Phaser AE

Projected Wireframe

Free packv3.0.0-32DVector

World-space points, through a camera, to 2D line segments: the trick the first-person vector games were built on. There is no depth buffer and no 3D world, only maths, and the 2D line layer draws the result.

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

Released October 14, 2026 · updated October 20, 2026

Projected Wireframe: 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

vectorWire

function vectorWire(): Pack<{ vecWire: VecWireApi }>

The vector-wire pack. Pure maths, so it needs no backend and runs headless.

Helpers

focalLength

function focalLength(view: WireView, fov = 1.05): number

Pixels per world unit at one unit of depth. Divide by an edge's za/zb for the scale at that end — the whole of perspective in one number.

const f = vecWire.focalLength(view);
layer.seg(e.a.x, e.a.y, e.b.x, e.b.y, { width: Math.max(0.4, 3 * f / e.za / 40) });

horizon

function horizon(cam: WireCamera, view: WireView): [{ x: number; y: number }, { x: number; y: number }]

The ground line — where a flat plane at infinite distance lands on screen.

It depends only on pitch (yaw slides along it, and there is no roll), so it is a horizontal line at h/2 + f·tan(pitch). Draw it first and everything else sits in a world.

placeModel

function placeModel(model: WireModel, x: number, y: number, z: number, yaw = 0): WireModel

Move and turn a model without touching the original — the way you place many copies of one shape, or drive a tank around. yaw turns it about its own vertical axis first, then it is offset to (x, y, z).

y positions the model's OWN ORIGIN, wherever the model happens to keep it. If what you want is for the thing to stand on the floor, reach for placeModelOnGround and give it no y at all.

placeModelOnGround

function placeModelOnGround( model: WireModel, x: number, z: number, yaw = 0, groundY = 0, ): WireModel

Stand a model on the ground at (x, z) — the placement a first-person wireframe scene actually wants.

const block = vecWire.placeModelOnGround(vecWire.wireBox(120, 280, 90), 900, -1400);
const tank  = vecWire.placeModelOnGround(TANK, tx, tz, heading);

No height to work out and none to get wrong. Models keep their origin wherever suits them — wireBox about its centre, a hand-built pyramid at its base — so "put it at y = 0" means different things for different models and silently buries the ones built about their middle. This asks each model where its own floor is and drops it exactly there, so a box, a pyramid and a multi-part tank all land flush without the caller knowing how any of them were built.

groundY moves the floor; yaw turns the model about its vertical axis, which cannot change how tall it is, so it stays flush at any heading.

project

function project(model: WireModel, cam: WireCamera, view: WireView): WireEdge[]

Project a wireframe model to 2D segments, near-plane clipped.

for (const e of vecWire.project(tank, cam, view)) {
  game.vector.seg(e.a.x, e.a.y, e.b.x, e.b.y, { color: '#41d6ff', width: 3 * f / e.za });
}

An edge straddling the near plane is CUT at it rather than dropped, so geometry doesn't pop out of existence as you walk into it — the one thing a naive projector always gets wrong (divide by a negative z and the edge whips across the screen backwards).

projectPoint

function projectPoint( p: Vec3, cam: WireCamera, view: WireView, ): { x: number; y: number; z: number } | null

Project ONE world point to the screen, or null if it is behind the near plane. Handy for placing 2D things (labels, a targeting box, a sprite) on a 3D position without projecting a whole model.

wireBounds

function wireBounds(model: WireModel): WireBounds

Measure a model: the corners of its axis-aligned box, and the size and centre they imply. For a cheap distance cull, or for scaling something to fit.

To stand a model on the ground use placeModelOnGround, which does the sum for you.

wireBox

function wireBox(w: number, h: number, d: number, cx = 0, cy = 0, cz = 0): WireModel

A wireframe box centred on (cx, cy, cz) — eight points and twelve edges of pure boilerplate, and the staple obstacle of every first-person vector game.

wireGrid

function wireGrid(n: number, step: number, y = 0, cx = 0, cz = 0): WireModel

A ground grid as a wireframe model — n lines each way at step apart, centred on (cx, cz) at height y. The cheapest way to make a first-person scene feel like it has a floor.

Types

Vec3

interface Vec3

A point in 3D world space (x right, y up, z forward: yaw 0 looks along +z).

  • x: number — The X component, in world units. X is right.
  • y: number — The Y component, in world units. Y is up.
  • z: number — The Z component, in world units. Z is forward.

VecWireApi

type VecWireApi = typeof kit

game.vecWire — every function this pack carries, as one namespace.

WireBounds

interface WireBounds

A model's axis-aligned extent, and the size and centre it implies.

  • min: Vec3 — The minimum corner of the axis-aligned box, in world units.
  • max: Vec3 — The maximum corner of the axis-aligned box, in world units.
  • size: Vec3 — The size of the box on each axis, in world units.
  • center: Vec3 — The centre of the box, in world units.

WireCamera

interface WireCamera

Where the camera is and which way it is looking.

  • x: number — The camera X position, in world units.
  • y: number — The camera Y position, in world units.
  • z: number — The camera Z position, in world units.
  • yaw?: number — Heading in radians — 0 looks along +z, increasing turns RIGHT.
  • pitch?: number — Tilt in radians — increasing looks UP.
  • fov?: number — Vertical field of view in radians (default 1.05, about 60°).

WireEdge

interface WireEdge

One projected edge, with the depth at each end so width can taper.

  • a: { x: number; y: number } — The projected 2D start point of the edge, in world units, y down.
  • b: { x: number; y: number } — The projected 2D end point of the edge, in world units, y down.
  • za: number — Distance in front of the camera at each end, AFTER near-plane clipping.
  • zb: number — The distance of end b in front of the camera, after near-plane clipping.

WireModel

interface WireModel

A wireframe model: shared points, plus the index pairs that join them.

  • points: Vec3[] — The shared 3D points of the model, in world units.
  • edges: Array<[number, number]> — The edges, as pairs of indexes into points.

WireView

interface WireView

The screen rect to project into (world units — usually game.view).

  • w: number — The width of the screen rect, in world units.
  • h: number — The height of the screen rect, in world units.
  • near?: number — Nothing closer than this is drawn (default 1). Keeps the divide sane.