# 法术编辑器并入 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)」非法时应用被拒绝并给出错误状态,不写坏数据。