docs(spell-editor): 法术编辑器并入 game_designer + 字段化表单设计
This commit is contained in:
@@ -0,0 +1,141 @@
|
|||||||
|
# 法术编辑器并入 game_designer + 字段化表单 — 设计
|
||||||
|
|
||||||
|
- 日期:2026-07-21
|
||||||
|
- 状态:设计已确认,待实现
|
||||||
|
- 范围:编辑器工具层(`addons/`)+ 文档,**游戏运行时代码零改动**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景与问题
|
||||||
|
|
||||||
|
当前有两个已启用的编辑器插件:
|
||||||
|
|
||||||
|
- `addons/spell_editor/`(独立插件,左下停靠面板,入口 `spell_editor.gd` → `spell_dock.gd`)
|
||||||
|
- `addons/game_designer/`(底部面板,`designer_panel.gd` 用 `TabContainer` 整合多个 tab)
|
||||||
|
|
||||||
|
`designer_panel.gd` 已经把 `spell_dock.gd` 复用为「✨ 法术」标签页 → **法术编辑器同时出现在两处,入口冗余**。
|
||||||
|
|
||||||
|
同时,`spell_dock.gd` 的 `meta` 字段是一个**自由格式 JSON TextEdit**,随法术类型/子类型变化很大,容易配置错误、可读性差 —— 与 `core_tab.gd`(字段化输入框)风格不一致。
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
|
||||||
|
1. **并入**:法术编辑只保留在 `game_designer` 底部面板的 tab 里;删除独立 `spell_editor` 插件。
|
||||||
|
2. **字段化**:用按类型动态切换的输入框表单替代原始 JSON `meta`,对齐 `core_tab.gd` 风格。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 结构变更(并入)
|
||||||
|
|
||||||
|
- 新建 `addons/game_designer/spell_tab.gd`,结构对齐 `core_tab.gd`:
|
||||||
|
- `@tool extends VBoxContainer`
|
||||||
|
- `const UI = preload("res://addons/game_designer/designer_ui.gd")`
|
||||||
|
- 复用 `UI.spin / line / opt / btn / cell_label / header / status_label / set_status / load_json / save_json`
|
||||||
|
- 布局:标题 → 列表(ItemList) → `➕新增 / ⧉复制 / 🗑删除` → 表单区 → `✓应用到列表 / 💾保存文件 / ↻重载` → 状态行
|
||||||
|
- `designer_panel.gd` 第 12 行:
|
||||||
|
- 由 `preload("res://addons/spell_editor/spell_dock.gd").new()`
|
||||||
|
- 改为 `preload("res://addons/game_designer/spell_tab.gd").new()`
|
||||||
|
- **删除独立插件**:
|
||||||
|
- 从 `project.godot` `[editor_plugins] enabled` 数组移除 `"res://addons/spell_editor/plugin.cfg"`
|
||||||
|
- 删除整个 `addons/spell_editor/` 目录(`spell_editor.gd(.uid)`、`spell_dock.gd(.uid)`、`plugin.cfg`)
|
||||||
|
- 数据路径与文件格式不变:`res://data/spells.json`;`SpellRegistry` 运行时加载逻辑零改动。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 动态表单(核心)
|
||||||
|
|
||||||
|
### 3.1 固定区(所有类型)
|
||||||
|
`ID`(LineEdit,唯一) / `类型`(OptionButton: ACTION/MODIFIER/TRIGGER/LOGIC) / `显示名`(LineEdit) / `描述`(LineEdit) / `元素标签`(LineEdit,逗号分隔)。
|
||||||
|
|
||||||
|
### 3.2 通用 meta 区(所有类型常驻)
|
||||||
|
`mana_cost`(int) / `shop_cost`(int) / `unlock_cost`(int)。
|
||||||
|
|
||||||
|
### 3.3 动态 meta 区
|
||||||
|
一个占位容器(如 `VBoxContainer`),随 `类型` 变化(ACTION 额外看 `action_kind` 子选择、LOGIC 额外看 `logic_op` 子选择)**销毁并重建其子节点**。字段 schema:
|
||||||
|
|
||||||
|
| 类型 | 子选择 | 字段(控件类型) |
|
||||||
|
|---|---|---|
|
||||||
|
| ACTION | 弹道(默认,action_kind 省略) | base_damage(float) speed(float) lifetime(float) radius(float) damage_type(enum) apply_status_id(enum,含「无」) apply_combo_mark(bool) pierce(int) |
|
||||||
|
| ACTION | zone 毒池(action_kind="zone") | radius(float) status_id(enum) duration(float) tick_interval(float) |
|
||||||
|
| ACTION | summon 召唤(action_kind="summon") | lifetime(float) range(float) fire_interval(float) base_damage(float) speed(float) damage_type(enum) |
|
||||||
|
| MODIFIER | — | damage_add(float) damage_mult(float) spread_add(int) multicast(int) pierce(int) heavy_cost(int) mana_leech(int) |
|
||||||
|
| TRIGGER | — | base_damage(float) speed(float) lifetime(float) radius(float) damage_type(enum) |
|
||||||
|
| LOGIC | every_n_shots | n(int) reg(int) |
|
||||||
|
| LOGIC | if_hp_below | threshold(float, 0–1) |
|
||||||
|
| LOGIC | if_enemy_nearby | range(float) |
|
||||||
|
| LOGIC | loop | count(int) |
|
||||||
|
|
||||||
|
- ACTION 的 `action_kind` 用一个 OptionButton 子选择器:`弹道 / zone 毒池 / summon 召唤`;选「弹道」时不写 `action_kind` 键。
|
||||||
|
- LOGIC 的 `logic_op` 用一个 OptionButton 子选择器:`every_n_shots / if_hp_below / if_enemy_nearby / loop`;始终写入 `logic_op` 字符串。
|
||||||
|
|
||||||
|
### 3.4 枚举下拉(整数存储)
|
||||||
|
标签来自域枚举,控件用 `UI.opt(...)`,存整数值:
|
||||||
|
|
||||||
|
- `damage_type`(`scripts/autoloads/damage_type.gd`):0 物理 / 1 火 / 2 冰 / 3 雷 / 4 毒 / 5 奥术
|
||||||
|
- `status`(`apply_status_id` / `status_id`,`scripts/autoloads/status_id.gd`):0 无 / 1 燃烧 / 2 冻结 / 3 中毒 / 4 潮湿 / 5 沾油 / 6 眩晕 / 7 连击 / 8 易伤
|
||||||
|
- 下拉 index 即整数值(0 = 无 = 不写该键)。
|
||||||
|
|
||||||
|
> 枚举标签在 `spell_tab.gd` 内以常量数组硬编码,index 对齐上述整数值;若域枚举新增值,需同步更新此数组(在文件顶部注释标注来源)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据安全(防丢键 / 保持简洁)
|
||||||
|
|
||||||
|
### 4.1 「其它(JSON)」逃生舱
|
||||||
|
动态区下方放一个小 `TextEdit`(标签「其它(JSON)」)。
|
||||||
|
|
||||||
|
- **加载**(选中法术):把 `meta` 中 schema **已知键**填入对应控件;**剩余未知键**打包成 JSON 字符串填入此框(无剩余则空)。
|
||||||
|
- **应用**:最终 `meta` = 通用字段 + 当前类型/子类型动态字段 + 解析「其它(JSON)」得到的键(后者仅补充未被 schema 覆盖的键;若解析失败则报错、拒绝应用)。
|
||||||
|
|
||||||
|
作用:游戏日后新增 meta 键、或某法术带非常规键时,编辑器不会静默丢弃。
|
||||||
|
|
||||||
|
### 4.2 可选键清洁写出
|
||||||
|
以下键在值为默认(0 / false / "" / 未设)时**不写入** JSON,保持与现有 `spells.json` 一致的简洁:
|
||||||
|
`unlock_cost`(=0) / `apply_status_id`(=0) / `apply_combo_mark`(=false) / `pierce`(=0) / `damage_mult`(=1.0,乘数默认值,等于 1.0 时不写) / `spread_add`(=0) / `multicast`(=0) / `heavy_cost`(=0) / `mana_leech`(=0) / `reg`(=0)。
|
||||||
|
|
||||||
|
> `mana_cost` / `shop_cost` 始终写入(含 0,等离子风暴 `mana_cost:0 shop_cost:0` 是有意义的显式 0)。
|
||||||
|
|
||||||
|
### 4.3 回读验证目标
|
||||||
|
现有 `data/spells.json` 全部 22 条必须能被正确回读进新表单(含 zone、summon、全部 4 种 logic_op),再次「应用+保存」后语义等价(允许可选默认键因 §4.2 被省略,属预期简化)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 组件边界
|
||||||
|
|
||||||
|
| 单元 | 职责 | 依赖 |
|
||||||
|
|---|---|---|
|
||||||
|
| `spell_tab.gd` | 法术 tab 全部 UI + 增删改查 + schema 驱动的表单构建/读写 | `designer_ui.gd`;`data/spells.json` |
|
||||||
|
| `designer_panel.gd` | 只改一行 preload 指向新 tab | `spell_tab.gd` |
|
||||||
|
| `project.godot` | 移除 spell_editor 插件启用项 | — |
|
||||||
|
|
||||||
|
`spell_tab.gd` 内部建议拆出:`_schema_for(type, sub)` 返回字段描述数组;`_rebuild_dynamic()` 依 schema 建控件;`_read_form()`/`_write_form(meta)` 在控件与 meta 字典间搬运。schema 表是唯一事实源,加/改字段只动这张表。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 文档
|
||||||
|
|
||||||
|
更新 `docs/handbook/06_spell_editor.md`:
|
||||||
|
|
||||||
|
- 入口改为「Godot 编辑器底部面板 → 🎮 游戏设计器 → ✨ 法术 标签页」。
|
||||||
|
- 删除「1. 启用插件(左下停靠区)」相关描述。
|
||||||
|
- 界面示意图改为字段化表单(固定区 + 通用区 + 动态区 + 其它JSON)。
|
||||||
|
- 「meta 字段速查」保留(作为字段语义参考),补一句「现由表单按类型呈现」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 范围外(YAGNI)
|
||||||
|
|
||||||
|
- 不动其它 tab(敌人/波次/Core/共鸣/状态/平衡)。
|
||||||
|
- 不动游戏运行时代码(`SpellRegistry` / VM / `wand_preset.gd`)。
|
||||||
|
- 不做字段级校验规则引擎(超范围);仅做 ID 非空、JSON 合法性、枚举 index 合法性等基本校验。
|
||||||
|
- 不做 tag 的下拉选择器(元素标签仍为逗号分隔文本,沿用现状)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 验收标准
|
||||||
|
|
||||||
|
1. Godot 编辑器仅在底部「🎮 游戏设计器 → ✨ 法术」出现法术编辑,左下不再有独立「法术编辑器」停靠面板。
|
||||||
|
2. `addons/spell_editor/` 目录已删除,`project.godot` 不再引用它,编辑器加载无报错。
|
||||||
|
3. 逐一选中 22 条现有法术,字段正确回读(类型/子类型/枚举/数值/其它JSON 均对)。
|
||||||
|
4. 新增一条各类型法术(含 zone、summon、每种 logic_op),应用+保存后 `spells.json` 合法且游戏 F5 能加载。
|
||||||
|
5. 改 ID 时旧键被移除;复制/删除正常。
|
||||||
|
6. 「其它(JSON)」非法时应用被拒绝并给出错误状态,不写坏数据。
|
||||||
Reference in New Issue
Block a user