diff --git a/project.godot b/project.godot index d83824c..ce6fe1e 100644 --- a/project.godot +++ b/project.godot @@ -56,6 +56,7 @@ AudioManager="*res://scripts/autoloads/audio_manager.gd" SceneManager="*res://scripts/autoloads/scene_manager.gd" MetaProgress="*res://scripts/autoloads/meta_progress.gd" EnemyBulletManager="*res://scripts/autoloads/enemy_bullet_manager.gd" +BossManager="*res://scripts/autoloads/boss_manager.gd" MCPScreenshot="*res://addons/godot_mcp/mcp_screenshot_service.gd" MCPInputService="*res://addons/godot_mcp/mcp_input_service.gd" MCPGameInspector="*res://addons/godot_mcp/mcp_game_inspector_service.gd" diff --git a/scripts/autoloads/boss_manager.gd b/scripts/autoloads/boss_manager.gd new file mode 100644 index 0000000..f06902c --- /dev/null +++ b/scripts/autoloads/boss_manager.gd @@ -0,0 +1,152 @@ +## BossManager — Boss 阶段状态机 + 攻击模式调度(Autoload) +## Boss 实体仍在 EnemyManager SoA(渲染/受击/HP);本管理器侧挂运行态、接管移动、驱动招式。 +## 数据驱动:data/bosses.json(每 boss_type 的 move_speed / phases / attack_patterns),自载。 +extends Node + +const BOSSES_JSON: String = "res://data/bosses.json" + +# boss_type(int) → { move_speed:float, phases:Array[Dictionary] } +var _defs: Dictionary = {} +# boss_id(int) → 运行态 Dictionary +var _active: Dictionary = {} + +func _ready() -> void: + _load_defs() + EventBus.subscribe(EventID.ENEMY_KILLED, _on_enemy_killed) + +func _load_defs() -> void: + if not FileAccess.file_exists(BOSSES_JSON): + push_error("BossManager: 缺失 res://data/bosses.json") + return + var data = JSON.parse_string(FileAccess.get_file_as_string(BOSSES_JSON)) + if not (data is Dictionary): + push_error("BossManager: bosses.json 格式错误") + return + _defs.clear() + for k in data: + _defs[int(k)] = data[k] + +## wave_manager._spawn_boss 生成 SoA Boss 后调用 +func register_boss(boss_id: int, boss_type: int, max_hp: float, wave: int) -> void: + var def: Dictionary = _defs.get(boss_type, {}) + var phases: Array = def.get("phases", []) + _active[boss_id] = { + "boss_type": boss_type, + "max_hp": max_hp, + "wave": wave, + "move_speed": float(def.get("move_speed", 100.0)), + "phase": 0, + "phases": phases, + "pattern_index": 0, + "cooldown": 1.0, # 入场后短暂延迟再起手 + "action": {}, # 进行中招式(空=无) + "spiral_phase": 0.0, # spiral 招式的累积旋转角 + } + EnemyManager.register_external_mover(boss_id) + if not phases.is_empty(): + _emit_phase(boss_id, 0) + +func _physics_process(delta: float) -> void: + if _active.is_empty(): + return + for boss_id in _active.keys(): + if not EnemyManager.has_entity(boss_id): + continue + var st: Dictionary = _active[boss_id] + _check_phase(boss_id, st) + _tick_movement(boss_id, st, delta) + _tick_action(boss_id, st, delta) + +# 阶段切换:HP% 跨入更深阶段(phases 按 hp_threshold 降序,取满足阈值的最深项) +func _check_phase(boss_id: int, st: Dictionary) -> void: + var hp_pct: float = EnemyManager.get_hp_percent(boss_id) + var phases: Array = st["phases"] + var target: int = int(st["phase"]) + for i in phases.size(): + if hp_pct <= float(phases[i].get("hp_threshold", 1.0)): + target = i + if target > int(st["phase"]): + st["phase"] = target + st["pattern_index"] = 0 + st["cooldown"] = 0.5 + st["action"] = {} + _summon_adds(boss_id, int(phases[target].get("summon_adds", 0))) + _emit_phase(boss_id, target) + +func _emit_phase(boss_id: int, phase: int) -> void: + EventBus.emit(EventID.BOSS_PHASE_CHANGED, { + "boss_id": boss_id, "boss_type": int(_active[boss_id]["boss_type"]), "phase": phase}) + +func _summon_adds(boss_id: int, n: int) -> void: + if n <= 0: + return + var boss_pos: Vector2 = EnemyManager.get_pos_by_id(boss_id) + for i in n: + var ang: float = TAU * float(i) / float(n) + var off: Vector2 = Vector2(cos(ang), sin(ang)) * 80.0 + EnemyManager.spawn_enemy(boss_pos + off, 40.0, EnemyManager.Type.FAST) + +func _cur_phase(st: Dictionary) -> Dictionary: + var phases: Array = st["phases"] + if phases.is_empty(): + return {} + return phases[clampi(int(st["phase"]), 0, phases.size() - 1)] + +# 移动:无「接管移动」的招式在进行时,朝玩家缓慢漂移 +func _tick_movement(boss_id: int, st: Dictionary, delta: float) -> void: + var action: Dictionary = st["action"] + if not action.is_empty() and bool(action.get("controls_move", false)): + return + var pos: Vector2 = EnemyManager.get_pos_by_id(boss_id) + var player: Vector2 = PlayerManager.get_position() + var spd: float = float(st["move_speed"]) * float(_cur_phase(st).get("move_speed_mult", 1.0)) + var to: Vector2 = player - pos + if to.length() > 1.0: + pos += to.normalized() * spd * delta + EnemyManager.set_entity_pos(boss_id, pos) + +# 招式推进:有进行中招式 → 推进;否则冷却到点 → 起手下一招式 +func _tick_action(boss_id: int, st: Dictionary, delta: float) -> void: + var action: Dictionary = st["action"] + if not action.is_empty(): + _advance_action(boss_id, st, action, delta) + return + st["cooldown"] = float(st["cooldown"]) - delta + if float(st["cooldown"]) > 0.0: + return + var patterns: Array = _cur_phase(st).get("attack_patterns", []) + if patterns.is_empty(): + st["cooldown"] = 1.0 + return + var idx: int = int(st["pattern_index"]) % patterns.size() + st["pattern_index"] = int(st["pattern_index"]) + 1 + _start_pattern(boss_id, st, patterns[idx]) + +# 招式起手(Task 7/8 补齐分支);未知 kind 兜底置冷却 +func _start_pattern(boss_id: int, st: Dictionary, p: Dictionary) -> void: + st["cooldown"] = float(p.get("cooldown", 2.0)) + +# 招式推进(Task 8 补齐三段式);骨架版直接结束 +func _advance_action(boss_id: int, st: Dictionary, action: Dictionary, delta: float) -> void: + st["action"] = {} + +func _on_enemy_killed(payload: Dictionary) -> void: + var id: int = int(payload.get("entity_id", -1)) + if not _active.has(id): + return + var st: Dictionary = _active[id] + EnemyManager.unregister_external_mover(id) + EventBus.emit(EventID.BOSS_KILLED, { + "boss_id": id, "boss_type": int(st["boss_type"]), "wave": int(st["wave"])}) + _active.erase(id) + +func get_active_boss_count() -> int: + return _active.size() + +func get_phase(boss_id: int) -> int: + return int(_active.get(boss_id, {}).get("phase", -1)) + +func reset() -> void: + for id in _active.keys(): + EnemyManager.unregister_external_mover(id) + _active.clear() diff --git a/scripts/autoloads/boss_manager.gd.uid b/scripts/autoloads/boss_manager.gd.uid new file mode 100644 index 0000000..b09e650 --- /dev/null +++ b/scripts/autoloads/boss_manager.gd.uid @@ -0,0 +1 @@ +uid://c41eeogteiwa1