初次提交
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
## 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
|
||||
Reference in New Issue
Block a user