一、 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 状态内部包含了前摇、判定、后摇、连招分支等复杂逻辑,就应该考虑将其拆分为子状态机(HFSM)
2.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可以这样分层
TypeScript
战斗(Combat)[高层状态]
├── 近战攻击(MeleeAttack)[低层状态]
├── 远程攻击(RangedAttack)[低层状态]
└── 防御(Defend)[低层状态]
非战斗(NonCombat)[高层状态]
├── 巡逻(Patrol)
└── 待机(Idle)
从NonCombat进入Combat只需要一条高层转移,而不是从Patrol、Idle分别连接到MeleeAttack、RangedAttack、Defend的九条转移。HFSM还支持转移的重用------高层状态的转移条件可以被所有低层状态共享,减少了代码冗余
2. 为什么需要 HFSM:从状态爆炸说起
2.1 传统 FSM 的状态爆炸问题
假设一个战斗 AI 有以下状态:
TypeScript
Idle, Patrol, Alert, Chase, MeleeAttack, RangedAttack, Block, Dodge, Flee, Dead
如果采用扁平 FSM,需要考虑的转移数量是:
-
每个状态平均需要检查 3~5 个转移条件
-
10 个状态 → 约 30~50 条转移
-
每新增一个状态,需要接入的转移数随已有状态数线性增长
更严重的是语义重复。例如:
-
Idle、Patrol、Alert都可能因为"发现玩家"转移到Chase -
MeleeAttack、RangedAttack、Block、Dodge都可能因为"生命值过低"转移到Flee -
所有状态都可能因为"生命值归零"转移到
Dead
这些转移条件在每个状态中重复编写,修改时需要同步修改多处,维护成本随状态数增长而急剧上升。
2.2 HFSM 的核心洞察
HFSM 的核心洞察是:很多状态共享相同的转移逻辑,很多转移只在特定状态组内才有意义。
将状态分组后:
-
组内转移:只在组内状态之间发生,外部不需要知道
-
组间转移:由父状态统一管理,子状态不需要重复编写
-
通用转移:定义在高层,所有子状态自动继承
示例:将上述 10 个状态分组:
TypeScript
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 的设计原则
原则一:按语义分组,而非按实现分组
好的分组原则:
TypeScript
Combat(战斗)
├── Chase(追击)
├── Attack(攻击)
└── Defend(防御)
不好的分组:
TypeScript
StatesWithTimer(带计时器的状态)
├── Idle
├── Attack
└── Flee
原则二:父状态应该有明确的语义
父状态名应该能概括所有子状态的共同特征。如果找不到合适的名字,说明分组不合理
原则三:层级深度控制在 3~4 层以内
过深的层级会增加调试复杂度。大多数战斗 AI 的层级深度在 3 层左右:
TypeScript
Root → Combat → MeleeAttack
三、状态机代码实现(TypeScript)
1. 状态模式(State Pattern)
每个状态一个类,符合开闭原则,易于扩展和复用。
1.1 接口定义
TypeScript
interface IState<C> {
/** 进入状态时调用一次 */
enter(context: C): void;
/** 每帧调用 */
update(context: C, deltaTime: number): void;
/** 离开状态时调用一次 */
exit(context: C): void;
/** 状态名称,用于调试 */
readonly name: string;
}
interface IStateMachine<C> {
currentState: IState<C>;
changeState(newState: IState<C>): void;
update(deltaTime: number): void;
}
1.2 战斗 AI 上下文与状态实现
TypeScript
//状态类型枚举
export enum EnemyStateType {
Idle="Idle", //待机
Patrol= "Patrol", //巡逻
Chase="Chase", //追击
Attack="Attack", //攻击
Flee="Flee", //逃跑
Dead="Dead", //死亡
}
//所有状态列表
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!: StateMachine<EnemyContext>;
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 IState<EnemyContext> {
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 IState<EnemyContext> {
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 IState<EnemyContext> {
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 IState<EnemyContext> {
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 IState<EnemyContext> {
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 IState<EnemyContext> {
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 状态机核心
TypeScript
class StateMachine<C> implements IStateMachine<C> {
private _currentState: IState<C>;
private _defaultState: string;
private context: C;
private stateTime: number = 0;
private previousState: IState<C> | null = null;
private states: Map<string, 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(): IState<C> {
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);
}
}
使用示例
TypeScript
function main(): void {
const ctx = new EnemyContext();
const stateMachine = new StateMachine<EnemyContext>(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. 分层有限状态机(HFSM)
HFSM 通过将状态分组并层级化,解决 FSM 的状态爆炸问题。
2.1 核心类型定义
TypeScript
enum TransitionResult {
/** 转移已处理,不需要冒泡到父状态 */
Handled = 0,
/** 未处理,冒泡到父状态 */
Unhandled = 1,
}
interface IHState<C> {
readonly name: string;
enter(ctx: C): void;
update(ctx: C, dt: number): void;
exit(ctx: C): void;
/** 处理转移,返回是否已处理 */
handleTransition(ctx: C): TransitionResult;
/** 获取当前活跃的子状态(用于调试) */
getActiveChild(): IHState<C> | null;
}
2.2 分层状态基类
TypeScript
abstract class HierarchicalState<C> implements IHState<C> {
public readonly name: string;
protected parent: HierarchicalState<C> | null = null;
constructor(name: string) {
this.name = name;
}
public setParent(parent: HierarchicalState<C>): 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(): IHState<C> | null {
return null;
}
}
2.3 复合状态(带子状态机)
TypeScript
class CompositeState<C> extends HierarchicalState<C> {
private children: Map<string, HierarchicalState<C>> = new Map();
private activeChild: HierarchicalState<C> | null = null;
private initialStateName: string;
constructor(name: string, initialStateName: string) {
super(name);
this.initialStateName = initialStateName;
}
public addChild(child: HierarchicalState<C>): 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(): IHState<C> | 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 实现
TypeScript
// --- 复合状态:非战斗 ---
class NonCombatState<C extends EnemyContext> extends CompositeState<C> {
constructor() {
super("NonCombat", "Idle");
}
protected onHandleTransition(ctx: C): TransitionResult {
if (ctx.canSeePlayer()) {
// 冒泡到根状态,由根状态处理到 Combat 的转移
return TransitionResult.Unhandled;
}
return TransitionResult.Unhandled;
}
}
// --- 复合状态:战斗 ---
class CombatState<C extends EnemyContext> extends CompositeState<C> {
constructor() {
super("Combat", "Chase");
}
protected onHandleTransition(ctx: C): TransitionResult {
if (!ctx.canSeePlayer() && !ctx.inAttackRange()) {
// 冒泡,由根状态处理回 NonCombat
return TransitionResult.Unhandled;
}
return TransitionResult.Unhandled;
}
}
// --- 叶子状态:待机 ---
class IdleLeaf<C extends EnemyContext> extends HierarchicalState<C> {
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 CompositeState<C>;
parent.changeChild("Patrol", ctx);
return TransitionResult.Handled;
}
return TransitionResult.Unhandled;
}
}
// --- 叶子状态:巡逻 ---
class PatrolLeaf<C extends EnemyContext> extends HierarchicalState<C> {
constructor() {
super("Patrol");
}
public update(ctx: C, dt: number): void {
// 巡逻移动逻辑
}
}
// --- 叶子状态:追击 ---
class ChaseLeaf<C extends EnemyContext> extends HierarchicalState<C> {
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 CompositeState<C>;
parent.changeChild("Flee", ctx);
return TransitionResult.Handled;
}
if (ctx.inAttackRange()) {
const parent = this.parent as CompositeState<C>;
parent.changeChild("Attack", ctx);
return TransitionResult.Handled;
}
return TransitionResult.Unhandled;
}
}
// --- 根状态 ---
class RootState<C extends EnemyContext> extends CompositeState<C> {
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 并行状态机
并行状态机允许同时运行多个独立的状态机,处理不同维度的行为。
实现:
TypeScript
class ParallelStateMachine<C> {
private machines: Map<string, StateMachine<C>> = new Map();
private context: C;
constructor(context: C) {
this.context = context;
}
public addMachine(name: string, machine: StateMachine<C>): void {
this.machines.set(name, machine);
}
public getMachine(name: string): StateMachine<C> | undefined {
return this.machines.get(name);
}
public update(deltaTime: number): void {
for (const machine of this.machines.values()) {
machine.update(deltaTime);
}
}
}
使用示例:角色多维度行为
TypeScript
class CharacterContext {
public health = 100;
public isMoving = false;
public isAttacking = false;
public isCasting = false;
public movementMachine!: StateMachine<CharacterContext>;
public actionMachine!: StateMachine<CharacterContext>;
}
// 移动状态机:Idle / Walk / Run / Jump
// 动作状态机:None / Attack / Block / Cast
// 两个状态机并行运行,通过共享上下文协调
并行状态机适合复杂角色的行为组合,但需要注意:
-
状态机之间的互斥规则(如攻击时不能移动)
-
优先级冲突解决
-
共享状态的同步
3.2 栈式状态机
用栈管理状态,支持 Push / Pop 操作,适合需要"返回"的场景。
TypeScript
class StackStateMachine<C> {
private stack: IState<C>[] = [];
private context: C;
constructor(context: C) {
this.context = context;
}
public get currentState(): IState<C> | null {
return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
}
/** 压入新状态,当前状态暂停 */
public push(state: IState<C>): 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: IState<C>): 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);
}
}