Phaser AE

Scrolling stages

Pro packv3.0.0-32DArcade

The stage that comes at the player, keyed by distance: named sections with checkpoints, an obstacle plan that fires as the distance is passed, a floor and a ceiling to fly between, and a generator that builds caverns for them. A level becomes a table you author, and a checkpoint is a number rather than a branch in your code.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 19, 2026

Scrolling stages: 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

route

function route(): Pack<{ route: RouteApi }>

The route pack: the playfields that come at you, keyed by distance. Pure logic, so it runs headless.

Helpers

createEnvelope

function createEnvelope(floor: readonly XY[], ceiling?: readonly XY[] | null): TerrainEnvelope

Creates a TerrainEnvelope from a floor polyline and an optional ceiling polyline.

createRoute

function createRoute(o: RouteOptions): RouteStage

Creates a RouteStage from the given options.

createSchedule

function createSchedule(entries: ScheduleEntry[]): ObstacleSchedule

Creates an ObstacleSchedule from a list of entries.

generateCavern

function generateCavern(o: CavernOptions): Cavern

Scramble's six stages as one polyline pair: sections declare their floor and ceiling bands and the walk fills in the terrain, so "open sky", "tight cavern" and "the maze" are three table rows rather than three generators.

An open-sky section puts the ceiling far above the screen, so the same collision test runs everywhere and there is no per-section special case.

heightAt

function heightAt(pts: readonly XY[], x: number): number

Linear interpolation along a heightfield, flat outside its ends.

Classes

ObstacleSchedule

class ObstacleSchedule

Hazards placed by DISTANCE, resolved in order as the route passes them. The schedule is data, so a stage's layout is a table rather than a pile of if (distance > 3200 && !spawnedTower) flags.

  • constructor(entries: ScheduleEntry[])
  • get all(): readonly Due[] — Everything scheduled — for a stage map or a level preview.
  • upTo(distance: number): Due[] — Everything from the last call up to distance, in order.
  • between(from: number, to: number): Due[] — Placements inside a window — for spawning ahead of the camera.
  • seek(distance: number): void — Rewind the cursor (a crash that resumes at a checkpoint).
  • reset(): void — Move the cursor back to the start, so upTo() replays from distance 0.
  • add(entry: ScheduleEntry): void — Add more (a per-loop mutator dropping extra traffic in). Placements the schedule has already played past do not fire; the rest fire when reached, and nothing that has fired fires again.

RouteStage

class RouteStage

A route that only advances. advance(speed * dt) from the player's speed for a racer, or a constant for a scroller that pulls you along whether you like it or not — the difference between Pole Position and Scramble is that one line.

  • distance: number — How far along the route we are, in world units. Starts at 0.
  • lap: number — Completed laps, when loop is on.
  • finished: boolean — True once the end is reached and loop is off.
  • readonly sections: RouteSection[] — The resolved sections, each with its from and to distances.
  • readonly length: number — The total length of the route: the sum of the section lengths.
  • readonly checkpoints: number[] — The checkpoint distances, sorted ascending.
  • constructor(o: RouteOptions)
  • get section(): RouteSection — The section the route is in.
  • get sectionName(): string — The name of the current section, or an empty string before the start.
  • get progress(): number — 0..1 through the whole route.
  • get sectionProgress(): number — 0..1 through the current section — what a stage-progress bar draws.
  • get remaining(): number — The distance left to the end of the route.
  • advance(by: number): void — Move along. Fires section, checkpoint and finish events in order; on a looping route, a lap ends and the next one's sections and checkpoints fire for the distance carried into it.
  • indexAt(d: number): number — Which section a distance falls in.
  • sectionAt(d: number): RouteSection — The section a distance falls in.
  • reset(): void — Back to the start (a new life, or a new race).
  • rewindToCheckpoint(): number — Restart at the last checkpoint passed — the crash-and-resume rule.

TerrainEnvelope

class TerrainEnvelope

A floor polyline and an optional ceiling polyline, with the queries a side-scroller actually asks: how high is the ground here, how low is the roof, how big is the gap, and did I just fly into either.

