Boss 有显式的 enum BossPhase 三阶段,精英怪则走了另一条路:不用 enum,用护盾值 + 血量比例 + 计时器三个维度隐式判定阶段。509 行的 elite.gd 里,同一个函数 _handle_attacks() 根据当前状态自动分流到不同攻击模式。

阶段判定:散落在一个函数里的状态机

Boss 的阶段切换有 _set_phase() + _update_phase() 两个专门函数。精英怪更直接——所有阶段逻辑都在 _handle_attacks() 里用 if/else 分流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func _handle_attacks(delta: float) -> void:
if _shoot_timer <= 0.0 and _target:
# Phase A:有护盾 → 窄扇射击,压制玩家
if _shield > 0:
_shoot_timer = 2.2
_attack_narrow_fan()
return

var hp_ratio := float(_health) / max(float(_max_health), 1.0)

# Phase B:刚破盾 → 4秒易伤窗口,定向连射
if _phase_b_timer > 0.0:
_shoot_timer = 1.8
_attack_targeted_burst()
return

# Phase C:无盾 + 血量 > 35% → 环形弹幕
if hp_ratio > 0.35:
_shoot_timer = 3.0
_attack_ring()
# Phase D:无盾 + 血量 ≤ 35% → 濒死三连预测弹
else:
_shoot_timer = 1.4
_attack_triple_predictive()

没有 enum,没有状态转换表,但逻辑清晰——每个 if 分支就是一个阶段,条件就是阶段的触发和退出。

四个阶段的设计意图

阶段 条件 射击间隔 弹幕类型 玩家策略
A 护盾压制 _shield > 0 2.2s 窄扇面 3 发 绕背输出,打盾
B 破盾易伤 _phase_b_timer > 0 1.8s 定向连射 抓窗口全力输出
C 裂隙召唤 无盾 + HP > 35% 3.0s 环形弹幕 走位躲避,持续输出
D 濒死预测 无盾 + HP ≤ 35% 1.4s 三连预测弹 极限操作,速杀

护盾破盾:4 秒易伤窗口

破盾是精英战的核心转折点。护盾归零时启动一个 4 秒计时器,期间精英伤害承受 +25%:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# elite.gd
func take_damage(amount: int = 1, killer_peer_id: int = -1) -> void:
if _dead:
return
if _shield > 0:
_shield -= amount
_spawn_shield_hit_effect()
_update_shield_bar()
if _shield <= 0:
_shield_break_effect()
_phase_b_timer = PHASE_B_EXPOSE_DURATION
shield_broken_window_started.emit()
return
var applied_damage: int = amount
if _phase_b_timer > 0.0:
applied_damage = int(ceil(float(amount) * 1.25))
_health -= applied_damage

注意:没有 _damage_multiplier 变量——1.25 倍伤害是内联计算的,在 take_damage 内部直接乘。_phase_b_timer 的持续时间由常量 PHASE_B_EXPOSE_DURATION 控制。破盾时发射 shield_broken_window_started 信号,HUD 据此显示提示。

掉落物:弧形喷射 + 联机轮转

精英死亡时,固定 12 个掉落物以半弧形喷射(函数名 _spawn_rewards):

1
2
3
4
5
6
7
8
9
10
11
12
# elite.gd
func _spawn_rewards() -> void:
var types: Array[String] = ["heal", "bomb", "spread", "speed", "heal", "power", "heal", "power", "speed", "spread", "heal", "bomb"]
var alive_ids: Array[int] = []
if NetworkManager.is_online() and multiplayer.is_server():
alive_ids = GameState.get_alive_player_ids()
var assign_idx: int = 0
for i in range(types.size()):
var step: float = PI / max(float(types.size() - 1), 1.0)
var angle: float = -PI * 0.85 + step * float(i)
var offset := Vector2(cos(angle), sin(angle)) * randf_range(26.0, 58.0)
# ...

弧形角度是 PI * 0.85(约 153°),偏移范围 26~58 像素。12 个掉落物类型是预定义的(heal/bomb/spread/speed/power),不是随机生成。

联机模式下,掉落物归属按轮转分配:通过 GameState.get_alive_player_ids() 获取存活玩家,依次循环分配 assigned_peer_id。

和 Boss 架构的对比

Boss (914 行) 精英 (509 行)
状态定义 显式 enum State + BossPhase 隐式 if/else 分支
阶段驱动 血量比例 + _set_phase() 护盾值 + 血量 + 计时器
行为状态 5 个(IDLE/CIRCLE/ATTACK/RETREAT/ENRAGED) 无独立行为状态
阶段数 3 个 4 个
独有机制 Boss BGM 切换、补给碎片 护盾系统、易伤窗口、掉落物归属

Boss 用两层分离解耦了行为和节奏,精英怪用一层隐式判定更紧凑。两种方式各有适用场景——行为复杂用显式 enum,行为单一用 if/else。

状态机不是目的,清晰的逻辑才是。能用 if/else 说清楚的事,不需要 enum。