Phaser AE

Drawing and gesture recognition

Pro packv3.0.0-32DArcade

A drawing pad that turns pointer movement into clean strokes, a recogniser that names a hand-drawn shape without any training, and playback that redraws a recording. Enough to cast a spell with a circle, sign a name or let a title write itself on screen.

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

Released October 14, 2026 · updated October 20, 2026

Drawing and gesture recognition: 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

sketch

function sketch(): Pack<{ sketch: SketchApi }>

The sketch pack. Pure logic, so it needs no backend and runs headless.

Helpers

builtinGestures

function builtinGestures(): Record<string, XY[]>

A starter set, generated rather than typed out as point lists: circle, triangle, square, line, vee, caret, zigzag, arrow and spiral. Feed them straight to learn, or teach your own instead.

createPlayback

function createPlayback(strokes: Stroke[], o: PlaybackOptions = {}): Playback

Creates a Playback that replays the given strokes.

createRecognizer

function createRecognizer(): Recognizer

Creates an empty Recognizer. Teach it shapes with learn or learnBuiltins.

createSketchPad

function createSketchPad(o: SketchPadOptions = {}): SketchPad

Creates a SketchPad that turns pointer samples into strokes.

learnBuiltins

function learnBuiltins(rec: Recognizer): Recognizer

Every built-in taught in one call.

simplify

function simplify(points: readonly XY[], tolerance = 2): XY[]

Ramer–Douglas–Peucker: drop every point that is within tolerance of the line its neighbours already describe.

This is the one that matters. A pointer stroke is hundreds of points a couple of pixels apart; a vector outline wants a dozen, a Box2D hull wants at most eight, and the beam's dwell only reads as a corner if there IS a corner.

strokeBounds

function strokeBounds(points: readonly XY[]): Bounds

The axis-aligned bounds of a point set. Returns all-zero bounds for an empty list.

Functions

centroid

function centroid(points: readonly XY[]): XY

The mean of the points. Returns (0, 0) for an empty list.

isClosed

function isClosed(points: readonly XY[], tol = 24): boolean

Did the stroke come back to where it started?

resample

function resample(points: readonly XY[], n = 64): XY[]

n points spaced evenly by ARC LENGTH along the stroke. The first step of gesture recognition, and how you make two strokes comparable at all.

rotatePoints

function rotatePoints(points: readonly XY[], angle: number, about?: XY): XY[]

Rotate a point set about a centre.

signedArea

function signedArea(points: readonly XY[]): number

Signed area — positive is clockwise on a y-down screen.

smooth

function smooth(points: readonly XY[], iterations = 1, closed = false): XY[]

Chaikin corner cutting: replace each corner with two points a quarter and three quarters along, once per iteration. Rounds a stroke off without adding the wobble a spline through noisy samples would.

Simplify FIRST, then smooth. Smoothing a raw stroke keeps every tremor and simply makes it a smooth tremor.

strokeLength

function strokeLength(points: readonly XY[], closed = false): number

Total length along a polyline.

Classes

Playback

class Playback

Replays strokes as they were drawn — the AnimAction half of the light-pen cartridges, and the reason a title card can WRITE itself on screen.

visible is a list of partial polylines, so the drawing appears at the pace it was made (or at a constant rate, which is the better look for a logo).

  • strokes: Stroke[] — The strokes to replay, in order.
  • speed: number — Multiplier on the recorded timings. Default 1.
  • rate: number — Constant draw rate, in length each second. 0 uses the recorded timings.
  • gap: number — The pause between strokes, in seconds. Default 0.25.
  • loop: boolean — True restarts the replay from the top when it finishes.
  • time: number — Seconds into the replay.
  • done: boolean — True when the replay reached the end (never true while loop is set).
  • constructor(strokes: Stroke[], o: PlaybackOptions = {})
  • get duration(): number — How long the whole replay takes.
  • update(dt: number): void — Advances the replay clock. Call once per frame with the frame time in seconds.
  • reset(): void — Rewinds the replay to the start.
  • get visible(): XY[][] — The polylines to draw this frame — completed strokes plus a partial one.
  • get head(): XY | null — Where the beam is right now, or null between strokes.

Recognizer

class Recognizer

Wobbler's-tolerant single-stroke shape matching: resample to a fixed count, rotate so the first point sits at a known angle from the centre, scale to a reference box, centre on the origin, then compare point-for-point under a golden-section search over rotation.

Size, position and (by default) rotation do not matter, which is what makes a hand-drawn circle a circle.

  • readonly gestures: Gesture[] — Every learned template. Several entries can share one name.
  • angleRange: number — Search ±this many radians for the best alignment. Default 45°.
  • learn(name: string, points: readonly XY[]): Gesture — Teach it a shape. Several samples under one name all count.
  • forget(name: string): void — Removes every learned gesture with this name.
  • match(points: readonly XY[], minScore = 0): Match | null — Best match, or null when nothing has been learned.
  • rank(points: readonly XY[]): Match[] — Every gesture scored, best first — for a debug readout.

