精英怪四阶段:护盾→破盾→裂隙→濒死的隐式状态机
Boss 有显式的 enum BossPhase 三阶段,精英怪则走了另一条路:不用 enum,用护盾值 + 血量比例 + 计时器三个维度隐式判定阶段。509 行的 elite.gd 里,同一个函数 _handle_attacks() 根据当前状态自动分流到不同攻击模式。
graph TD
A["精英怪出现"] --> B{"护盾 > 0?"}
B -->|"是"| C["Phase A 护盾压制"]
B -->|"否"| D{"phase_b_timer 激活?"}
D -->|"是"| E["Phase B 破盾易伤"]
D -->|"否"| F{"血量 > 35%?"}
F -->|"是"| G["Phase C 裂隙召唤"]
F -->|"否"| H["Phase D 濒死预测弹"]
C -->|"护盾归零"| I["破盾 4s 易伤窗口"]
I --> D
阶段判定:散落在一个函数里的状态机
Boss 的阶段切换有 _set_phase() + _update_phase() 两个专门函数。精英怪更直接——所有阶段逻辑都在 _handle_attacks() 里用 if/else 分流:
1 | func _handle_attacks(delta: float) -> void: |
没有 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 | # elite.gd |
注意:没有 _damage_multiplier 变量——1.25 倍伤害是内联计算的,在 take_damage 内部直接乘。_phase_b_timer 的持续时间由常量 PHASE_B_EXPOSE_DURATION 控制。破盾时发射 shield_broken_window_started 信号,HUD 据此显示提示。
掉落物:弧形喷射 + 联机轮转
精英死亡时,固定 12 个掉落物以半弧形喷射(函数名 _spawn_rewards):
1 | # elite.gd |
弧形角度是 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。