Files
spellforge/docs_dev/specs/2026-07-21-spell-editor-fieldize-design.md

142 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 法术编辑器并入 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, 01) |
| 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)」非法时应用被拒绝并给出错误状态,不写坏数据。