From a0e205a03127e0ade588c680f2ebb95e09444850 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Fri, 31 Jul 2026 12:05:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(attr):=20=E6=B8=B8=E6=88=8F=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E5=99=A8=E6=96=B0=E5=A2=9E=E3=80=8C=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E3=80=8D=E9=A1=B5=EF=BC=8C=E5=AD=97=E6=AE=B5=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E8=BE=91=20attributes.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 CLAUDE.md 编辑器插件规范:控件经 designer_ui 工厂创建、标签中英双语中文在前、 combine 下拉存 key 不存显示串、_save 合并式写回防丢键、UI 在 _ready 而非 _init 构建。 另修:SpinBox 按 step 吸附会引入双精度噪声(0.01 → 0.00999999999476), 直接写回会把噪声固化进 JSON;_collect 用 snappedf(v, 1e-6) 抹平, 使「不改任何值就保存」得到逐位一致的 attributes.json。 Co-Authored-By: Claude Opus 5 --- addons/game_designer/attribute_tab.gd | 70 +++++++++++++++++++++++ addons/game_designer/attribute_tab.gd.uid | 1 + addons/game_designer/designer_panel.gd | 1 + 3 files changed, 72 insertions(+) create mode 100644 addons/game_designer/attribute_tab.gd create mode 100644 addons/game_designer/attribute_tab.gd.uid diff --git a/addons/game_designer/attribute_tab.gd b/addons/game_designer/attribute_tab.gd new file mode 100644 index 0000000..3d511d9 --- /dev/null +++ b/addons/game_designer/attribute_tab.gd @@ -0,0 +1,70 @@ +## 属性编辑器标签 — 玩家属性的 base / soft / hard / combine +## 数据:res://data/attributes.json(权威属性定义见 docs/design/numerical_design.md §1.1) +@tool +extends VBoxContainer + +const UI = preload("res://addons/game_designer/designer_ui.gd") +const PATH = "res://data/attributes.json" +# 显示与存储解耦:LABELS 仅供下拉显示,KEYS 才写进 JSON,二者 index 对齐 +const COMBINE_KEYS = ["hybrid", "inverse", "add_int"] +const COMBINE_LABELS = ["混合 hybrid", "反向 inverse", "整数加 add_int"] +const ATTR_ORDER = ["cpu_limit", "move_speed", "cast_delay_mod", "hp_max"] + +var _data: Dictionary = {} +var _rows: Dictionary = {} # attr_id → {"base": SpinBox, "soft": SpinBox, "hard": SpinBox, "combine": OptionButton} +var _status: Label + +func _ready() -> void: + add_child(UI.header("📊 属性编辑器 — 玩家属性 base / soft / hard / combine")) + _load() + var grid := GridContainer.new(); grid.columns = 5; add_child(grid) + for h in ["属性", "基准 base", "软上限 soft", "硬上限 hard", "合成 combine"]: + var l := Label.new(); l.text = h; l.modulate = Color(0.7, 0.8, 1.0) + grid.add_child(l) + for attr_id in ATTR_ORDER: + var d: Dictionary = _data.get(attr_id, {}) + grid.add_child(UI.cell_label(String(d.get("display_name", attr_id)))) + var sp_base := UI.spin(-99999.0, 99999.0, 0.01, float(d.get("base", 0.0))) + var sp_soft := UI.spin(-99999.0, 99999.0, 0.01, float(d.get("soft", 0.0))) + var sp_hard := UI.spin(-99999.0, 99999.0, 0.01, float(d.get("hard", 0.0))) + var idx: int = COMBINE_KEYS.find(String(d.get("combine", "hybrid"))) + var op := UI.opt(COMBINE_LABELS, idx if idx >= 0 else 0) + grid.add_child(sp_base); grid.add_child(sp_soft); grid.add_child(sp_hard); grid.add_child(op) + _rows[attr_id] = {"base": sp_base, "soft": sp_soft, "hard": sp_hard, "combine": op} + add_child(HSeparator.new()) + var tip := Label.new() + tip.text = "hard=0 在 hybrid 下表示不钳制;inverse 的 hard 是「下限」,填 0 即钳到 0(与 hybrid 相反);add_int 拒绝 pct 加成。" + tip.modulate = Color(0.7, 0.7, 0.7) + add_child(tip) + add_child(UI.btn("💾 保存 attributes.json", _save)) + _status = UI.status_label(); add_child(_status) + +func _load() -> void: + var d = UI.load_json(PATH) + _data = d if d is Dictionary else {} + +## SpinBox 按 step 吸附会引入双精度噪声(0.01 → 0.00999999999476), +## 直接写回会把噪声固化进 JSON;抹到 1e-6 精度,远细于本页任何有意义的调参粒度。 +static func _clean(v: float) -> float: + return snappedf(v, 0.000001) + +## 表单 → 字典(合并式:以 _data 为基底保留未知键) +func _collect() -> Dictionary: + var out: Dictionary = _data.duplicate(true) + for attr_id in ATTR_ORDER: + var r: Dictionary = _rows.get(attr_id, {}) + if r.is_empty(): + continue + var entry: Dictionary = out.get(attr_id, {}).duplicate(true) + entry["base"] = _clean(r["base"].value) + entry["soft"] = _clean(r["soft"].value) + entry["hard"] = _clean(r["hard"].value) + entry["combine"] = COMBINE_KEYS[r["combine"].selected] # 存 key,不存双语显示串 + out[attr_id] = entry + return out + +func _save() -> void: + if UI.save_json(PATH, _collect()): + UI.set_status(_status, "💾 已保存(重启 F5 生效)") + else: + UI.set_status(_status, "✗ 保存失败", true) diff --git a/addons/game_designer/attribute_tab.gd.uid b/addons/game_designer/attribute_tab.gd.uid new file mode 100644 index 0000000..78d0398 --- /dev/null +++ b/addons/game_designer/attribute_tab.gd.uid @@ -0,0 +1 @@ +uid://dh0i3k3dmv3mu diff --git a/addons/game_designer/designer_panel.gd b/addons/game_designer/designer_panel.gd index 04b3854..830de5e 100644 --- a/addons/game_designer/designer_panel.gd +++ b/addons/game_designer/designer_panel.gd @@ -17,6 +17,7 @@ func _init() -> void: _add_tab(tabs, "🔥 状态", preload("res://addons/game_designer/status_tab.gd").new()) _add_tab(tabs, "👑 Boss", preload("res://addons/game_designer/boss_tab.gd").new()) _add_tab(tabs, "⚖ 平衡", preload("res://addons/game_designer/balance_tab.gd").new()) + _add_tab(tabs, "📊 属性", preload("res://addons/game_designer/attribute_tab.gd").new()) func _add_tab(tabs: TabContainer, title: String, content: Control) -> void: var scroll := ScrollContainer.new()