一、 FSM 的核心概念详解1. 原理与实现FSM的核心由状态State 、转移Transition 和条件Condition 三要素构成。AI始终处于某一个确定状态中持续执行该状态的逻辑直到满足某个转移条件才切换到新状态。典型的实现是一个while(true)循环包裹switch(state)语句块每个case对应一个状态的执行逻辑和转移判断。2. 概念详解2.1 状态State状态表示 AI 当前的行为模式而不是具体动作。比如: 在战斗AI中一个典型的状态集合是Idle(待机 / 空闲) → Patrol(巡逻) → Alert(警戒) → Chase(追击) → Attack(攻击) → Retreat(撤退)。状态数量少于7个时FSM是最经济的选择。注意:一个设计良好的状态应该是高内聚的状态内部逻辑围绕一个明确的行为模式组织。如果 Attack 状态内部包含了前摇、判定、后摇、连招分支等复杂逻辑就应该考虑将其拆分为子状态机HFSM2.2 转移Transition转移定义了状态之间的切换关系。一个完整的转移包含源状态从哪个状态出发目标状态切换到哪个状态触发事件什么事件触发转移如 SeePlayer守卫条件转移必须满足的条件如 distance 10 hasAmmo转移动作转移瞬间执行的动作如播放音效、设置变量优先级多个转移同时满足时的选择顺序转移的触发方式有两种轮询式每帧检查所有转移条件。简单但可能浪费性能。事件驱动式只在特定事件发生时检查相关转移。更高效但需要事件系统支持。2.3 动作Action动作是状态机在特定时刻执行的逻辑。通常分为三类进入动作Entry Action进入状态时执行一次如播放动画、设置速度、重置计时器更新动作Update Action状态持续期间每帧执行如移动、检测玩家、更新计时器退出动作Exit Action离开状态时执行一次如停止移动、清理资源、恢复默认值将动作按进入/更新/退出分离是状态模式实现的关键能有效避免资源泄漏和状态残留。3. 适用场景FSM非常适合状态少且稳定、转移关系明确、行为模式化的场景。例如2D游戏中的简单敌人AI、Boss战的阶段切换、塔防游戏中怪物的行进逻辑等。在《星际争霸》这类RTS中FSM也被用于控制单位的底层行为。技术选型要点状态 ≤ 7个 → FSM状态转移条件清晰且不随功能迭代频繁增加 → FSM需要精确调试每个状态行为 → FSM因为状态显式调试最直观。二、分层有限状态机 (HFSM)HFSM是对FSM的直接改进通过将状态分组并层级化来解决状态转移的组合爆炸问题。1. 原理与实现HFSM的核心思想是把一组相关的低层状态封装到一个高层状态中低层状态之间的转移在内部处理不需要暴露给外部状态。高层状态之间只维护粗粒度的转移关系从而大幅减少需要显式管理的转移数量。示例战斗AI可以这样分层战斗Combat[高层状态] ├── 近战攻击MeleeAttack[低层状态] ├── 远程攻击RangedAttack[低层状态] └── 防御Defend[低层状态] 非战斗NonCombat[高层状态] ├── 巡逻Patrol └── 待机Idle从NonCombat进入Combat只需要一条高层转移而不是从Patrol、Idle分别连接到MeleeAttack、RangedAttack、Defend的九条转移。HFSM还支持转移的重用——高层状态的转移条件可以被所有低层状态共享减少了代码冗余2. 为什么需要 HFSM从状态爆炸说起2.1 传统 FSM 的状态爆炸问题假设一个战斗 AI 有以下状态Idle, Patrol, Alert, Chase, MeleeAttack, RangedAttack, Block, Dodge, Flee, Dead如果采用扁平 FSM需要考虑的转移数量是每个状态平均需要检查 3~5 个转移条件10 个状态 → 约 30~50 条转移每新增一个状态需要接入的转移数随已有状态数线性增长更严重的是语义重复。例如Idle、Patrol、Alert都可能因为“发现玩家”转移到ChaseMeleeAttack、RangedAttack、Block、Dodge都可能因为“生命值过低”转移到Flee所有状态都可能因为“生命值归零”转移到Dead这些转移条件在每个状态中重复编写修改时需要同步修改多处维护成本随状态数增长而急剧上升。2.2 HFSM 的核心洞察HFSM 的核心洞察是很多状态共享相同的转移逻辑很多转移只在特定状态组内才有意义。将状态分组后组内转移只在组内状态之间发生外部不需要知道组间转移由父状态统一管理子状态不需要重复编写通用转移定义在高层所有子状态自动继承示例将上述 10 个状态分组Root ├── NonCombat非战斗 │ ├── Idle │ ├── Patrol │ └── Alert └── Combat战斗 ├── Chase ├── MeleeAttack ├── RangedAttack ├── Block └── Dodge └── Flee逃跑 └── Dead死亡分组后NonCombat内部的Idle → Patrol → Alert转移只在组内处理NonCombat → Combat只需要一条高层转移发现玩家Combat → Flee只需要一条高层转移生命值过低Any → Dead只需要一条全局转移生命值归零转移数量从 30~50 条降至 15~20 条且新增状态时只需接入组内转移。3. HFSM 的设计原则原则一按语义分组而非按实现分组好的分组原则Combat战斗 ├── Chase追击 ├── Attack攻击 └── Defend防御不好的分组StatesWithTimer带计时器的状态 ├── Idle ├── Attack └── Flee原则二父状态应该有明确的语义父状态名应该能概括所有子状态的共同特征。如果找不到合适的名字说明分组不合理原则三层级深度控制在 3~4 层以内过深的层级会增加调试复杂度。大多数战斗 AI 的层级深度在 3 层左右Root → Combat → MeleeAttack三、状态机代码实现(TypeScript)1. 状态模式State Pattern每个状态一个类符合开闭原则易于扩展和复用。1.1 接口定义interface IStateC { /** 进入状态时调用一次 */ enter(context: C): void; /** 每帧调用 */ update(context: C, deltaTime: number): void; /** 离开状态时调用一次 */ exit(context: C): void; /** 状态名称用于调试 */ readonly name: string; } interface IStateMachineC { currentState: IStateC; changeState(newState: IStateC): void; update(deltaTime: number): void; }1.2 战斗 AI 上下文与状态实现//状态类型枚举 export enum EnemyStateType { IdleIdle, //待机 Patrol Patrol, //巡逻 ChaseChase, //追击 AttackAttack, //攻击 FleeFlee, //逃跑 DeadDead, //死亡 } //所有状态列表 const EnemyFsmState { StateList: { [EnemyStateType.Idle]: new IdleState, [EnemyStateType.Patrol]: new PatrolState, [EnemyStateType.Chase]: new ChaseState, [EnemyStateType.Attack]: new AttackState, [EnemyStateType.Flee]: new FleeState, [EnemyStateType.Dead]: new DeadState, }, Default: EnemyStateType.Idle, } // --- 上下文 --- class EnemyContext { public health: number 100; public maxHealth: number 100; public position { x: 0, y: 0 }; public playerPosition { x: 0, y: 0 }; public attackRange: number 2.0; public chaseRange: number 10.0; public fleeThreshold: number 30; public stateMachine!: StateMachineEnemyContext; public distanceToPlayer(): number { const dx this.position.x - this.playerPosition.x; const dy this.position.y - this.playerPosition.y; return Math.sqrt(dx * dx dy * dy); } public canSeePlayer(): boolean { return this.distanceToPlayer() this.chaseRange; } public inAttackRange(): boolean { return this.distanceToPlayer() this.attackRange; } public isDead(): boolean { return this.health 0; } } // --- 待机状态 --- class IdleState implements IStateEnemyContext { public readonly name Idle; private timer 0; public enter(ctx: EnemyContext): void { this.timer 0; console.log([AI] 进入待机); } public update(ctx: EnemyContext, dt: number): void { this.timer dt; if (ctx.canSeePlayer()) { ctx.stateMachine.changeState(new ChaseState()); return; } if (this.timer 2.0) { ctx.stateMachine.changeState(new PatrolState()); } } public exit(ctx: EnemyContext): void { console.log([AI] 离开待机); } } // --- 巡逻状态 --- class PatrolState implements IStateEnemyContext { public readonly name Patrol; private pathIndex 0; private patrolPath [ { x: 0, y: 0 }, { x: 5, y: 0 }, { x: 5, y: 5 }, { x: 0, y: 5 }, ]; public enter(ctx: EnemyContext): void { console.log([AI] 进入巡逻); } public update(ctx: EnemyContext, dt: number): void { if (ctx.isDead()) { ctx.stateMachine.changeState(new DeadState()); return; } if (ctx.canSeePlayer()) { ctx.stateMachine.changeState(new ChaseState()); return; } this.moveAlongPath(ctx, dt); } public exit(ctx: EnemyContext): void { console.log([AI] 离开巡逻); } private moveAlongPath(ctx: EnemyContext, dt: number): void { const target this.patrolPath[this.pathIndex]; const dx target.x - ctx.position.x; const dy target.y - ctx.position.y; const dist Math.sqrt(dx * dx dy * dy); if (dist 0.1) { this.pathIndex (this.pathIndex 1) % this.patrolPath.length; return; } const speed 2.0; ctx.position.x (dx / dist) * speed * dt; ctx.position.y (dy / dist) * speed * dt; } } // --- 追击状态 --- class ChaseState implements IStateEnemyContext { public readonly name Chase; public enter(ctx: EnemyContext): void { console.log([AI] 进入追击); } public update(ctx: EnemyContext, dt: number): void { if (ctx.isDead()) { ctx.stateMachine.changeState(new DeadState()); return; } if (ctx.health ctx.fleeThreshold) { ctx.stateMachine.changeState(new FleeState()); return; } if (ctx.inAttackRange()) { ctx.stateMachine.changeState(new AttackState()); return; } if (!ctx.canSeePlayer()) { ctx.stateMachine.changeState(new PatrolState()); return; } this.moveTowardPlayer(ctx, dt); } public exit(ctx: EnemyContext): void { console.log([AI] 离开追击); } private moveTowardPlayer(ctx: EnemyContext, dt: number): void { const dx ctx.playerPosition.x - ctx.position.x; const dy ctx.playerPosition.y - ctx.position.y; const dist Math.sqrt(dx * dx dy * dy); if (dist 0.01) return; const speed 4.0; ctx.position.x (dx / dist) * speed * dt; ctx.position.y (dy / dist) * speed * dt; } } // --- 攻击状态 --- class AttackState implements IStateEnemyContext { public readonly name Attack; private attackTimer 0; private readonly attackDuration 0.8; private hasDealtDamage false; public enter(ctx: EnemyContext): void { console.log([AI] 进入攻击); this.attackTimer 0; this.hasDealtDamage false; } public update(ctx: EnemyContext, dt: number): void { if (ctx.isDead()) { ctx.stateMachine.changeState(new DeadState()); return; } this.attackTimer dt; // 前摇 0~0.3s判定 0.3s后摇 0.3~0.8s if (this.attackTimer 0.3 !this.hasDealtDamage) { this.dealDamage(ctx); this.hasDealtDamage true; } if (this.attackTimer this.attackDuration) { if (ctx.inAttackRange()) { ctx.stateMachine.changeState(new AttackState()); } else { ctx.stateMachine.changeState(new ChaseState()); } } } public exit(ctx: EnemyContext): void { console.log([AI] 离开攻击); } private dealDamage(ctx: EnemyContext): void { if (ctx.inAttackRange()) { console.log([AI] 攻击命中玩家); } } } // --- 逃跑状态 --- class FleeState implements IStateEnemyContext { public readonly name Flee; public enter(ctx: EnemyContext): void { console.log([AI] 进入逃跑); } public update(ctx: EnemyContext, dt: number): void { if (ctx.isDead()) { ctx.stateMachine.changeState(new DeadState()); return; } if (ctx.health ctx.fleeThreshold * 1.5) { ctx.stateMachine.changeState(new ChaseState()); return; } this.moveAwayFromPlayer(ctx, dt); } public exit(ctx: EnemyContext): void { console.log([AI] 离开逃跑); } private moveAwayFromPlayer(ctx: EnemyContext, dt: number): void { const dx ctx.position.x - ctx.playerPosition.x; const dy ctx.position.y - ctx.playerPosition.y; const dist Math.sqrt(dx * dx dy * dy); if (dist 0.01) return; const speed 5.0; ctx.position.x (dx / dist) * speed * dt; ctx.position.y (dy / dist) * speed * dt; } } // --- 死亡状态 --- class DeadState implements IStateEnemyContext { public readonly name Dead; public enter(ctx: EnemyContext): void { console.log([AI] 进入死亡状态); // 播放死亡动画、禁用碰撞等 } public update(ctx: EnemyContext, dt: number): void { // 死亡状态不处理任何逻辑 } public exit(ctx: EnemyContext): void { // 通常不会离开死亡状态 } }1.3 状态机核心class StateMachineC implements IStateMachineC { private _currentState: IStateC; private _defaultState: string; private context: C; private stateTime: number 0; private previousState: IStateC | null null; private states: Mapstring, FSMState new Map(); constructor(context: C) { this.context context; this._defaultState EnemyFsmState.Default; this.states.clear(); Object.entries(EnemyFsmState.stateList).forEach(([key, state]) { this.states.set(key, state); }); this._currentState this.states.get(this._defaultState); this._currentState.enter(context); } public get currentState(): IStateC { return this._currentState; } public get timeInState(): number { return this.stateTime; } public changeState(stateType:EnemyStateType): void { let newState this.states.get(stateType); if (newState this._currentState) { return; // 避免重复进入同一状态 } this._currentState.exit(this.context); this.previousState this._currentState; this._currentState newState; this.stateTime 0; this._currentState.enter(this.context); } /** 返回到上一个状态 */ public revertToPreviousState(): void { if (this.previousState) { this.changeState(this.previousState); } } public update(deltaTime: number): void { this.stateTime deltaTime; this._currentState.update(this.context, deltaTime); } }使用示例function main(): void { const ctx new EnemyContext(); const stateMachine new StateMachineEnemyContext(ctx); ctx.stateMachine stateMachine; // 模拟游戏循环 const fixedDeltaTime 1 / 60; let elapsed 0; const gameLoop setInterval(() { stateMachine.update(fixedDeltaTime); elapsed fixedDeltaTime; // 模拟玩家靠近 if (elapsed 1.0 elapsed 5.0) { ctx.playerPosition { x: ctx.position.x 3, y: ctx.position.y }; } if (elapsed 10.0) { clearInterval(gameLoop); console.log(模拟结束); } }, fixedDeltaTime * 1000); }2. 分层有限状态机HFSMHFSM 通过将状态分组并层级化解决 FSM 的状态爆炸问题。2.1 核心类型定义enum TransitionResult { /** 转移已处理不需要冒泡到父状态 */ Handled 0, /** 未处理冒泡到父状态 */ Unhandled 1, } interface IHStateC { readonly name: string; enter(ctx: C): void; update(ctx: C, dt: number): void; exit(ctx: C): void; /** 处理转移返回是否已处理 */ handleTransition(ctx: C): TransitionResult; /** 获取当前活跃的子状态用于调试 */ getActiveChild(): IHStateC | null; }2.2 分层状态基类abstract class HierarchicalStateC implements IHStateC { public readonly name: string; protected parent: HierarchicalStateC | null null; constructor(name: string) { this.name name; } public setParent(parent: HierarchicalStateC): void { this.parent parent; } public enter(ctx: C): void { console.log([HFSM] 进入 ${this.name}); } public update(ctx: C, dt: number): void { // 先让子状态处理 const child this.getActiveChild(); if (child) { child.update(ctx, dt); } } public exit(ctx: C): void { console.log([HFSM] 离开 ${this.name}); } public handleTransition(ctx: C): TransitionResult { // 先让子状态处理 const child this.getActiveChild(); if (child) { const result child.handleTransition(ctx); if (result TransitionResult.Handled) { return TransitionResult.Handled; } } // 子状态未处理当前状态处理 return this.onHandleTransition(ctx); } protected onHandleTransition(ctx: C): TransitionResult { return TransitionResult.Unhandled; } public getActiveChild(): IHStateC | null { return null; } }2.3 复合状态带子状态机class CompositeStateC extends HierarchicalStateC { private children: Mapstring, HierarchicalStateC new Map(); private activeChild: HierarchicalStateC | null null; private initialStateName: string; constructor(name: string, initialStateName: string) { super(name); this.initialStateName initialStateName; } public addChild(child: HierarchicalStateC): this { child.setParent(this); this.children.set(child.name, child); return this; } public enter(ctx: C): void { super.enter(ctx); const initial this.children.get(this.initialStateName); if (initial) { this.activeChild initial; initial.enter(ctx); } } public update(ctx: C, dt: number): void { // 先检查转移 const result this.handleTransition(ctx); if (result TransitionResult.Handled) { return; } // 再更新 super.update(ctx, dt); } public exit(ctx: C): void { this.activeChild?.exit(ctx); this.activeChild null; super.exit(ctx); } public getActiveChild(): IHStateC | null { return this.activeChild; } /** 切换子状态 */ public changeChild(newChildName: string, ctx: C): void { const newChild this.children.get(newChildName); if (!newChild) { console.warn([HFSM] 子状态 ${newChildName} 不存在于 ${this.name}); return; } if (this.activeChild newChild) return; this.activeChild?.exit(ctx); this.activeChild newChild; newChild.enter(ctx); } }2.4 战斗 AI 的 HFSM 实现// --- 复合状态非战斗 --- class NonCombatStateC extends EnemyContext extends CompositeStateC { constructor() { super(NonCombat, Idle); } protected onHandleTransition(ctx: C): TransitionResult { if (ctx.canSeePlayer()) { // 冒泡到根状态由根状态处理到 Combat 的转移 return TransitionResult.Unhandled; } return TransitionResult.Unhandled; } } // --- 复合状态战斗 --- class CombatStateC extends EnemyContext extends CompositeStateC { constructor() { super(Combat, Chase); } protected onHandleTransition(ctx: C): TransitionResult { if (!ctx.canSeePlayer() !ctx.inAttackRange()) { // 冒泡由根状态处理回 NonCombat return TransitionResult.Unhandled; } return TransitionResult.Unhandled; } } // --- 叶子状态待机 --- class IdleLeafC extends EnemyContext extends HierarchicalStateC { private timer 0; constructor() { super(Idle); } public enter(ctx: C): void { super.enter(ctx); this.timer 0; } public update(ctx: C, dt: number): void { this.timer dt; // 转移由父状态处理 } protected onHandleTransition(ctx: C): TransitionResult { if (this.timer 2.0) { const parent this.parent as CompositeStateC; parent.changeChild(Patrol, ctx); return TransitionResult.Handled; } return TransitionResult.Unhandled; } } // --- 叶子状态巡逻 --- class PatrolLeafC extends EnemyContext extends HierarchicalStateC { constructor() { super(Patrol); } public update(ctx: C, dt: number): void { // 巡逻移动逻辑 } } // --- 叶子状态追击 --- class ChaseLeafC extends EnemyContext extends HierarchicalStateC { constructor() { super(Chase); } public update(ctx: C, dt: number): void { // 追击移动逻辑 } protected onHandleTransition(ctx: C): TransitionResult { if (ctx.health ctx.fleeThreshold) { // 在战斗复合状态内切换到逃跑 const parent this.parent as CompositeStateC; parent.changeChild(Flee, ctx); return TransitionResult.Handled; } if (ctx.inAttackRange()) { const parent this.parent as CompositeStateC; parent.changeChild(Attack, ctx); return TransitionResult.Handled; } return TransitionResult.Unhandled; } } // --- 根状态 --- class RootStateC extends EnemyContext extends CompositeStateC { constructor() { super(Root, NonCombat); } protected onHandleTransition(ctx: C): TransitionResult { const activeChild this.getActiveChild(); // 全局转移死亡 if (ctx.isDead()) { this.changeChild(Dead, ctx); return TransitionResult.Handled; } // NonCombat - Combat 切换 if (activeChild?.name NonCombat ctx.canSeePlayer()) { this.changeChild(Combat, ctx); return TransitionResult.Handled; } if (activeChild?.name Combat !ctx.canSeePlayer() !ctx.inAttackRange()) { this.changeChild(NonCombat, ctx); return TransitionResult.Handled; } return TransitionResult.Unhandled; } }2.5 HFSM 的优缺点优点大幅减少转移数量解决状态爆炸保留 FSM 的低开销和显式控制支持转移冒泡父状态可处理通用转移缺点调试复杂度高于纯 FSM可能出现高层与低层状态的语义冲突3. 并行状态机 与 栈式状态机3.1 并行状态机并行状态机允许同时运行多个独立的状态机处理不同维度的行为。实现:class ParallelStateMachineC { private machines: Mapstring, StateMachineC new Map(); private context: C; constructor(context: C) { this.context context; } public addMachine(name: string, machine: StateMachineC): void { this.machines.set(name, machine); } public getMachine(name: string): StateMachineC | undefined { return this.machines.get(name); } public update(deltaTime: number): void { for (const machine of this.machines.values()) { machine.update(deltaTime); } } }使用示例角色多维度行为class CharacterContext { public health 100; public isMoving false; public isAttacking false; public isCasting false; public movementMachine!: StateMachineCharacterContext; public actionMachine!: StateMachineCharacterContext; } // 移动状态机Idle / Walk / Run / Jump // 动作状态机None / Attack / Block / Cast // 两个状态机并行运行通过共享上下文协调并行状态机适合复杂角色的行为组合但需要注意状态机之间的互斥规则如攻击时不能移动优先级冲突解决共享状态的同步3.2 栈式状态机用栈管理状态支持 Push / Pop 操作适合需要返回的场景。class StackStateMachineC { private stack: IStateC[] []; private context: C; constructor(context: C) { this.context context; } public get currentState(): IStateC | null { return this.stack.length 0 ? this.stack[this.stack.length - 1] : null; } /** 压入新状态当前状态暂停 */ public push(state: IStateC): void { this.currentState?.exit(this.context); this.stack.push(state); state.enter(this.context); } /** 弹出当前状态恢复到上一个状态 */ public pop(): void { if (this.stack.length 0) return; const state this.stack.pop()!; state.exit(this.context); this.currentState?.enter(this.context); } /** 替换当前状态 */ public replace(state: IStateC): void { if (this.stack.length 0) { const old this.stack.pop()!; old.exit(this.context); } this.stack.push(state); state.enter(this.context); } public update(deltaTime: number): void { this.currentState?.update(this.context, deltaTime); } }