State Machine (FSM)
@esengine/fsm provides a type-safe finite state machine implementation for characters, AI, or any scenario requiring state management.
Installation
Section titled “Installation”npm install @esengine/fsmQuick Start
Section titled “Quick Start”import { createStateMachine } from '@esengine/fsm';
// Define state typestype PlayerState = 'idle' | 'walk' | 'run' | 'jump';
// Create state machineconst fsm = createStateMachine<PlayerState>('idle');
// Define states with callbacksfsm.defineState('idle', { onEnter: (ctx, from) => console.log(`Entered idle from ${from}`), onExit: (ctx, to) => console.log(`Exiting idle to ${to}`), onUpdate: (ctx, dt) => { /* Update every frame */ }});
fsm.defineState('walk', { onEnter: () => console.log('Started walking')});
// Manual transitionfsm.transition('walk');
console.log(fsm.current); // 'walk'Core Concepts
Section titled “Core Concepts”State Configuration
Section titled “State Configuration”Each state can be configured with the following callbacks:
interface StateConfig<TState, TContext> { name: TState; // State name onEnter?: (context: TContext, from: TState | null) => void; // Enter callback onExit?: (context: TContext, to: TState) => void; // Exit callback onUpdate?: (context: TContext, deltaTime: number) => void; // Update callback tags?: string[]; // State tags metadata?: Record<string, unknown>; // Metadata}Transition Conditions
Section titled “Transition Conditions”Define conditional state transitions:
interface Context { isMoving: boolean; isRunning: boolean; isGrounded: boolean;}
const fsm = createStateMachine<PlayerState, Context>('idle', { context: { isMoving: false, isRunning: false, isGrounded: true }});
// Define transition conditionsfsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving);fsm.defineTransition('walk', 'run', (ctx) => ctx.isRunning);fsm.defineTransition('walk', 'idle', (ctx) => !ctx.isMoving);
// Automatically evaluate and execute matching transitionsfsm.evaluateTransitions();Transition Priority
Section titled “Transition Priority”When multiple transitions are valid, higher priority executes first:
// Higher priority number = higher priorityfsm.defineTransition('idle', 'attack', (ctx) => ctx.isAttacking, 10);fsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving, 1);
// If both conditions are met, 'attack' (priority 10) is tried firstAPI Reference
Section titled “API Reference”createStateMachine
Section titled “createStateMachine”function createStateMachine<TState extends string, TContext = unknown>( initialState: TState, options?: StateMachineOptions<TContext>): IStateMachine<TState, TContext>Parameters:
initialState- Initial stateoptions.context- Context object, accessible in callbacksoptions.maxHistorySize- Maximum history entries (default 100)options.enableHistory- Enable history tracking (default true)
State Machine Properties
Section titled “State Machine Properties”| Property | Type | Description |
|---|---|---|
current | TState | Current state |
previous | TState | null | Previous state |
context | TContext | Context object |
isTransitioning | boolean | Whether currently transitioning |
currentStateDuration | number | Current state duration (ms) |
State Machine Methods
Section titled “State Machine Methods”State Definition
Section titled “State Definition”// Define statefsm.defineState('idle', { onEnter: (ctx, from) => {}, onExit: (ctx, to) => {}, onUpdate: (ctx, dt) => {}});
// Check if state existsfsm.hasState('idle'); // true
// Get state configurationfsm.getStateConfig('idle');
// Get all statesfsm.getStates(); // ['idle', 'walk', ...]Transition Operations
Section titled “Transition Operations”// Define transitionfsm.defineTransition('idle', 'walk', condition, priority);
// Remove transitionfsm.removeTransition('idle', 'walk');
// Get transitions from statefsm.getTransitionsFrom('idle');
// Check if transition is possiblefsm.canTransition('walk'); // true/false
// Manual transitionfsm.transition('walk');
// Force transition (ignore conditions)fsm.transition('walk', true);
// Auto-evaluate transition conditionsfsm.evaluateTransitions();Lifecycle
Section titled “Lifecycle”// Update state machine (calls current state's onUpdate)fsm.update(deltaTime);
// Reset state machinefsm.reset(); // Reset to current statefsm.reset('idle'); // Reset to specified stateEvent Listeners
Section titled “Event Listeners”// Listen to entering specific stateconst unsubscribe = fsm.onEnter('walk', (from) => { console.log(`Entered walk from ${from}`);});
// Listen to exiting specific statefsm.onExit('walk', (to) => { console.log(`Exiting walk to ${to}`);});
// Listen to any state changefsm.onChange((event) => { console.log(`${event.from} -> ${event.to} at ${event.timestamp}`);});
// Unsubscribeunsubscribe();Debugging
Section titled “Debugging”// Get state historyconst history = fsm.getHistory();// [{ from: 'idle', to: 'walk', timestamp: 1234567890 }, ...]
// Clear historyfsm.clearHistory();
// Get debug infoconst info = fsm.getDebugInfo();// { current, previous, duration, stateCount, transitionCount, historySize }Practical Examples
Section titled “Practical Examples”Character State Machine
Section titled “Character State Machine”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 }});
// Define statescharacterFSM.defineState('idle', { onEnter: (ctx) => { ctx.speed = 0; }});
characterFSM.defineState('walk', { onEnter: (ctx) => { ctx.speed = 100; }});
characterFSM.defineState('run', { onEnter: (ctx) => { ctx.speed = 200; }});
// Define transitionscharacterFSM.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);
// Jump has highest prioritycharacterFSM.defineTransition('idle', 'jump', (ctx) => !ctx.isGrounded, 10);characterFSM.defineTransition('walk', 'jump', (ctx) => !ctx.isGrounded, 10);
// Game loop usagefunction gameUpdate(dt: number) { // Update context characterFSM.context.velocity.x = getInputVelocity(); characterFSM.context.isGrounded = checkGrounded();
// Evaluate transitions characterFSM.evaluateTransitions();
// Update current state characterFSM.update(dt);}ECS Integration
Section titled “ECS Integration”import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework';import { createStateMachine, type IStateMachine } from '@esengine/fsm';
// State machine componentclass FSMComponent extends Component { fsm: IStateMachine<string>;
constructor(initialState: string) { super(); this.fsm = createStateMachine(initialState); }}
// State machine systemclass 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); }}Blueprint Nodes
Section titled “Blueprint Nodes”The FSM module provides blueprint nodes for visual scripting:
GetCurrentState- Get current stateTransitionTo- Transition to specified stateCanTransition- Check if transition is possibleIsInState- Check if in specified stateWasInState- Check if was ever in specified stateGetStateDuration- Get state durationEvaluateTransitions- Evaluate transition conditionsResetStateMachine- Reset state machine
Documentation
Section titled “Documentation”- API Reference - Complete API documentation
- Examples - Character FSM, AI behavior, ECS integration