SketchPad

class SketchPad

Pointer in, strokes out.

const ptr = game.input.pointer;
if (ptr?.down) pad.move(ptr.x, ptr.y, game.time);   // begin() is implicit
else pad.end();
  • readonly strokes: Stroke[] — Every finished stroke, oldest first.
  • live: XY[] — The stroke being drawn right now, as raw samples. Empty when idle.
  • minDist: number — Samples nearer than this to the last one are dropped, in pixels. Default 4.
  • simplifyTol: number — RDP tolerance applied on finish. 0 keeps every sample. Default 2.
  • smoothPasses: number — Chaikin smoothing iterations applied after simplification. Default 1.
  • closeDist: number — Ends within this distance make a closed stroke. 0 never closes. Default 24.
  • minLength: number — Strokes shorter than this are dropped on finish. Default 12.
  • snapClosed: boolean — True snaps a closed stroke's last point onto its first. Default true.
  • onStroke: ((s: Stroke) => void) | null — Called with each finished stroke, after it is added to strokes.
  • constructor(o: SketchPadOptions = {})
  • get isDrawing(): boolean — True while a stroke is in progress.
  • begin(x: number, y: number, time = 0): void — Starts a new stroke at (x, y). move calls this for you on the first sample.
  • move(x: number, y: number, time = 0): void — A sample. Starts the stroke if there is not one, so a game can simply call this every frame the pointer is down and end() every frame it is not.
  • end(): Stroke | null — Finish. Returns the stroke, or null if it was too short or absent.
  • cancel(): void — Throw away the stroke in progress without finishing it.
  • undo(): Stroke | undefined — Removes and returns the most recent finished stroke.
  • clear(): void — Removes every finished stroke and cancels the one in progress.
  • reprocess(): void — Re-run simplify/smooth over every finished stroke with the current settings.

Types

Bounds

interface Bounds

An axis-aligned bounding box, as returned by strokeBounds.

  • min: XY — The minimum corner (left, top).
  • max: XY — The maximum corner (right, bottom).
  • size: XY — The width and height.
  • center: XY — The middle of the box.

Gesture

interface Gesture

A learned gesture template held by the Recognizer.

  • name: string — The gesture name that match reports.
  • points: XY[] — Normalised: resampled, rotated to its indicative angle, scaled, centred.

Match

interface Match

The result of a gesture match: the winning name, its score and its rotation.

  • name: string — The name of the matched gesture.
  • score: number — 0..1. Above about 0.8 is a confident match.
  • angle: number — How far the drawn stroke was turned from the template, radians.

PlaybackOptions

interface PlaybackOptions

Options for a Playback: replay speed or a constant rate, the gap between strokes, and looping.

  • speed?: number — Multiplier on the recorded timings. Default 1.
  • rate?: number — Ignore the recording and lay every stroke down at this length per second.
  • gap?: number — Seconds of pause between strokes. Default 0.25.
  • loop?: boolean — Restart from the top when the replay finishes. Default false.

SketchApi

type SketchApi = typeof kit

game.sketch — every function and type this pack carries, as one namespace.

SketchPadOptions

interface SketchPadOptions

Options for a SketchPad: sample spacing, simplify/smooth strength and close detection.

  • minDist?: number — Samples nearer than this to the last one are dropped. Default 4.
  • simplify?: number — RDP tolerance applied on finish. 0 keeps every sample. Default 2.
  • smooth?: number — Chaikin iterations applied after simplification. Default 1.
  • closeDist?: number — Ends within this become a CLOSED stroke. 0 never closes. Default 24.
  • minLength?: number — Drop strokes shorter than this on finish. Default 12.
  • snapClosed?: boolean — Snap a closed stroke's last point onto its first. Default true.

Stroke

interface Stroke

One finished stroke from a SketchPad, with its raw samples and derived geometry.

  • points: XY[] — The finished polyline: simplified, then smoothed. What you draw and bind.
  • raw: XY[] — Every sample as captured, before simplification.
  • times: number[] — Seconds since the stroke began, one per raw point — for replay.
  • closed: boolean — Ends met within closeDist.
  • length: number — Total length of the finished polyline.
  • bounds: Bounds — The axis-aligned bounds of the finished polyline.
  • duration: number — How long the stroke took to draw, in seconds.

XY

interface XY

A 2D point: x and y in the caller's units (pixels from the pointer, or world units).

  • x: number — The X position, in the caller's units.
  • y: number — The Y position, in the caller's units.