文档目标: 详解 Vampirefall 中 AI 行为的核心驱动逻辑。 适用对象: 怪物 AI、防御塔逻辑、Boss 阶段转换。
有限状态机由三个要素组成:
Idle, Chase, Attack)Distance < 5m -> 切换到 Attack)OnEnter: 播放吼叫动画)混合品类游戏的怪物 AI 比传统塔防更复杂,因为它需要处理“仇恨切换”。
graph TD
Spawn(出生) -->|落地动画结束| Move(寻路移动)
Move -->|遇到路障| Breach(拆墙/塔)
Move -->|被玩家攻击/嘲讽| Aggro(仇恨追击)
Aggro -->|距离过远/超时| Move
Aggro -->|进入攻击范围| Attack(攻击)
Breach -->|塔被摧毁| Move
Attack -->|目标死亡| Move
Attack -->|攻击后摇结束| Cooldown(对峙/调整)
Cooldown -->|CD结束| Attack
State_Move (寻路移动)Obstacle (塔/墙)。如果有,切换到 State_Breach。State_Breach (拆墙)Move。Aggro。State_Aggro (仇恨/追击)MaxAggroDistance (比如 15米) 或 MaxAggroTime (比如 5秒)。超过限制强制切回 Move,并且 3秒内免疫仇恨。结合游戏特色,我们定义两种极端的怪物 AI 模板。
Aggro 状态。HP < 30% 时,切换到 Sprint (冲刺) 状态,移速翻倍。Flee (逃离) 状态:
DistanceToPlayer < 3m (被近身) 时,触发逃离。(SelfPos - PlayerPos) 方向移动 2秒。Stealth (隐身) 状态:
Boss 不仅仅是血厚的怪,它是一个带有剧本的流程。我们需要引入 “Phase System” (阶段系统)。
Boss 的 FSM 是分层的:上层管理阶段,下层管理具体行为。
graph TD
Intro(登场演出) --> Phase1
Phase1 -->|HP < 60%| Transition1(转阶段/无敌)
Transition1 --> Phase2
Phase2 -->|HP < 20%| Transition2(狂暴前摇)
Transition2 --> Phase3(软狂暴/Rage)
Phase3 --> Dead
Slash: 前方扇形斩击。Thrust: 针对仇恨目标的快速突刺。SummonBats: 召唤 2 只小蝙蝠骚扰。HP < 60% -> 触发 State_Vanish (消失)。BloodRain: 地面随机生成红圈,2秒后爆炸。LaserSweep: 旋转激光扫射全场,玩家必须跟着转或用无敌帧穿过。HP < 20% -> 触发 State_Enrage (吼叫动画)。塔的逻辑相对简单,重点在于“索敌效率”和“状态重置”。
graph TD
Idle(待机扫描) -->|发现敌人| Warmup(预热/瞄准)
Warmup -->|预热完成| Fire(开火)
Fire -->|开火结束| Cooldown(冷却/装填)
Cooldown -->|CD结束 & 有敌人| Fire
Cooldown -->|CD结束 & 无敌人| Idle
Fire -->|目标死亡/丢失| Idle
State_Idle (待机扫描)Update 里都去 Physics.OverlapSphere。State_Warmup (预热/瞄准)TurretHead.LookAt(Target)。Idle (浪费了蓄力)。State_Fire (开火)State_Cooldown。不要停留在 Fire 状态。不要用复杂的类继承 (Class-based States) 来做成千上万的小怪,那样内存开销太大。 推荐使用 Enum + Switch (简单怪) 或 Struct-based FSM (复杂怪)。
public enum EState { Idle, Move, Attack, Dead }
public class SimpleMonster : MonoBehaviour {
private EState _currentState;
private float _stateTimer;
void Update() {
// 状态机驱动
switch (_currentState) {
case EState.Move: UpdateMove(); break;
case EState.Attack: UpdateAttack(); break;
}
}
void ChangeState(EState newState) {
OnExit(_currentState);
_currentState = newState;
_stateTimer = 0;
OnEnter(newState);
}
// ... 具体逻辑写在函数里
}
如果你嫌 Switch 太乱,可以用轻量级的类封装,但要避免每帧 new。
// 1. 定义状态基类
public abstract class BaseState {
protected BossController _boss;
public BaseState(BossController boss) { _boss = boss; }
public abstract void OnEnter();
public abstract void OnUpdate();
public abstract void OnExit();
}
// 2. 状态机控制器
public class StateMachine {
private BaseState _current;
public void ChangeState(BaseState newState) {
_current?.OnExit();
_current = newState;
_current?.OnEnter();
}
public void Update() {
_current?.OnUpdate();
}
}
// 3. 具体状态实现
public class BossChaseState : BaseState {
public override void OnUpdate() {
if (_boss.DistanceToPlayer < 2.0f) {
_boss.FSM.ChangeState(_boss.StateAttack); // 预先创建好的实例,不要 new!
}
}
}
Move 和 Attack 之间疯狂切换,每帧切一次,导致动画鬼畜。Distance < 5.0mDistance > 6.0mStunned (眩晕) 做成一个普通状态。Stunned 是一个 Bool 标记。在 Update() 的最开头检查:
void Update() {
if (isStunned) return; // 如果被晕,状态机完全停转
FSM.Update();
}
OnStateExit 里写代码改逻辑变量。这会导致逻辑很难调试。ChangeState(Attack) -> 调用 animator.SetTrigger("Attack")。