## WaveManager — 波次管理(Autoload: WaveManager) ## S6 内容:Wave 1~20 完整配置(敌人原型/精英寻路/Mini Boss W8/Final Boss W20) ## 设计参考:game_design.md §3.3(W1-5 数值 / W6-10 机制 / W11-20 弹幕)、§4.5 Boss extends Node enum WaveState { IDLE = 0, BATTLE = 1, COMPLETE = 2 } var current_wave: int = 0 var wave_state: int = WaveState.IDLE var enemies_alive: int = 0 var _wave_start_time: float = 0.0 const ARENA_RADIUS: float = 700.0 const MAX_WAVE: int = 20 ## Boss 追踪 var _boss_id: int = -1 var _boss_max_hp: float = 0.0 var _boss_phase: int = 0 var _boss_wave: bool = false ## 波次配置表:纯数据驱动,唯一来源 data/waves.json(游戏设计器「波次」面板维护) ## 字段:count, hp, type, elite, gold, xp, boss(-1=无 / 4=MiniBoss / 5=Boss) var WAVE_CONFIG: Array = [] const WAVES_JSON: String = "res://data/waves.json" func _ready() -> void: EventBus.subscribe(EventID.ENEMY_KILLED, _on_enemy_killed) _load_json_waves() ## 数据驱动:从 data/waves.json 加载波次表 func _load_json_waves() -> void: if not FileAccess.file_exists(WAVES_JSON): push_error("WaveManager: 缺失 res://data/waves.json(无波次配置)") return var data = JSON.parse_string(FileAccess.get_file_as_string(WAVES_JSON)) if data is Array and not data.is_empty(): WAVE_CONFIG = data else: push_error("WaveManager: waves.json 格式错误(应为非空数组)") func _physics_process(_delta: float) -> void: if _boss_wave and _boss_id >= 0: _update_boss_phase() ## 开始指定波次(由 CombatManager 调用) func start_wave(wave_num: int) -> void: current_wave = wave_num wave_state = WaveState.BATTLE _wave_start_time = Time.get_ticks_msec() / 1000.0 _boss_id = -1 _boss_phase = 0 _boss_wave = false var cfg: Dictionary = _get_wave_config(wave_num) enemies_alive = int(cfg["count"]) _spawn_wave_enemies(cfg) if int(cfg.get("boss", -1)) >= 0: _spawn_boss(int(cfg["boss"]), wave_num) EventBus.emit(EventID.GAME_STATE_CHANGED, {"from": "SHOP", "to": "BATTLE", "wave": wave_num}) print("[WaveManager] Wave %d 开始:%d 杂兵 + %d 精英%s" % [ wave_num, int(cfg["count"]), int(cfg.get("elite", 0)), ("(Boss!)" if int(cfg.get("boss", -1)) >= 0 else "")]) func _spawn_wave_enemies(cfg: Dictionary) -> void: # 难度乘子:怪物数量(挑战×1.2)、怪物血量(初学者×0.7) var count: int = int(round(int(cfg["count"]) * SettingsManager.wave_count_mult())) var hp: float = float(cfg["hp"]) * SettingsManager.enemy_hp_mult() var etype: int = int(cfg.get("type", 0)) # enemies_alive 已按 cfg.count 初始化,按难度修正差额 enemies_alive += count - int(cfg["count"]) for _i in count: EnemyManager.spawn_enemy(_ring_pos(), hp, etype) # W15+ 精英:带 NavigationAgent2D 寻路(ADR-A4),更高血量 var elites: int = int(cfg.get("elite", 0)) for _e in elites: EnemyManager.spawn_enemy(_ring_pos(), hp * 2.5, EnemyManager.Type.ELITE, true) enemies_alive += elites # 精英计入存活 func _spawn_boss(boss_type: int, wave_num: int) -> void: # Boss 基础血量由 SettingsManager(balance.json 可调)提供 × 难度乘子 var hp: float = SettingsManager.get_boss_base_hp(boss_type) * SettingsManager.boss_hp_mult() _boss_max_hp = hp # Boss 在玩家上方稍远处入场 _boss_id = EnemyManager.spawn_enemy(Vector2(0.0, -ARENA_RADIUS * 0.8), hp, boss_type) _boss_wave = true _boss_phase = 1 enemies_alive += 1 EventBus.emit(EventID.BOSS_SPAWNED, { "boss_id": _boss_id, "boss_type": boss_type, "wave": wave_num, "max_hp": hp}) print("[WaveManager] Boss 入场!type=%d hp=%.0f (wave %d)" % [boss_type, hp, wave_num]) ## Boss 阶段:HP 跨越阈值时进阶并召唤援军(简化 AI,game_design §4.5) func _update_boss_phase() -> void: if not EnemyManager.has_entity(_boss_id): return var frac: float = EnemyManager.get_hp_percent(_boss_id) var target_phase: int = 1 if frac <= 0.33: target_phase = 3 elif frac <= 0.66: target_phase = 2 if target_phase > _boss_phase: _boss_phase = target_phase _on_boss_phase_enter(target_phase) func _on_boss_phase_enter(phase: int) -> void: # 进阶召唤一批援军施压(Phase 2/3) var add_count: int = 4 + phase * 2 var boss_pos: Vector2 = EnemyManager.get_pos_by_id(_boss_id) for _i in add_count: var off := Vector2(randf_range(-120, 120), randf_range(-120, 120)) var aid: int = EnemyManager.spawn_enemy(boss_pos + off, 40.0, EnemyManager.Type.FAST) if aid >= 0: enemies_alive += 1 EventBus.emit(EventID.GAME_STATE_CHANGED, {"from": "BATTLE", "to": "BOSS_PHASE_%d" % phase}) print("[WaveManager] Boss 进入 Phase %d,召唤 %d 援军" % [phase, add_count]) func _ring_pos() -> Vector2: var angle: float = randf() * TAU var radius: float = randf_range(250.0, ARENA_RADIUS) return Vector2(cos(angle) * radius, sin(angle) * radius) func _on_enemy_killed(payload: Dictionary) -> void: if wave_state != WaveState.BATTLE: return var killed_id: int = int(payload.get("entity_id", -1)) # Boss 波:Boss 死亡即通关本波(援军为附赠) if _boss_wave and killed_id == _boss_id: _complete_wave() return enemies_alive -= 1 if enemies_alive <= 0 and not _boss_wave: _complete_wave() func _complete_wave() -> void: if wave_state != WaveState.BATTLE: return wave_state = WaveState.COMPLETE _boss_wave = false _boss_id = -1 var cfg: Dictionary = _get_wave_config(current_wave) PlayerStats.gain_gold(int(cfg["gold"])) PlayerStats.gain_xp(int(cfg["xp"])) var elapsed: float = Time.get_ticks_msec() / 1000.0 - _wave_start_time EventBus.emit(EventID.WAVE_COMPLETE, {"wave": current_wave, "elapsed_sec": elapsed}) print("[WaveManager] Wave %d 完成!奖励: %dG, %d XP" % [current_wave, int(cfg["gold"]), int(cfg["xp"])]) func _get_wave_config(wave_num: int) -> Dictionary: var idx: int = clamp(wave_num - 1, 0, WAVE_CONFIG.size() - 1) var base: Dictionary = WAVE_CONFIG[idx].duplicate() # 超过 W20(Endless):数值膨胀 if wave_num > MAX_WAVE: var extra: int = wave_num - MAX_WAVE base["count"] = int(base["count"]) + extra * 6 base["hp"] = float(base["hp"]) * pow(1.12, extra) base["gold"] = int(base["gold"]) + extra * 8 base["xp"] = int(base["xp"]) + extra * 12 base["boss"] = -1 # Endless 不重复终 Boss return base func get_wave_count(wave_num: int) -> int: return int(_get_wave_config(wave_num)["count"]) func is_boss_wave(wave_num: int) -> bool: return int(_get_wave_config(wave_num).get("boss", -1)) >= 0 func reset() -> void: current_wave = 0 wave_state = WaveState.IDLE enemies_alive = 0 _boss_id = -1 _boss_phase = 0 _boss_wave = false