From 3480255a5a67312a5ed0c40190c2ecf77a2ec4f6 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 23 Jul 2026 10:27:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(boss):=20=E6=B8=B8=E6=88=8F=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E5=99=A8=20Boss=20=E5=88=86=E9=A1=B5=EF=BC=88?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B/=E9=98=B6=E6=AE=B5/=E6=8B=9B=E5=BC=8F?= =?UTF-8?q?=E4=B8=89=E7=BA=A7=E5=AD=97=E6=AE=B5=E5=8C=96=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=EF=BC=8C=E5=8F=8C=E8=AF=AD=EF=BC=8C=E7=A6=81=E6=89=8B=E5=86=99?= =?UTF-8?q?JSON=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/game_designer/boss_tab.gd | 202 +++++++++++++++++++++++++ addons/game_designer/boss_tab.gd.uid | 1 + addons/game_designer/designer_panel.gd | 1 + 3 files changed, 204 insertions(+) create mode 100644 addons/game_designer/boss_tab.gd create mode 100644 addons/game_designer/boss_tab.gd.uid diff --git a/addons/game_designer/boss_tab.gd b/addons/game_designer/boss_tab.gd new file mode 100644 index 0000000..32ff493 --- /dev/null +++ b/addons/game_designer/boss_tab.gd @@ -0,0 +1,202 @@ +## Boss 编辑器标签 — 类型/阶段/招式(三级 select-form,字段化) +@tool +extends VBoxContainer + +const UI = preload("res://addons/game_designer/designer_ui.gd") +const PATH = "res://data/bosses.json" +const BOSS_LABELS := ["精英 Mini(4)", "首领 Final(5)"] +const BOSS_KEYS := ["4", "5"] +const KIND_LABELS := ["环 ring", "扇形 spread", "瞄准 aimed", "旋转 spiral", "冲刺 dash", "蓄力 charge_up", "近战 melee"] +const KIND_KEYS := ["ring", "spread", "aimed", "spiral", "dash", "charge_up", "melee"] + +var _data: Dictionary = {} +var _boss_key: String = "4" +var _phase_idx: int = -1 +var _pat_idx: int = -1 + +var _boss_opt: OptionButton +var _move_speed: SpinBox +var _phase_list: ItemList +var _hp_thr: SpinBox +var _spd_mult: SpinBox +var _summon: SpinBox +var _pat_list: ItemList +var _kind: OptionButton +var _release_kind: OptionButton +var _f: Dictionary = {} # 参数名 → SpinBox +var _tele: LineEdit +var _status: Label + +func _ready() -> void: + add_child(UI.header("👑 Boss 编辑器 — 阶段与攻击模式")) + _load() + # Boss 类型选择 + move_speed + _boss_opt = _row("Boss 类型", UI.opt(BOSS_LABELS, 0)) + _boss_opt.item_selected.connect(_on_boss_selected) + _move_speed = _row("移动速度(Move Speed)", UI.spin(0, 1000, 5, 100)) + add_child(HSeparator.new()) + # 阶段列表 + add_child(UI.header("阶段 Phases")) + _phase_list = ItemList.new(); _phase_list.custom_minimum_size = Vector2(0, 70) + _phase_list.item_selected.connect(_on_phase_selected); add_child(_phase_list) + var pr := HBoxContainer.new(); add_child(pr) + pr.add_child(UI.btn("➕加阶段", _on_add_phase)) + pr.add_child(UI.btn("🗑删阶段", _on_del_phase)) + _hp_thr = _row("血量阈值(HP Threshold 0~1)", UI.spin(0, 1, 0.01, 1.0)) + _spd_mult = _row("速度倍率(Move Speed Mult)", UI.spin(0.1, 5, 0.1, 1.0)) + _summon = _row("进阶召唤数(Summon Adds)", UI.spin(0, 40, 1, 0)) + add_child(HSeparator.new()) + # 招式列表 + add_child(UI.header("招式 Attack Patterns")) + _pat_list = ItemList.new(); _pat_list.custom_minimum_size = Vector2(0, 70) + _pat_list.item_selected.connect(_on_pat_selected); add_child(_pat_list) + var ar := HBoxContainer.new(); add_child(ar) + ar.add_child(UI.btn("➕加招式", _on_add_pat)) + ar.add_child(UI.btn("🗑删招式", _on_del_pat)) + _kind = _row("类型(Kind)", UI.opt(KIND_LABELS, 0)) + _release_kind = _row("蓄力释放类型(Release Kind)", UI.opt(KIND_LABELS, 0)) + _f["cooldown"] = _row("冷却(Cooldown s)", UI.spin(0, 20, 0.1, 2.0)) + _f["count"] = _row("弹数(Count)", UI.spin(0, 200, 1, 12)) + _f["speed"] = _row("弹速(Speed)", UI.spin(0, 2000, 10, 200)) + _f["half_angle"] = _row("扇形半角(Half Angle°)", UI.spin(0, 180, 1, 30)) + _f["angle_step"] = _row("旋转步进(Angle Step°)", UI.spin(0, 180, 1, 15)) + _f["bullet_damage"] = _row("弹伤(Bullet Damage)", UI.spin(0, 500, 1, 8)) + _f["bullet_radius"] = _row("弹半径(Bullet Radius)", UI.spin(1, 64, 1, 8)) + _f["lifetime"] = _row("弹存活(Lifetime s)", UI.spin(0.5, 20, 0.5, 4)) + _f["windup"] = _row("前摇(Windup s)", UI.spin(0, 5, 0.1, 0.8)) + _f["recover"] = _row("后摇(Recover s)", UI.spin(0, 5, 0.1, 0.8)) + _f["dash_speed"] = _row("冲刺速度(Dash Speed)", UI.spin(0, 3000, 10, 600)) + _f["dash_time"] = _row("冲刺时长(Dash Time s)", UI.spin(0, 3, 0.05, 0.5)) + _f["contact_radius"] = _row("冲刺命中半径(Contact Radius)", UI.spin(0, 300, 2, 40)) + _f["contact_damage"] = _row("冲刺伤害(Contact Damage)", UI.spin(0, 500, 1, 15)) + _f["contact_hit_cd"] = _row("冲刺命中冷却(Contact Hit CD s)", UI.spin(0, 3, 0.05, 0.4)) + _f["melee_radius"] = _row("近战半径(Melee Radius)", UI.spin(0, 400, 5, 90)) + _f["melee_damage"] = _row("近战伤害(Melee Damage)", UI.spin(0, 500, 1, 20)) + _tele = _row("预警 VFX(Telegraph VFX)", UI.line("hit_spark")) + add_child(HSeparator.new()) + var sr := HBoxContainer.new(); add_child(sr) + sr.add_child(UI.btn("✓应用招式", _on_apply_pat)) + sr.add_child(UI.btn("✓应用阶段", _on_apply_phase)) + sr.add_child(UI.btn("💾保存 bosses.json", _save)) + _status = UI.status_label(); add_child(_status) + _refresh_boss() + +func _row(label: String, ctrl: Control) -> Control: + var h := HBoxContainer.new(); h.add_child(UI.cell_label(label)); h.add_child(ctrl); add_child(h) + return ctrl + +func _load() -> void: + var d = UI.load_json(PATH) + _data = d if d is Dictionary else {} + +func _boss() -> Dictionary: + if not _data.has(_boss_key): + _data[_boss_key] = {"move_speed": 100.0, "phases": []} + return _data[_boss_key] + +func _phases() -> Array: + return _boss().get("phases", []) + +func _on_boss_selected(idx: int) -> void: + _boss_key = BOSS_KEYS[idx]; _phase_idx = -1; _pat_idx = -1 + _refresh_boss() + +func _refresh_boss() -> void: + _move_speed.value = float(_boss().get("move_speed", 100.0)) + _refresh_phases() + +func _refresh_phases() -> void: + _phase_list.clear() + var ph := _phases() + for i in ph.size(): + _phase_list.add_item("阶段%d 阈值%.2f 招式×%d" % [i, float(ph[i].get("hp_threshold", 1.0)), (ph[i].get("attack_patterns", []) as Array).size()]) + _refresh_pats() + +func _on_phase_selected(idx: int) -> void: + _phase_idx = idx; _pat_idx = -1 + var ph: Dictionary = _phases()[idx] + _hp_thr.value = float(ph.get("hp_threshold", 1.0)) + _spd_mult.value = float(ph.get("move_speed_mult", 1.0)) + _summon.value = float(ph.get("summon_adds", 0)) + _refresh_pats() + +func _refresh_pats() -> void: + _pat_list.clear() + if _phase_idx < 0 or _phase_idx >= _phases().size(): + return + var pats: Array = _phases()[_phase_idx].get("attack_patterns", []) + for i in pats.size(): + _pat_list.add_item("%d. %s" % [i, str(pats[i].get("kind", "ring"))]) + +func _on_pat_selected(idx: int) -> void: + _pat_idx = idx + var p: Dictionary = _phases()[_phase_idx]["attack_patterns"][idx] + _kind.selected = maxi(0, KIND_KEYS.find(String(p.get("kind", "ring")))) + _release_kind.selected = maxi(0, KIND_KEYS.find(String(p.get("release_kind", "ring")))) + for key in _f: + (_f[key] as SpinBox).value = float(p.get(key, (_f[key] as SpinBox).value)) + _tele.text = String(p.get("telegraph_vfx", "hit_spark")) + +func _on_add_phase() -> void: + _phases().append({"hp_threshold": 1.0, "move_speed_mult": 1.0, "summon_adds": 0, "attack_patterns": []}) + _refresh_phases(); UI.set_status(_status, "已加阶段(记得保存)") + +func _on_del_phase() -> void: + if _phase_idx >= 0 and _phase_idx < _phases().size(): + _phases().remove_at(_phase_idx); _phase_idx = -1; _refresh_phases() + +func _on_apply_phase() -> void: + if _phase_idx < 0: + UI.set_status(_status, "✗ 先选阶段", true); return + var ph: Dictionary = _phases()[_phase_idx] + ph["hp_threshold"] = _hp_thr.value + ph["move_speed_mult"] = _spd_mult.value + ph["summon_adds"] = int(_summon.value) + _boss()["move_speed"] = _move_speed.value + _refresh_phases(); UI.set_status(_status, "✓ 阶段已应用(记得保存)") + +func _on_add_pat() -> void: + if _phase_idx < 0: + UI.set_status(_status, "✗ 先选阶段", true); return + (_phases()[_phase_idx]["attack_patterns"] as Array).append({"kind": "ring", "count": 12, "speed": 180, "bullet_damage": 7, "bullet_radius": 8, "lifetime": 4, "cooldown": 2.0}) + _refresh_pats() + +func _on_del_pat() -> void: + if _phase_idx >= 0 and _pat_idx >= 0: + (_phases()[_phase_idx]["attack_patterns"] as Array).remove_at(_pat_idx); _pat_idx = -1; _refresh_pats() + +func _on_apply_pat() -> void: + if _phase_idx < 0 or _pat_idx < 0: + UI.set_status(_status, "✗ 先选招式", true); return + var kind: String = KIND_KEYS[_kind.selected] + var p: Dictionary = {"kind": kind, "cooldown": _f["cooldown"].value} + # 按 kind 只写相关字段,保持 JSON 简洁 + match kind: + "ring": + for k in ["count", "speed", "bullet_damage", "bullet_radius", "lifetime"]: p[k] = _f[k].value + "spread": + for k in ["count", "half_angle", "speed", "bullet_damage", "bullet_radius", "lifetime"]: p[k] = _f[k].value + "aimed": + for k in ["count", "speed", "bullet_damage", "bullet_radius", "lifetime"]: p[k] = _f[k].value + "spiral": + for k in ["count", "angle_step", "speed", "bullet_damage", "bullet_radius", "lifetime"]: p[k] = _f[k].value + "dash": + for k in ["windup", "dash_speed", "dash_time", "contact_radius", "contact_damage", "contact_hit_cd", "recover"]: p[k] = _f[k].value + p["telegraph_vfx"] = _tele.text + "charge_up": + p["release_kind"] = KIND_KEYS[_release_kind.selected] + for k in ["windup", "count", "speed", "half_angle", "angle_step", "bullet_damage", "bullet_radius", "lifetime", "recover"]: p[k] = _f[k].value + p["telegraph_vfx"] = _tele.text + "melee": + for k in ["windup", "melee_radius", "melee_damage", "recover"]: p[k] = _f[k].value + p["telegraph_vfx"] = _tele.text + # 整数化 count + if p.has("count"): p["count"] = int(p["count"]) + _phases()[_phase_idx]["attack_patterns"][_pat_idx] = p + _refresh_pats(); UI.set_status(_status, "✓ 招式已应用(记得保存)") + +func _save() -> void: + if UI.save_json(PATH, _data): + UI.set_status(_status, "💾 已保存 bosses.json(重启 F5 生效)") + else: + UI.set_status(_status, "✗ 保存失败", true) diff --git a/addons/game_designer/boss_tab.gd.uid b/addons/game_designer/boss_tab.gd.uid new file mode 100644 index 0000000..2bef6de --- /dev/null +++ b/addons/game_designer/boss_tab.gd.uid @@ -0,0 +1 @@ +uid://bswo7oiqntlb8 diff --git a/addons/game_designer/designer_panel.gd b/addons/game_designer/designer_panel.gd index 0124e3f..04b3854 100644 --- a/addons/game_designer/designer_panel.gd +++ b/addons/game_designer/designer_panel.gd @@ -15,6 +15,7 @@ func _init() -> void: _add_tab(tabs, "🪄 Core", preload("res://addons/game_designer/core_tab.gd").new()) _add_tab(tabs, "🔮 共鸣", preload("res://addons/game_designer/resonance_tab.gd").new()) _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()) func _add_tab(tabs: TabContainer, title: String, content: Control) -> void: