153 lines
5.4 KiB
GDScript
153 lines
5.4 KiB
GDScript
## 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()
|