## 平衡编辑器标签 — 难度乘子(3 档)+ Boss 基础血量 @tool extends VBoxContainer const UI = preload("res://addons/game_designer/designer_ui.gd") const PATH = "res://data/balance.json" const MULT_KEYS = ["enemy_hp", "player_dmg", "wave_count", "boss_hp"] const MULT_LABELS = ["敌人血量×", "玩家受伤×", "波次怪数×", "Boss血量×"] const DIFF_NAMES = ["初学者", "标准", "挑战"] var _data: Dictionary = {} var _mult_spins: Dictionary = {} # key → [SpinBox ×3] var _miniboss: SpinBox var _boss: SpinBox var _aether var _status: Label var _iframe: SpinBox func _ready() -> void: add_child(UI.header("⚖ 平衡编辑器 — 难度乘子 & Boss 血量")) _load() # 难度乘子表格:4 行 × 3 难度 var grid := GridContainer.new(); grid.columns = 4; add_child(grid) grid.add_child(Label.new()) # 角 for dn in DIFF_NAMES: var l := Label.new(); l.text = dn; l.modulate = Color(0.7, 0.8, 1.0) grid.add_child(l) var mults: Dictionary = _data.get("difficulty_mults", {}) for i in MULT_KEYS.size(): var key: String = MULT_KEYS[i] grid.add_child(UI.cell_label(MULT_LABELS[i])) var arr: Array = mults.get(key, [1.0, 1.0, 1.0]) var spins: Array = [] for d in 3: var sp := UI.spin(0.0, 10.0, 0.05, float(arr[d]) if d < arr.size() else 1.0) grid.add_child(sp); spins.append(sp) _mult_spins[key] = spins add_child(HSeparator.new()) # Boss 基础血量 var bhp: Dictionary = _data.get("boss_hp", {}) var h1 := HBoxContainer.new(); add_child(h1) h1.add_child(UI.cell_label("MiniBoss(W8) 血量")) _miniboss = UI.spin(1, 999999, 10, float(bhp.get("4", 450))) h1.add_child(_miniboss) var h2 := HBoxContainer.new(); add_child(h2) h2.add_child(UI.cell_label("Final Boss(W20) 血量")) _boss = UI.spin(1, 999999, 10, float(bhp.get("5", 1800))) h2.add_child(_boss) add_child(HSeparator.new()) 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]])) add_child(HSeparator.new()) var hf := HBoxContainer.new(); add_child(hf) hf.add_child(UI.cell_label("无敌帧时长(秒) player_iframe_sec")) _iframe = UI.spin(0.0, 2.0, 0.05, float(_data.get("player_iframe_sec", 0.5))) hf.add_child(_iframe) add_child(UI.btn("💾 保存 balance.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 {} func _save() -> void: var mults: Dictionary = {} for key in MULT_KEYS: var spins: Array = _mult_spins[key] mults[key] = [spins[0].value, spins[1].value, spins[2].value] var out: Dictionary = _data.duplicate(true) # 基底保留未知键(mana_heat/infinite_spells 等) out["difficulty_mults"] = mults out["boss_hp"] = {"4": _miniboss.value, "5": _boss.value} out["aether_thresholds"] = _aether.get_values() out["player_iframe_sec"] = _iframe.value if UI.save_json(PATH, out): UI.set_status(_status, "💾 已保存(重启 F5 生效)") else: UI.set_status(_status, "✗ 保存失败", true)