From 1f00720f44823bb108d57637c4add990e60c2bf1 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 11:01:22 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(designer):=20=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=A1=86=E6=92=91=E6=BB=A1=E9=9D=A2=E6=9D=BF=20+=20=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=E5=85=83=E7=B4=A0=E8=AF=8D=E8=A1=A8=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/game_designer/designer_ui.gd | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/addons/game_designer/designer_ui.gd b/addons/game_designer/designer_ui.gd index c2be71f..f4d1998 100644 --- a/addons/game_designer/designer_ui.gd +++ b/addons/game_designer/designer_ui.gd @@ -1,12 +1,17 @@ -## 游戏设计器通用 UI 辅助(静态方法,各标签页复用) +## 游戏设计器通用 UI 辅助(静态方法 + 共享常量,各标签页复用) @tool extends RefCounted +## 元素词表(spell_tab 复选框 / resonance_tab 元素下拉共用);label 双语显示,tag 存储,index 对齐 +const ELEM_LABELS := ["火 Fire", "冰 Ice", "雷 Lightning", "水 Water", "毒 Poison"] +const ELEM_TAGS := ["tag:fire", "tag:ice", "tag:lightning", "tag:water", "tag:poison"] + static func spin(min_v: float, max_v: float, step: float, val: float) -> SpinBox: var s := SpinBox.new() s.min_value = min_v; s.max_value = max_v; s.step = step s.value = val s.custom_minimum_size = Vector2(80, 0) + s.size_flags_horizontal = Control.SIZE_EXPAND_FILL return s static func line(text: String, placeholder: String = "") -> LineEdit: @@ -14,6 +19,7 @@ static func line(text: String, placeholder: String = "") -> LineEdit: le.text = text le.placeholder_text = placeholder le.custom_minimum_size = Vector2(120, 0) + le.size_flags_horizontal = Control.SIZE_EXPAND_FILL return le static func opt(items: Array, selected: int) -> OptionButton: @@ -21,6 +27,7 @@ static func opt(items: Array, selected: int) -> OptionButton: for it in items: o.add_item(str(it)) o.selected = clampi(selected, 0, items.size() - 1) + o.size_flags_horizontal = Control.SIZE_EXPAND_FILL return o static func header(text: String) -> Label: From 95af2e1db64c084cbfe62ef7b1b7ff2204ba0f36 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 11:11:27 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(spell-editor):=20=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E6=94=B9=E5=A4=8D=E9=80=89=E6=A1=86=20+=20?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E5=8F=8C=E8=AF=AD=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/game_designer/spell_tab.gd | 39 ++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/addons/game_designer/spell_tab.gd b/addons/game_designer/spell_tab.gd index c0be1e0..96a6dd3 100644 --- a/addons/game_designer/spell_tab.gd +++ b/addons/game_designer/spell_tab.gd @@ -7,11 +7,12 @@ extends VBoxContainer const UI = preload("res://addons/game_designer/designer_ui.gd") const PATH := "res://data/spells.json" -const TYPE_NAMES := ["ACTION", "MODIFIER", "TRIGGER", "LOGIC"] -const ACTION_KINDS := ["弹道", "zone 毒池", "summon 召唤"] # 0=默认(不写 action_kind) 1=zone 2=summon -const LOGIC_OPS := ["every_n_shots", "if_hp_below", "if_enemy_nearby", "loop"] -const DMG_LABELS := ["物理", "火", "冰", "雷", "毒", "奥术"] # index = damage_type 整数 -const ST_LABELS := ["无", "燃烧", "冻结", "中毒", "潮湿", "沾油", "眩晕", "连击", "易伤"] # index = status 整数 +const TYPE_NAMES := ["动作 ACTION", "修饰 MODIFIER", "触发 TRIGGER", "逻辑 LOGIC"] +const ACTION_KINDS := ["弹道 Projectile", "毒池 Zone", "召唤 Summon"] # 0=默认(不写 action_kind) 1=zone 2=summon +const LOGIC_OPS := ["every_n_shots", "if_hp_below", "if_enemy_nearby", "loop"] # 存储 key,不变 +const LOGIC_OP_LABELS := ["每 N 次 every_n_shots", "生命低于 if_hp_below", "附近有敌 if_enemy_nearby", "循环 loop"] # 下拉显示 +const DMG_LABELS := ["物理 Physical", "火 Fire", "冰 Ice", "雷 Lightning", "毒 Poison", "奥术 Arcane"] # index = damage_type 整数 +const ST_LABELS := ["无 None", "燃烧 Burn", "冻结 Freeze", "中毒 Poison", "潮湿 Wet", "沾油 Oily", "眩晕 Stun", "连击 Combo", "易伤 Vuln"] # index = status 整数 var _data: Dictionary = {} var _cur: String = "" @@ -21,7 +22,8 @@ var _id: LineEdit var _type: OptionButton var _name: LineEdit var _desc: LineEdit -var _tags: LineEdit +var _elem_checks: Array = [] +var _custom_tags: LineEdit var _mana: SpinBox var _shop: SpinBox var _unlock: SpinBox @@ -52,7 +54,14 @@ func _ready() -> void: _type.item_selected.connect(_on_type_or_sub_changed) _name = _row("显示名", UI.line("", "tr 键或文本")) _desc = _row("描述", UI.line("", "tr 键或文本")) - _tags = _row("元素标签", UI.line("", "逗号分隔,如 tag:fire")) + var erow := HBoxContainer.new() + erow.add_child(UI.cell_label("元素标签")) + _elem_checks.clear() + for i in UI.ELEM_LABELS.size(): + var cb := CheckBox.new(); cb.text = UI.ELEM_LABELS[i] + _elem_checks.append(cb); erow.add_child(cb) + add_child(erow) + _custom_tags = _row("自定义标签", UI.line("", "非元素 tag,逗号分隔")) add_child(HSeparator.new()) _mana = _row("蓝耗 mana_cost", UI.spin(0, 100000, 1, 0)) @@ -62,7 +71,7 @@ func _ready() -> void: _akind = UI.opt(ACTION_KINDS, 0) _akind.item_selected.connect(_on_type_or_sub_changed) _akind_row = _labeled_row("动作种类", _akind) - _lop = UI.opt(LOGIC_OPS, 0) + _lop = UI.opt(LOGIC_OP_LABELS, 0) _lop.item_selected.connect(_on_type_or_sub_changed) _lop_row = _labeled_row("逻辑 logic_op", _lop) @@ -208,7 +217,13 @@ func _on_select(idx: int) -> void: _type.selected = clampi(int(d.get("type", 0)), 0, 3) _name.text = String(d.get("display_name", "")) _desc.text = String(d.get("description", "")) - _tags.text = ",".join(d.get("element_tags", [])) + var etags: Array = d.get("element_tags", []) + for i in _elem_checks.size(): + _elem_checks[i].button_pressed = UI.ELEM_TAGS[i] in etags + var custom_list := [] + for t in etags: + if not (String(t) in UI.ELEM_TAGS): custom_list.append(String(t)) + _custom_tags.text = ",".join(custom_list) var ak := String(meta.get("action_kind", "")) _akind.selected = (1 if ak == "zone" else (2 if ak == "summon" else 0)) _lop.selected = maxi(0, LOGIC_OPS.find(String(meta.get("logic_op", "every_n_shots")))) @@ -287,9 +302,11 @@ func _on_apply() -> void: if not (meta is Dictionary): UI.set_status(_status, "✗ 其它(JSON) 不是合法对象", true); return var tags: Array = [] - for t in _tags.text.split(",", false): + for i in _elem_checks.size(): + if _elem_checks[i].button_pressed: tags.append(UI.ELEM_TAGS[i]) + for t in _custom_tags.text.split(",", false): var s := t.strip_edges() - if s != "": tags.append(s) + if s != "" and not (s in tags): tags.append(s) if _cur != "" and _cur != sid and _data.has(_cur): _data.erase(_cur) _data[sid] = {"type": _type.selected, "display_name": _name.text, From d17a4579d53766b3d7f69e2b71d397cd7d93addc Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 11:12:51 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(designer):=20=E5=85=B1=E9=B8=A3?= =?UTF-8?q?=E9=85=8D=E6=96=B9=E5=85=83=E7=B4=A0=20A/B=20=E6=94=B9=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=20+=20=E5=8C=B9=E9=85=8D=E6=96=B9=E5=BC=8F=E5=8F=8C?= =?UTF-8?q?=E8=AF=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/game_designer/resonance_tab.gd | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/addons/game_designer/resonance_tab.gd b/addons/game_designer/resonance_tab.gd index d280f7e..d5a23e6 100644 --- a/addons/game_designer/resonance_tab.gd +++ b/addons/game_designer/resonance_tab.gd @@ -4,13 +4,14 @@ extends VBoxContainer const UI = preload("res://addons/game_designer/designer_ui.gd") const PATH = "res://data/resonance.json" -const MATCH = ["adjacent", "anywhere_in_deck"] +const MATCH = ["adjacent", "anywhere_in_deck"] # 存储 key +const MATCH_LABELS = ["相邻 adjacent", "卡组内任意 anywhere_in_deck"] var _data: Array = [] var _cur: int = -1 var _id: LineEdit -var _p0: LineEdit -var _p1: LineEdit +var _p0: OptionButton +var _p1: OptionButton var _match: OptionButton var _result: LineEdit var _consume: CheckBox @@ -29,9 +30,9 @@ func _ready() -> void: brow.add_child(UI.btn("🗑删除", _on_delete)) add_child(HSeparator.new()) _id = _row("配方 ID", UI.line("")) - _p0 = _row("元素 A", UI.line("", "tag:water")) - _p1 = _row("元素 B", UI.line("", "tag:lightning")) - _match = _row("匹配方式", UI.opt(MATCH, 0)) + _p0 = _row("元素 A", UI.opt(UI.ELEM_LABELS, 0)) + _p1 = _row("元素 B", UI.opt(UI.ELEM_LABELS, 0)) + _match = _row("匹配方式", UI.opt(MATCH_LABELS, 0)) _result = _row("产物法术 ID", UI.line("", "action_plasma_storm")) _consume = CheckBox.new(); _consume.text = "消耗输入法术(合成后移除原法术)" add_child(_consume) @@ -61,8 +62,8 @@ func _on_select(idx: int) -> void: var d: Dictionary = _data[idx] var p: Array = d.get("pattern", ["", ""]) _id.text = String(d.get("id", "")) - _p0.text = String(p[0]) if p.size() > 0 else "" - _p1.text = String(p[1]) if p.size() > 1 else "" + _p0.selected = max(0, UI.ELEM_TAGS.find(String(p[0])) if p.size() > 0 else 0) + _p1.selected = max(0, UI.ELEM_TAGS.find(String(p[1])) if p.size() > 1 else 0) _match.selected = max(0, MATCH.find(String(d.get("match", "adjacent")))) _result.text = String(d.get("result_spell_id", "")) _consume.button_pressed = bool(d.get("consume_inputs", false)) @@ -70,7 +71,7 @@ func _on_select(idx: int) -> void: func _on_apply() -> void: var r := { "id": _id.text.strip_edges(), - "pattern": [_p0.text.strip_edges(), _p1.text.strip_edges()], + "pattern": [UI.ELEM_TAGS[_p0.selected], UI.ELEM_TAGS[_p1.selected]], "match": MATCH[_match.selected], "result_spell_id": _result.text.strip_edges(), "consume_inputs": _consume.button_pressed, @@ -83,7 +84,7 @@ func _on_apply() -> void: UI.set_status(_status, "✓ 已应用(记得保存)") func _on_new() -> void: - _data.append({"id": "new_recipe", "pattern": ["tag:a", "tag:b"], + _data.append({"id": "new_recipe", "pattern": ["tag:water", "tag:lightning"], "match": "adjacent", "result_spell_id": "", "consume_inputs": true}) _cur = _data.size() - 1 _refresh() From c08d526a18de394f43028a89a15a59aa9c381ff4 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 11:13:54 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat(designer):=20=E6=B3=A2=E6=AC=A1/Core/?= =?UTF-8?q?=E7=8A=B6=E6=80=81=20=E6=9E=9A=E4=B8=BE=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E5=BD=92=E4=B8=80=E4=B8=BA=E4=B8=AD=E8=8B=B1=E5=8F=8C=E8=AF=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/game_designer/core_tab.gd | 4 ++-- addons/game_designer/status_tab.gd | 2 +- addons/game_designer/wave_tab.gd | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/game_designer/core_tab.gd b/addons/game_designer/core_tab.gd index 5947847..640fc32 100644 --- a/addons/game_designer/core_tab.gd +++ b/addons/game_designer/core_tab.gd @@ -4,8 +4,8 @@ extends VBoxContainer const UI = preload("res://addons/game_designer/designer_ui.gd") const PATH = "res://data/cores.json" -const TOPO = ["LINEAR 线性", "MATRIX 矩阵", "CIRCUIT 电路"] -const FEAT = ["无", "PERSISTENT_MEMORY 记忆", "DUAL_STREAM 双流", "ALWAYS_CAST_LAST", "SHUFFLE_DECK", "INFINITE_SPELLS"] +const TOPO = ["线性 LINEAR", "矩阵 MATRIX", "电路 CIRCUIT"] +const FEAT = ["无 None", "记忆 PERSISTENT_MEMORY", "双流 DUAL_STREAM", "末位施法 ALWAYS_CAST_LAST", "洗牌 SHUFFLE_DECK", "无限法术 INFINITE_SPELLS"] var _data: Dictionary = {} var _cur: String = "" diff --git a/addons/game_designer/status_tab.gd b/addons/game_designer/status_tab.gd index 71cb9ef..edc769c 100644 --- a/addons/game_designer/status_tab.gd +++ b/addons/game_designer/status_tab.gd @@ -4,7 +4,7 @@ extends VBoxContainer const UI = preload("res://addons/game_designer/designer_ui.gd") const PATH = "res://data/status_effects.json" -const STACK = ["REFRESH 刷新", "INTENSITY 叠层", "INDEPENDENT 独立"] +const STACK = ["刷新 REFRESH", "叠层 INTENSITY", "独立 INDEPENDENT"] var _data: Dictionary = {} var _cur: String = "" diff --git a/addons/game_designer/wave_tab.gd b/addons/game_designer/wave_tab.gd index 71c1a1a..1268131 100644 --- a/addons/game_designer/wave_tab.gd +++ b/addons/game_designer/wave_tab.gd @@ -4,8 +4,8 @@ extends VBoxContainer const UI = preload("res://addons/game_designer/designer_ui.gd") const PATH = "res://data/waves.json" -const TYPE_NAMES = ["Basic", "Fast", "Armored", "Elite", "MiniBoss", "Boss"] -const BOSS_NAMES = ["无", "MiniBoss(4)", "Boss(5)"] # 映射到 boss 值 -1/4/5 +const TYPE_NAMES = ["杂兵 Basic", "快速 Fast", "护甲 Armored", "精英 Elite", "首领 MiniBoss", "终焉 Boss"] +const BOSS_NAMES = ["无 None", "首领 MiniBoss", "终焉 Boss"] # 映射到 boss 值 -1/4/5 const BOSS_VALS = [-1, 4, 5] var _waves: Array = [] From f8e810a2c08f4bafd746b1b5b685fcf0939f27ae Mon Sep 17 00:00:00 2001 From: Joywayer Date: Wed, 22 Jul 2026 11:14:42 +0800 Subject: [PATCH 5/5] =?UTF-8?q?docs(spell-editor):=20=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E6=94=B9=E5=A4=8D=E9=80=89=E6=A1=86=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/handbook/06_spell_editor.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/handbook/06_spell_editor.md b/docs/handbook/06_spell_editor.md index 586ed84..e4cc56e 100644 --- a/docs/handbook/06_spell_editor.md +++ b/docs/handbook/06_spell_editor.md @@ -27,11 +27,12 @@ Godot 编辑器 → 底部面板 → 🎮 游戏设计器 → ✨ 法术 标签 └────────────────────────┘ [➕新增] [⧉复制] [🗑删除] ──────────────────────── -ID(唯一)/ 类型[ACTION ▾]/ 显示名 / 描述 / 元素标签(逗号) +ID(唯一)/ 类型[动作 ACTION ▾]/ 显示名 / 描述 +元素标签:[✓]火 [ ]冰 [✓]雷 [ ]水 [ ]毒 + 自定义标签框 ── 通用 meta ── 蓝耗 mana_cost / 商店价 shop_cost / 解锁价 unlock_cost ── 动态字段(随类型/子类型自动切换)── -ACTION:动作种类[弹道 / zone 毒池 / summon 召唤] + 对应字段 +ACTION:动作种类[弹道 / 毒池 / 召唤] + 对应字段 (伤害 / 速度 / 存活 / 半径 / 伤害类型▾ / 施加状态▾ / …) MODIFIER / TRIGGER / LOGIC(logic_op▾ 子选择)各自字段 ── 其它(JSON) ── ← schema 之外的键,保留不丢 @@ -50,7 +51,7 @@ MODIFIER / TRIGGER / LOGIC(logic_op▾ 子选择)各自字段 2. 改 **ID**(如 `action_lightning`,必须全局唯一) 3. 选 **类型**(ACTION / MODIFIER / TRIGGER / LOGIC) 4. 填 **显示名 / 描述**(建议填 `tr()` 键,如 `SPELL_LIGHTNING_NAME`,见 [04 本地化](04_localization.md)) -5. 填 **元素标签**(共鸣用,如 `tag:lightning`;多个用逗号) +5. 勾选 **元素标签**(火/冰/雷/水/毒 复选框,可多选;共鸣用);非元素标签填「自定义标签」框 6. 填 **meta 字段**——通用区(蓝耗/商店价/解锁价)常驻;下方动态区随类型(及 ACTION 动作种类 / LOGIC logic_op)自动列出对应字段,直接填数值即可 7. 点 **✓应用到列表** → 校验并写入内存 8. 点 **💾保存文件** → 写入 `res://data/spells.json`