Phaser AE

Track networks

Free packv3.0.0-42D and 3DLogic

A directed graph of curves, written as plain data, with walkers that travel it at an even speed, choose a branch at each junction and know how far they still have to go. It is the route a tower-defence wave follows, a patrol with forks, carts on a rail network or a race circuit's centre line.

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

Released October 14, 2026 · updated October 19, 2026

Track networks: 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.

The pack

track

function track(): Pack<{ track: TrackApi }>

The track pack: a node-and-edge network of curves, with walkers that travel it and branch by weight. Pure maths, so it needs no backend and runs headless.

Classes

Track

class Track

A network of authored path segments that enemies follow. Build it once, then spawn() walkers and advance them each frame. Throws when an edge names an unknown node.

const track = new Track({
  nodes: { in: [0, 300], fork: [400, 300], top: [800, 120], bot: [800, 480], out: [1200, 300] },
  edges: [
    { from: 'in', to: 'fork' },
    { from: 'fork', to: 'top', via: [{ quad: [[600, 120], [800, 120]] }] },   // branch A
    { from: 'fork', to: 'bot', via: [{ quad: [[600, 480], [800, 480]] }] },   // branch B
    { from: 'top', to: 'out' }, { from: 'bot', to: 'out' },                    // merge
  ],
});
const w = track.spawn(140);                    // 140 units/s
// per frame: w.advance(dt); sprite.x = w.x; sprite.y = w.y;  if (w.done) leak();
  • readonly edges: TrackEdge[] — All baked edges.
  • readonly spawns: string[] — Spawn node names.
  • readonly goals: string[] — Goal node names.
  • constructor(data: TrackData, options: TrackOptions = {})
  • nodeAt(name: string): Vec2 | null — The world position of a named node, or null if unknown.
  • get length(): number — The total length of the track (sum of all edges).
  • spawn(speed: number, opts: TrackSpawnOptions = {}): TrackWalker — Create a follower. It enters at from (or a random spawn node) and picks a branch at each junction with choose, else by the edge weights drawn from rng (default the track's stream). speed is world units per second; startDist staggers a stream by starting it that far along. Throws when the track has no spawn node.
  • _pickEdge(node: TrackNode, rng: () => number, walker?: TrackWalker, choose?: TrackChooser): TrackEdge | null
  • _toGoal(name: string): number
  • _node(name: string): TrackNode | undefined
  • distanceToTrack(x: number, y: number): number — Shortest distance from world point (x, y) to the track centre-line — for validating tower placement ("must be off the path by margin") and for range-of-track checks. Returns Infinity for an empty track.

TrackWalker

class TrackWalker

A single follower walking a Track. Construct via track.spawn(); call advance(dt) each frame and read x / y / angle / done. snapshot() and restore() save and rewind it; save its branch stream with it.

  • x: number — Current world X.
  • y: number — Current world Y.
  • angle: number — Heading in radians (direction of travel).
  • done: boolean — true once a goal node is reached (the enemy "leaked").
  • travelled: number — Total distance travelled along the track so far.
  • speed: number — Travel speed in world units per second — mutate live (slows, boosts).
  • constructor(private track: Track, start: TrackNode, speed: number, private rng: () => number, private choose?: TrackChooser) — Enters at start, choosing its first edge there. Use track.spawn() rather than calling this.
  • get edge(): TrackEdge | null — The edge it is on, or null once done. Its from and to name the nodes either side.
  • get along(): number — How far along the current edge it is, in world units (0 once done).
  • snapshot(): TrackWalkerState — Its state as plain data, for a save or a rewind. Save the branch stream (rng.snapshot()) with it.
  • restore(state: TrackWalkerState): void — Puts it back where a snapshot of a walker on this track was taken. Throws on an edge index this track does not have.
  • get distanceToGoal(): number — Remaining along-track distance to the nearest goal — SMALLER means FURTHER along (use it to target the "first" enemy: the one closest to leaking).
  • advance(dt: number): boolean — Advance by dt seconds, carrying across junctions and choosing branches. Returns done.

Types

TrackApi

type TrackApi = typeof kit

game.track — every function and class this pack carries, as one namespace.

TrackChooser

type TrackChooser = (edges: readonly TrackEdge[], walker: TrackWalker) => TrackEdge | null | undefined

A branch policy: given the edges leaving the junction a walker has reached (more than one), return the one to take, or nothing to fall back to the weighted draw. A switchable junction, a player's choice or "always the shortest route" are each one of these.

TrackData

interface TrackData

The plain-data track notation — JSON-friendly, author it inline.

  • nodes: Record<string, PathPoint> — Named node positions, name: [x, y].
  • edges: TrackEdgeData[] — The directed edges connecting them.
  • spawns?: string[] — Entry node names (default: every node with no incoming edge).
  • goals?: string[] — Exit node names — reaching one ends the run / "leaks" (default: every node with no outgoing edge).

TrackEdge

interface TrackEdge

A baked edge: its Path, endpoints, length and a sampled polyline. The graph is fixed once built; only weight may be changed.

  • from: string — Node name this edge leaves.
  • to: string — Node name this edge arrives at.
  • path: Path — The arc-length curve for this edge.
  • length: number — Geometric length in world units.
  • weight: number — Branch weight at the from node. Mutable: change it to bias the draw at that junction from now on.
  • points: Vec2[] — A sampled polyline of the edge, for drawing + nearest-point queries.

TrackEdgeData

interface TrackEdgeData

One directed edge of the track in the data notation.

  • from: string — Name of the node this edge leaves.
  • to: string — Name of the node this edge arrives at.
  • via?: PathSegmentData[] — Optional curve geometry (any PathSegmentData chain, continuing from from's point). Omit for a straight line between the two nodes.
  • weight?: number — Relative likelihood of taking this edge at a branching node (default 1).

TrackOptions

interface TrackOptions extends PathOptions

Options for a Track: the parse-time transform of PathOptions, plus the default branch stream.

  • rng?: TrackRng — The stream walkers draw branch choices (and an unnamed spawn) from when spawn is given none. Default createRng(0), so a track is deterministic on its own; pass game.rng.fork('track') so the game's seed decides it. Shared by every walker that has no stream of its own.

TrackRng

type TrackRng = Rng | (() => number)

A random source for branch choices: a kernel Rng (such as game.rng.fork('track')), or a function returning 0..1.

TrackSpawnOptions

interface TrackSpawnOptions

Options for Track.spawn.

  • from?: string — The spawn node to enter at. Default (or when the name is not a spawn): one picked at random from spawns.
  • rng?: TrackRng — This walker's own branch stream. Default the track's (TrackOptions.rng).
  • startDist?: number — Start this far along the route, in world units, as if it had already travelled it (staggers a stream). Default 0.
  • choose?: TrackChooser — A branch policy consulted at every junction before the weighted draw. Default none: weights only.

TrackWalkerState

interface TrackWalkerState

A walker's saved state, from TrackWalker.snapshot: plain data, JSON-safe.

  • edge: number — Index of the current edge in track.edges, or -1 once done.
  • along: number — Distance along the current edge, world units.
  • travelled: number — Total distance travelled, world units.
  • speed: number — Speed, world units per second.
  • done: boolean — Whether it has reached a goal.
  • x: number — World X at the save.
  • y: number — World Y at the save.
  • angle: number — Heading at the save, radians.