Phaser AE

State machines and game flow

Free packv3.0.0-4Logic

A brain for every object that has modes, and a phase graph for the game itself. Patrol, chase and dying become three named states; attract, play, dying and game over become four named phases; and neither needs a tangle of flags.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

State machines and game flow: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

What it does

The engine has no entity classes, so an object's behaviour lives in a brain: a plain object of named states, each with optional enter, tick and exit callbacks. You pass it as brain when you spawn the object. Every simulate step the pack runs the current state's tick, and on the handle h.fsm.go switches state (calling the old state's exit and the new one's enter), h.fsm.is tests it and h.fsm.time counts the seconds spent in it. Write motion in tick, one-shot effects such as a sound or a shake in enter, and clean-up in exit.

The game's own flow is a different shape, and createPhases handles it. Phases share one context object, and each lists its way out: edges with a condition, tested in order after the phase's update, or a timed exit after so many seconds. Changes are queued and applied at the top of the next update, so a phase is never torn down while its own code is running. A phase can own things, such as a game-over banner, and they are disposed when it ends; timers started in a phase end with it too. restart() takes a phase's restart point, the death-to-respawn edge that skips an intro.

For an attract loop or a cut scene, createSequence plays named steps for set times and reports how far through the current step it is, so a title can cycle logo, demo and high scores and fade between them.

Features

  • Brains at spawn — named states with enter, tick and exit, attached with the brain spawn field.
  • Time in state — h.fsm.time counts seconds since the last change, for "chase for three seconds, then give up".
  • Typed states — game.fsm.define infers the state names as a union, so a misspelt go target is a type error, and a missing state at run time is an error that lists the states the brain has.
  • Ticks before collision — brains run before collision in the same frame, so a state change is seen at once.
  • Phase graphs — guarded edges, timed exits, restartAt, in(...names) and the previous phase.
  • Owned objects and timers — own, at and every, all cleared when the phase exits.
  • Sequences — looping attract cycles or one-shot cut scenes, with current, frac, go, reset and onStep.
  • Headless, 2D or 3D — it draws nothing and works in any world.

Good for

  • Arcade enemies with patrol, chase, flee and dying states.
  • Bosses with phases, where each phase is a state with its own attack pattern and an entry flourish.
  • Player controllers with grounded, jumping and dashing states.
  • Doors, turrets and pickups that idle, react and then do something else.
  • An arcade game's flow: attract, play, dying with a respawn, and game over back to attract.
  • Attract modes and cut scenes as timed steps.

Make it yours

A brain is just data, so each kind of enemy is a new object of states, and two enemies can share most of a brain. The pack has no options: compose it, pass brain at spawn, and import createPhases and createSequence (or use game.fsm.phases and game.fsm.sequence) when the game needs them.

Two quite different uses: a top-down shooter where every enemy carries a three-state brain that patrols, chases the player when near and plays a short death; and a game where fsm holds no brains at all and only runs the flow, an attract sequence that loops logo, demo and scores, then play, dying and game-over phases with a timed return.

A quick look

An enemy with three states, attached at spawn:

const crab = game.fsm.define<'patrol' | 'chase' | 'dying'>({
    initial: 'patrol',
    states: {
        patrol: { tick: (h, dt) => { h.x += 40 * dt; if (near(h, player)) h.fsm.go('chase'); } },
        chase:  { tick: (h, dt) => moveToward(h, player, 70 * dt) },
        dying:  { enter: h => game.after(0.25, () => kill(h)) },
    },
});
game.spawn({ id: 'crab', x, y, tags: ['enemy'], brain: crab });

Works with

  • collision — brains tick before it, so a state change counts in the same frame's hits.
  • scoreboard — score, lives and the high-score table that phases test and reset.
  • wave-plan — waves, difficulty and the win rule a play phase drives.
  • title-screen — a title screen drawn for you.
  • flow — ready-drawn title, pause and game-over scenes, when you do not need your own phases.

Good to know

  • One brain per object: a second brain() call replaces the first without calling its exit.
  • h.fsm.state is the state's index, a number; use is() or game.fsm.state(h) for the name.
  • A go from inside enter works but re-enters at once, so guard against loops. Ticks run in spawn order, with no priority.
  • Phases and sequences are not stepped for you: call update(dt) once a frame. A go to a phase with no definition enters an empty phase rather than throwing.
  • For one behaviour with no branches, a plain simulate step over a tag is simpler.

More Logic packs

Show all →