Files
esengine/docs/modules/fsm/index.md
YHH 4a16e30794 docs(modules): 添加框架模块文档 (#350)
* docs(modules): 添加框架模块文档

添加以下模块的完整文档:
- FSM (状态机): 状态定义、转换条件、优先级、事件监听
- Timer (定时器): 定时器调度、冷却系统、服务令牌
- Spatial (空间索引): GridSpatialIndex、AOI 兴趣区域管理
- Pathfinding (寻路): A* 算法、网格地图、导航网格、路径平滑
- Procgen (程序化生成): 噪声函数、种子随机数、加权随机

所有文档均基于实际源码 API 编写,包含:
- 快速开始示例
- 完整 API 参考
- 实际使用案例
- 蓝图节点说明
- 最佳实践建议

* docs(modules): 添加 Blueprint 模块文档和所有模块英文版

新增中文文档:
- Blueprint (蓝图可视化脚本): VM、自定义节点、组合系统、触发器

新增英文文档 (docs/en/modules/):
- FSM: State machine API, transitions, ECS integration
- Timer: Timers, cooldowns, service tokens
- Spatial: Grid spatial index, AOI management
- Pathfinding: A*, grid map, NavMesh, path smoothing
- Procgen: Noise functions, seeded random, weighted random
- Blueprint: Visual scripting, custom nodes, composition

所有文档均基于实际源码 API 编写。
2025-12-26 20:02:21 +08:00

8.1 KiB
Raw Blame History

状态机 (FSM)

@esengine/fsm 提供了一个类型安全的有限状态机实现用于角色、AI 或任何需要状态管理的场景。

安装

npm install @esengine/fsm

快速开始

import { createStateMachine } from '@esengine/fsm';

// 定义状态类型
type PlayerState = 'idle' | 'walk' | 'run' | 'jump';

// 创建状态机
const fsm = createStateMachine<PlayerState>('idle');

// 定义状态和回调
fsm.defineState('idle', {
    onEnter: (ctx, from) => console.log(`从 ${from} 进入 idle`),
    onExit: (ctx, to) => console.log(`从 idle 退出到 ${to}`),
    onUpdate: (ctx, dt) => { /* 每帧更新 */ }
});

fsm.defineState('walk', {
    onEnter: () => console.log('开始行走')
});

// 手动切换状态
fsm.transition('walk');

console.log(fsm.current); // 'walk'

核心概念

状态配置

每个状态可以配置以下回调:

interface StateConfig<TState, TContext> {
    name: TState;                                    // 状态名称
    onEnter?: (context: TContext, from: TState | null) => void;  // 进入回调
    onExit?: (context: TContext, to: TState) => void;            // 退出回调
    onUpdate?: (context: TContext, deltaTime: number) => void;   // 更新回调
    tags?: string[];                                 // 状态标签
    metadata?: Record<string, unknown>;              // 元数据
}

转换条件

可以定义带条件的状态转换:

interface Context {
    isMoving: boolean;
    isRunning: boolean;
    isGrounded: boolean;
}

const fsm = createStateMachine<PlayerState, Context>('idle', {
    context: { isMoving: false, isRunning: false, isGrounded: true }
});

// 定义转换条件
fsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving);
fsm.defineTransition('walk', 'run', (ctx) => ctx.isRunning);
fsm.defineTransition('walk', 'idle', (ctx) => !ctx.isMoving);

// 自动评估并执行满足条件的转换
fsm.evaluateTransitions();

转换优先级

当多个转换条件同时满足时,优先级高的先执行:

// 优先级数字越大越优先
fsm.defineTransition('idle', 'attack', (ctx) => ctx.isAttacking, 10);
fsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving, 1);

// 如果同时满足,会先尝试 attack优先级 10

API 参考

createStateMachine

function createStateMachine<TState extends string, TContext = unknown>(
    initialState: TState,
    options?: StateMachineOptions<TContext>
): IStateMachine<TState, TContext>

