From 0967b3f10b81800458c02018207c90dce7e83850 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 12:02:46 +0800 Subject: [PATCH] =?UTF-8?q?docs(designer):=20=E6=95=B4=E6=95=B0=E5=85=83?= =?UTF-8?q?=E7=BB=84=E5=88=97=E8=A1=A8=E7=BC=96=E8=BE=91=E5=99=A8=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=EF=BC=88edges/aether=20=E5=8E=BB=20JSON=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...07-22-designer-tuple-list-editor-design.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs_dev/specs/2026-07-22-designer-tuple-list-editor-design.md diff --git a/docs_dev/specs/2026-07-22-designer-tuple-list-editor-design.md b/docs_dev/specs/2026-07-22-designer-tuple-list-editor-design.md new file mode 100644 index 0000000..9fc014e --- /dev/null +++ b/docs_dev/specs/2026-07-22-designer-tuple-list-editor-design.md @@ -0,0 +1,160 @@ +# 游戏设计器 整数元组列表编辑器(edges / aether_thresholds 去 JSON)— 设计 + +- 日期:2026-07-22 +- 状态:设计已确认(方案),待细化评审 +- 范围:编辑器工具层 `addons/game_designer/`,**游戏运行时代码与数据文件格式零改动** + +--- + +## 1. 背景与问题 + +游戏设计器仍有两处「可变长度小整数元组列表」用手写 JSON 文本框,易配置错、可读性差: + +- **Core `edges`(电路边)** —— `core_tab.gd` 的 `电路边(CIRCUIT,JSON)` TextEdit。CIRCUIT 拓扑法杖的槽位有向连线:`[{"from":0,"to":1},{"from":0,"to":2}]`。当前仅 `circuit_fork` 使用。 +- **平衡 `aether_thresholds`(碎片阈值)** —— `balance_tab.gd` 的 `碎片奖励阈值(JSON [[波次,碎片],...])` TextEdit。元进度奖励表:`[[1,0],[5,3],[10,8],[15,14],[20,20]]` = [波次, 累计碎片]。 + +法术 `其它(JSON)` 是**逃生舱**(收纳 schema 之外的未知键,本质装任意键、无法字段化),**保持不动**,不在本次范围。 + +### 目标 +用一个**共享的行编辑器**替代这两处 JSON 文本:每行 N 个 SpinBox + 🗑 删行,底部 ➕ 加行。 + +--- + +## 2. 新组件:`tuple_list_editor.gd` + +新建 `addons/game_designer/tuple_list_editor.gd`,`@tool extends VBoxContainer`,可复用行编辑器。 + +### 2.1 数据模型 +- 对外数据 = `Array[Array[number]]`(行的数组,每行一组数值)。调用方负责与自身 JSON 结构(dict / array)互转。 + +### 2.2 API +```gdscript +## col_specs: Array[Dictionary],每列一项: +## {"label": String, "min": float, "max": float, "step": float, "int": bool} +## label 可为 ""(不显示列标签);int=true 时 get_values 返回该列为整数 +func setup(col_specs: Array, add_label: String = "➕ 加行") -> void +func set_values(rows: Array) -> void # rows: Array[Array];清空并重建 +func get_values() -> Array # 返回 Array[Array],按列 int 规格转 int +``` + +### 2.3 行为 +- `setup`:记录列规格,创建内部 `_rows_box: VBoxContainer` + 底部 ➕ 按钮(点按追加一空行,值取各列 default 或 min)。 +- 每行:一个 HBoxContainer,含(可选列标签 Label +)N 个 SpinBox(`size_flags_horizontal = SIZE_EXPAND_FILL`)+ 尾部 🗑 按钮。🗑 处理器:`_rows_box.remove_child(row); row.queue_free()`(**立即移出树**,保证随后的 `get_values` 不再计入)。 +- `set_values`:先**立即** `remove_child + free` 所有旧行(不用 queue_free,避免同帧残留),再逐行 `_add_row`。 +- `get_values`:遍历 `_rows_box` 现存行,收集每行 SpinBox 值;`int` 列 `int(v)`,否则 `float(v)`。 + +### 2.4 边界 +- 无排序/校验(YAGNI);行顺序即用户可见顺序。 +- 不做去重(edges/aether 语义允许重复行;调用方如需可自行处理,本次不做)。 + +--- + +## 3. 集成:`core_tab.gd`(edges) + +- 成员:`var _edges: TextEdit` → `var _edges: `(类型标注用 `Control` 或不标注,经 `.new()` 创建)。 +- `_ready()`:把原 + ```gdscript + var el := Label.new(); el.text = "电路边(CIRCUIT,JSON)"; add_child(el) + _edges = TextEdit.new(); _edges.custom_minimum_size = Vector2(0, 50) + _edges.placeholder_text = '[{"from":0,"to":1},{"from":0,"to":2}]' + add_child(_edges) + ``` + 替换为: + ```gdscript + var el := Label.new(); el.text = "电路边(CIRCUIT,from → to)"; add_child(el) + _edges = preload("res://addons/game_designer/tuple_list_editor.gd").new() + _edges.setup([ + {"label": "from", "min": 0, "max": 63, "step": 1, "int": true}, + {"label": "→ to", "min": 0, "max": 63, "step": 1, "int": true}], "➕ 加边") + add_child(_edges) + ``` +- `_on_select()`:`_edges.text = JSON.stringify(d.get("edges", []))` → + ```gdscript + var erows := [] + for e in d.get("edges", []): + erows.append([int(e.get("from", 0)), int(e.get("to", 0))]) + _edges.set_values(erows) + ``` +- `_on_apply()`:原 + ```gdscript + var edges = JSON.parse_string(_edges.text) if _edges.text.strip_edges() != "" else [] + if not (edges is Array): + edges = [] + ``` + 替换为: + ```gdscript + var edges := [] + for r in _edges.get_values(): + edges.append({"from": r[0], "to": r[1]}) + ``` + (后续 `_data[cid] = { ... "edges": edges, ... }` 不变。) +- 其余(拓扑/矩阵行列等)不变。max=63 为槽位上限的宽松值(当前 slot_count ≤ 8,给足冗余)。 + +--- + +## 4. 集成:`balance_tab.gd`(aether_thresholds) + +- 成员:`var _aether: TextEdit` → `var _aether: `。 +- `_ready()`:把原 + ```gdscript + var al := Label.new(); al.text = "碎片奖励阈值 aether_thresholds(JSON [[波次,碎片],...])"; add_child(al) + _aether = TextEdit.new(); _aether.custom_minimum_size = Vector2(0, 50) + _aether.text = JSON.stringify(_data.get("aether_thresholds", [[1, 0], [5, 3], [10, 8], [15, 14], [20, 20]])) + add_child(_aether) + ``` + 替换为: + ```gdscript + var al := Label.new(); al.text = "碎片奖励阈值 aether_thresholds(波次 → 碎片)"; add_child(al) + _aether = preload("res://addons/game_designer/tuple_list_editor.gd").new() + _aether.setup([ + {"label": "波次", "min": 1, "max": 9999, "step": 1, "int": true}, + {"label": "碎片", "min": 0, "max": 9999, "step": 1, "int": true}], "➕ 加行") + add_child(_aether) + _aether.set_values(_data.get("aether_thresholds", [[1, 0], [5, 3], [10, 8], [15, 14], [20, 20]])) + ``` +- `_save()`:原 + ```gdscript + var thresholds = JSON.parse_string(_aether.text) if _aether.text.strip_edges() != "" else [] + if not (thresholds is Array): + thresholds = [] + ``` + 替换为: + ```gdscript + var thresholds := _aether.get_values() + ``` + (`out["aether_thresholds"] = thresholds` 不变。) + +--- + +## 5. 组件边界与改动清单 + +| 文件 | 改动 | +|---|---| +| `tuple_list_editor.gd`(新建) | 可复用整数元组行编辑器(setup/set_values/get_values) | +| `core_tab.gd` | `edges` 文本框 → 行编辑器;dict↔array 边界转换 | +| `balance_tab.gd` | `aether_thresholds` 文本框 → 行编辑器 | + +不动:spell `其它(JSON)`、运行时脚本、数据文件格式与内容、其它 tab。 + +--- + +## 6. 验证 + +1. `validate_script` 三文件全绿;`reload_project` + `get_editor_errors` 无报错(GDScript 类缓存问题可能需重启编辑器,同上一增量)。 +2. 回读往返(`execute_editor_script`): + - **core**:实例化 core_tab,对 `cores.json` 每个 core `_on_select` → `_on_apply` 组装的 `edges` 与原一致(`circuit_fork` 两条边;无 edges 的 core 结果为空数组、原本也无该键——比较时空数组 vs 缺键视为等价)。 + - **balance**:实例化 balance_tab(`_ready` 后)→ `_aether.get_values()` 与 `balance.json` 的 `aether_thresholds` 逐元素一致。 +3. 目测:Core 页电路边为 from/to 行 + 加/删;平衡页碎片阈值为 波次/碎片 行 + 加/删(重启编辑器后由用户确认)。 + +## 7. 范围外(YAGNI) + +- 不动 spell `其它(JSON)`(逃生舱)。 +- 行编辑器不做排序/去重/跨行校验。 +- 不改数据文件内容(现有 edges/aether 原样回读)。 + +## 8. 验收标准 + +1. Core 页「电路边」为 from→to 的 SpinBox 行,可加/删;`circuit_fork` 正确显示两条边 (0→1, 0→2),应用保存后 `cores.json` 的 `edges` 不变。 +2. 平衡页「碎片阈值」为 波次/碎片 的 SpinBox 行,可加/删;显示 5 行,保存后 `balance.json` 的 `aether_thresholds` 不变。 +3. `validate_script` 无报错;core 与 balance 回读往返一致。 +4. spell `其它(JSON)` 仍在、未被改动。