State Machines
A state machine models gameplay state explicitly: idle, running, jumping, attacking. Each state knows its own transitions. The discipline keeps physics, animation, and input logic from devolving into a soup of ifs.
A character FSM
EXAMPLE
// 1) Hand-rolled FSM — fine for most games
const states = {
idle: {
enter() { player.sprite.play('idle'); },
update(dt) {
if (input.x !== 0) transition('run');
if (input.jumpPressed) transition('jump');
},
},
run: {
enter() { player.sprite.play('run'); },
update(dt) {
if (input.x === 0) transition('idle');
if (input.jumpPressed) transition('jump');
player.x += input.x * RUN_SPEED * dt;
},
},
jump: {
enter() {
player.vy = JUMP_VY;
player.sprite.play('jump');
},
update(dt) {
player.vy += GRAVITY * dt;
player.y += player.vy * dt;
if (player.onGround) transition(input.x === 0 ? 'idle' : 'run');
},
},
attack: {
enter() {
player.sprite.play('attack');
player.attackTime = 0.4;
},
update(dt) {
player.attackTime -= dt;
if (player.attackTime <= 0) transition('idle');
},
},
};
let current = states.idle;
function transition(name) {
current = states[name];
current.enter?.();
}
function update(dt) {
current.update(dt);
}
// 2) Hierarchical FSM — “in air” super-state with sub-states (jump, fall)
// Lets you handle attacks DURING jump without duplicating transitions.
// 3) For complex AI, reach for a behaviour tree (Behavior3, Owyl)
// or GOAP — they scale where flat FSMs would explode.
// 4) When you have async / parallel states, look at the actor model (XState)
// npm i xstate
import { createMachine, interpret } from 'xstate';
const characterMachine = createMachine({
id: 'char',
initial: 'idle',
states: {
idle: { on: { MOVE: 'run', JUMP: 'jump' } },
run: { on: { STOP: 'idle', JUMP: 'jump' } },
jump: { on: { LAND: 'idle' } },
},
});
const svc = interpret(characterMachine).start();
svc.send('MOVE'); // idle → run
Why it matters
FSMs cap the “input + animation + physics” explosion. The most-debugged player controller in any game becomes the easiest to extend — new state, new transitions, same shape.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// FSM keeps gameplay state predictable.
let state = 'idle';
function tick(dt) {
switch (state) {
case 'idle': if (input.move) state = 'run'; break;
case 'run': if (input.jump) state = 'jump'; break;
case 'jump': if (onGround) state = 'idle'; break;
}
}
Try it Yourself »
Exercise
Acronym for the classic AI pattern.
Three letters.
Discussion
Loading…