参数:

  • initialState - 初始状态
  • options.context - 上下文对象,在回调中可访问
  • options.maxHistorySize - 最大历史记录数(默认 100
  • options.enableHistory - 是否启用历史记录(默认 true

状态机属性

属性 类型 描述
current TState 当前状态
previous TState | null 上一个状态
context TContext 上下文对象
isTransitioning boolean 是否正在转换中
currentStateDuration number 当前状态持续时间(毫秒)

状态机方法

状态定义

// 定义状态
fsm.defineState('idle', {
    onEnter: (ctx, from) => {},
    onExit: (ctx, to) => {},
    onUpdate: (ctx, dt) => {}
});

// 检查状态是否存在
fsm.hasState('idle'); // true

// 获取状态配置
fsm.getStateConfig('idle');

// 获取所有状态
fsm.getStates(); // ['idle', 'walk', ...]

转换操作

// 定义转换
fsm.defineTransition('idle', 'walk', condition, priority);

// 移除转换
fsm.removeTransition('idle', 'walk');

// 获取从某状态出发的所有转换
fsm.getTransitionsFrom('idle');

// 检查是否可以转换
fsm.canTransition('walk'); // true/false

// 手动转换
fsm.transition('walk');

// 强制转换(忽略条件)
fsm.transition('walk', true);

// 自动评估转换条件
fsm.evaluateTransitions();

生命周期

// 更新状态机(调用当前状态的 onUpdate
fsm.update(deltaTime);

// 重置状态机
fsm.reset(); // 重置到当前状态
fsm.reset('idle'); // 重置到指定状态

事件监听

// 监听进入特定状态
const unsubscribe = fsm.onEnter('walk', (from) => {
    console.log(`从 ${from} 进入 walk`);
});

// 监听退出特定状态
fsm.onExit('walk', (to) => {
    console.log(`从 walk 退出到 ${to}`);
});

// 监听任意状态变化
fsm.onChange((event) => {
    console.log(`${event.from} -> ${event.to} at ${event.timestamp}`);
});

// 取消订阅
unsubscribe();

调试

// 获取状态历史
const history = fsm.getHistory();
// [{ from: 'idle', to: 'walk', timestamp: 1234567890 }, ...]

// 清除历史
fsm.clearHistory();

// 获取调试信息
const info = fsm.getDebugInfo();
// { current, previous, duration, stateCount, transitionCount, historySize }

实际示例

角色状态机

import { createStateMachine } from '@esengine/fsm';

type CharacterState = 'idle' | 'walk' | 'run' | 'jump' | 'fall' | 'attack';

interface CharacterContext {
    velocity: { x: number; y: number };
    isGrounded: boolean;
    isAttacking: boolean;
    speed: number;
}

const characterFSM = createStateMachine<CharacterState, CharacterContext>('idle', {
    context: {
        velocity: { x: 0, y: 0 },
        isGrounded: true,
        isAttacking: false,
        speed: 0
    }
});

// 定义状态
characterFSM.defineState('idle', {
    onEnter: (ctx) => {
        ctx.speed = 0;
    },
    onUpdate: (ctx, dt) => {
        // 播放待机动画
    }
});

characterFSM.defineState('walk', {
    onEnter: (ctx) => {
        ctx.speed = 100;
    }
});

characterFSM.defineState('run', {
    onEnter: (ctx) => {
        ctx.speed = 200;
    }
});

characterFSM.defineState('jump', {
    onEnter: (ctx) => {
        ctx.velocity.y = -300;
        ctx.isGrounded = false;
    }
});

// 定义转换
characterFSM.defineTransition('idle', 'walk', (ctx) => Math.abs(ctx.velocity.x) > 0);
characterFSM.defineTransition('walk', 'idle', (ctx) => ctx.velocity.x === 0);
characterFSM.defineTransition('walk', 'run', (ctx) => Math.abs(ctx.velocity.x) > 150);
characterFSM.defineTransition('run', 'walk', (ctx) => Math.abs(ctx.velocity.x) <= 150);

// 跳跃有最高优先级
characterFSM.defineTransition('idle', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('walk', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('run', 'jump', (ctx) => !ctx.isGrounded, 10);

characterFSM.defineTransition('jump', 'fall', (ctx) => ctx.velocity.y > 0);
characterFSM.defineTransition('fall', 'idle', (ctx) => ctx.isGrounded);

// 游戏循环中使用
function gameUpdate(dt: number) {
    // 更新上下文
    characterFSM.context.velocity.x = getInputVelocity();
    characterFSM.context.isGrounded = checkGrounded();

    // 评估状态转换
    characterFSM.evaluateTransitions();

    // 更新当前状态
    characterFSM.update(dt);
}

与 ECS 集成

import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { createStateMachine, type IStateMachine } from '@esengine/fsm';

// 状态机组件
class FSMComponent extends Component {
    fsm: IStateMachine<string>;

    constructor(initialState: string) {
        super();
        this.fsm = createStateMachine(initialState);
    }
}

// 状态机系统
class FSMSystem extends EntitySystem {
    constructor() {
        super(Matcher.all(FSMComponent));
    }

    protected processEntity(entity: Entity, dt: number): void {
        const fsmComp = entity.getComponent(FSMComponent);
        fsmComp.fsm.evaluateTransitions();
        fsmComp.fsm.update(dt);
    }
}

蓝图节点

FSM 模块提供了可视化脚本支持的蓝图节点:

  • GetCurrentState - 获取当前状态
  • TransitionTo - 转换到指定状态
  • CanTransition - 检查是否可以转换
  • IsInState - 检查是否在指定状态
  • WasInState - 检查是否曾在指定状态
  • GetStateDuration - 获取状态持续时间
  • EvaluateTransitions - 评估转换条件
  • ResetStateMachine - 重置状态机