142 lines
5.5 KiB
GDScript
142 lines
5.5 KiB
GDScript
## 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_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 格式错误(应为非空数组)")
|
||
|
||
## 开始指定波次(由 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_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
|
||
enemies_alive += 1
|
||
EventBus.emit(EventID.BOSS_SPAWNED, {
|
||
"boss_id": _boss_id, "boss_type": boss_type, "wave": wave_num, "max_hp": hp})
|
||
BossManager.register_boss(_boss_id, boss_type, hp, wave_num)
|
||
print("[WaveManager] Boss 入场!type=%d hp=%.0f (wave %d)" % [boss_type, hp, wave_num])
|
||
|
||
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_wave = false
|