Both are heightfields — one y per x, x increasing. That is a restriction the genre already lives under (you cannot fly under an overhang in Scramble) and it is what makes the queries a binary search instead of a physics pass.

  • readonly floor: readonly XY[] — The floor polyline, one y per x, x increasing.
  • readonly ceiling: readonly XY[] | null — The ceiling polyline, or null for open sky.
  • constructor(floor: readonly XY[], ceiling?: readonly XY[] | null)
  • get from(): number — The x where the floor starts.
  • get to(): number — The x where the floor ends.
  • floorAt(x: number): number — The y of the floor at x — the ground height.
  • ceilAt(x: number): number — -Infinity where there is no roof — open sky.
  • gapAt(x: number): { top: number; bottom: number; height: number } — The clear space at x: top is the roof, bottom the ground.
  • hits(x: number, y: number, r = 0): Contact — Where a circle at (x, y) is touching, or null.
  • slice(x0: number, x1: number, which: 'floor' | 'ceiling' = 'floor'): XY[] — Slice for drawing: the points between two x values, with the ends interpolated so the polyline does not jitter as it scrolls.
  • roofRuns(x0: number, x1: number, minY = -100): XY[][] — The ceiling between two x values, split into the runs where there actually IS a roof — the stretches at or below minY in screen terms. Open sky is a roof parked far above the top of the screen, which is what keeps the collision test unconditional. Drawing that literally throws a diagonal across the screen wherever a cavern opens out, so anything that DRAWS the ceiling wants the runs rather than the raw slice.
  • crater(x: number, radius: number, depth: number): void — Blow a crater in the floor — the bomb that flattens a fuel tank.

Types

Cavern

interface Cavern

The result of generateCavern(): the polylines, the section spans and a ready envelope.

  • floor: XY[] — The floor polyline, x increasing.
  • ceiling: XY[] — The ceiling polyline; open-sky sections sit far above the screen.
  • sections: { name: string; from: number; to: number }[] — The x span of each section, in order.
  • length: number — The total length of the cavern, in world units.
  • envelope: TerrainEnvelope — A TerrainEnvelope made from the floor and ceiling.

CavernOptions

interface CavernOptions

The options that configure a cavern. Passed to generateCavern().

  • sections: CavernSectionDef[] — The sections that make the cavern, in order.
  • baseY: number — Ground level: floor heights are measured UP from here.
  • topY?: number — Roof level: ceiling heights are measured DOWN from here. Default 0.
  • step?: number — Distance between samples. Default 48.
  • rng?: () => number — The random source, 0..1. Default a stream seeded from seed, so the same options make the same cavern.
  • seed?: number — The seed of the default random stream. Default 1.

CavernSectionDef

interface CavernSectionDef

One section of a cavern: its length and its floor and ceiling bands.

  • name: string — The name of the section, kept in the result's section list.
  • length: number — How long this section runs, in world units.
  • floor?: [number, number] — Ground height band above the base, [min, max]. Default [40, 120].
  • ceiling?: [number, number] | null — Roof band below the top, [min, max]. Omit for open sky.
  • rough?: number — 0..1 — how fast the bands are allowed to change. Default 0.35.

Contact

type Contact = 'floor' | 'ceiling' | null

What TerrainEnvelope.hits() returns: the surface touched, or null for clear air.

Due

interface Due

One resolved placement from an ObstacleSchedule — what to spawn and where.

  • type: string — The type of thing to spawn, copied from the entry.
  • at: number — The distance it was scheduled at.
  • data: unknown — The entry's data, or null when the entry had none.
  • entry: number — Index into the original entry list.

RouteApi

type RouteApi = typeof kit

game.route — staged routes, obstacle schedules, terrain envelopes and caverns, as one namespace.

RouteOptions

interface RouteOptions

The options that configure a RouteStage. Passed to createRoute().

  • sections: RouteSectionDef[] — The sections that make the route, in order.
  • checkpoints?: number[] — Distances at which onCheckpoint fires.
  • loop?: boolean — Start again from 0 at the end, lap incremented. Default false.
  • onSection?(section: RouteSection, route: RouteStage): void — Fires when the route enters a section.
  • onCheckpoint?(distance: number, index: number, route: RouteStage): void — Fires when the route passes a checkpoint distance.
  • onFinish?(route: RouteStage): void — Fires once when the route reaches its end and loop is off.

RouteSection

interface RouteSection extends RouteSectionDef

A resolved section of a built route: the definition plus its place on the route.

  • index: number — The position of this section in the route, from 0.
  • from: number — Distance this section starts at.
  • to: number — Distance the next one starts at.

RouteSectionDef

interface RouteSectionDef

One section of a route, as the game declares it in RouteOptions.sections.

  • name: string — The name of the section — what sectionName reports.
  • length: number — How long this section runs.
  • data?: unknown — Extra data for the game — the route does not read it.

ScheduleEntry

interface ScheduleEntry

One row of an obstacle schedule: a single placement at a distance, or a repeat.

  • type: string — The type of thing to spawn — the game decides what the string means.
  • at?: number — A single placement.
  • every?: number — …or a repeat: every every units from from (default 0) to to.
  • from?: number — The distance a repeat starts at. Default 0.
  • to?: number — The distance a repeat stops at.
  • count?: number — Cap on a repeat's placements.
  • data?: unknown — Extra data copied to each Due made from this entry.

XY

interface XY

A 2D point in world units. Used for the polylines in this pack.

  • x: number — The x position, in world units.
  • y: number — The y position, in world units.