7.6 KiB
7.6 KiB
游戏设计器 整数元组列表编辑器(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
## 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: <TupleListEditor 实例>(类型标注用Control或不标注,经.new()创建)。 _ready():把原替换为: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)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", []))→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():原替换为:var edges = JSON.parse_string(_edges.text) if _edges.text.strip_edges() != "" else [] if not (edges is Array): edges = [](后续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: <TupleListEditor 实例>。 _ready():把原替换为: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)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():原替换为:var thresholds = JSON.parse_string(_aether.text) if _aether.text.strip_edges() != "" else [] if not (thresholds is Array): thresholds = [](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. 验证
validate_script三文件全绿;reload_project+get_editor_errors无报错(GDScript 类缓存问题可能需重启编辑器,同上一增量)。- 回读往返(
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逐元素一致。
- core:实例化 core_tab,对
- 目测:Core 页电路边为 from/to 行 + 加/删;平衡页碎片阈值为 波次/碎片 行 + 加/删(重启编辑器后由用户确认)。
7. 范围外(YAGNI)
- 不动 spell
其它(JSON)(逃生舱)。 - 行编辑器不做排序/去重/跨行校验。
- 不改数据文件内容(现有 edges/aether 原样回读)。
8. 验收标准
- Core 页「电路边」为 from→to 的 SpinBox 行,可加/删;
circuit_fork正确显示两条边 (0→1, 0→2),应用保存后cores.json的edges不变。 - 平衡页「碎片阈值」为 波次/碎片 的 SpinBox 行,可加/删;显示 5 行,保存后
balance.json的aether_thresholds不变。 validate_script无报错;core 与 balance 回读往返一致。- spell
其它(JSON)仍在、未被改动。