Phaser AE

3D Trail Ribbons

Pro packv3.0.0-43DLook

Ribbons behind moving things in a 3D world, and deliberately not a strip of fading quads. A trail follows an object and reads as fire, plasma, a comet or a light-cycle wall, because its shader does the things a real trail does.

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

3D Trail Ribbons: 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

trails3d

function trails3d(): Pack<{ trails: Trails3dApi }>

The trails-3d pack: shader-drawn ribbons with turbulence, erosion, a hot core, fibre, edge hardness, crackle and path-shed sparks. Requires a 3D world and render-3d, whose depth pass and camera it joins; sparks need particles-3d too.

Functions

crTrail

function crTrail(a: TrailPoint, b: TrailPoint, c: TrailPoint, d: TrailPoint, t: number): TrailPoint

Catmull-Rom through four control points at t in 0..1 between b and c, interpolating the fade too, so k stays smooth along the curve.

The uniform span is the kernel's catmullRom1, whose t³ coefficient vanishes exactly for collinear, evenly spaced points; a sign slip there would make the ribbon bowtie once per cell.

curveAt

function curveAt(pts: readonly TrailPoint[], s: number): TrailPoint

Evaluate the smoothed curve at s in 0..1, 0 at the head, over an array of control points.

registerTrail3d

function registerTrail3d(k: { registry: { add(kind: string, def: TrailDef): void } }, def: TrailDef): void

A trail-* pack's attach calls this: register a preset under the registry kind 'trail3d'.

resampleTrail

function resampleTrail( points: ReadonlyArray<{ x: number; y: number; z: number; age: number }>, n: number, life: number, ): TrailPoint[]

Resample a trail's raw point history — oldest first — into n control points running HEAD (newest) to TAIL, each carrying a fade k of 1 at the head down to 0 at the end of life, spaced UNIFORMLY IN ARC LENGTH.

Arc-uniform spacing is what makes the ribbon's parameter proportional to DISTANCE along the path. That gives stable curves at any speed, and it lets the shader reconstruct absolute travelled distance linearly — which is the anchor every noise pattern on the ribbon is keyed to.

trailLength

function trailLength(points: ReadonlyArray<{ x: number; y: number; z: number }>): number

The arc length of a raw point history.

Constants

TRAIL_WGSL_FOR_TEST

const TRAIL_WGSL_FOR_TEST: "\nstruct Cam {\n viewProj : mat4x4<f32>,\n eye : vec4<f32>,\n sunDir : vec4<f32>, // xyz = direction the light travels, w = intensity\n sunCol : vec4<f32>,\n skyCol : vec4<f32>, // w = ambient strength\n groundCol: vec4<f32>,\n fogCol : vec4<f32>,\n params : vec4<f32>, // shininess, spe... …

Trail shader source exported so its rendering contract can be regression tested.

Types

Trail

interface Trail

A live trail. Every style field is mutable.

  • point(x: number, y: number, z: number): void — Feed a point by hand when nothing is followed.
  • jump(x: number, y: number, z: number): void — Move without laying a point — a teleport should not draw a streak across the world.
  • stop(): void — Stop recording; the tail runs out and it removes itself.
  • start(): void — Record again.
  • clear(): void — Forget the path at once.
  • readonly length: number — Points held.
  • readonly alive: boolean — Still alive.
  • kill(): void — Remove it now.
  • width: number — Ribbon width at the head.
  • life: number — Seconds a point survives.
  • alpha: number — Alpha at the head.
  • turbulence: number — Sideways flutter at the tail.
  • erode: number — Erosion toward the tail.
  • core: number — White-hot centre-line.
  • fiber: number — Fibre depth.
  • hard: number — Edge hardness.
  • crackle: number — Electric glint.

TrailDef

interface TrailDef

A registered trail preset — what a pack ships.

  • name: string — The name trails.preset() is called with.
  • description?: string — One line for the planner.
  • spec: TrailSpec — The spec it starts from.

TrailPoint

interface TrailPoint

One control point of a smoothed trail: a position and its fade, 1 at the head and 0 at the tail.

  • x: number — The X position, in world units.
  • y: number — The Y position, in world units.
  • z: number — The Z position, in world units.
  • k: number — The fade at this point, 0 to 1: 1 at the head, 0 at the tail.

Trails3dApi

interface Trails3dApi

game.trails.

  • add(spec?: TrailSpec): Trail — Start a trail.
  • preset(name: string, overrides?: Partial<TrailSpec>): Trail — Start a registered preset, overriding any of it.
  • readonly available: ReadonlyArray<string> — Every registered preset name.
  • readonly defs: ReadonlyArray<TrailDef> — The registered definitions, for a menu.
  • readonly all: ReadonlyArray<Trail> — Every live trail.

TrailSparks

interface TrailSparks

Particles shed along a trail's laid path, by a particles-3d emitter (compose particles-3d to use them).

  • per?: number — Particles shed per world unit of head travel. Default 0.25.
  • speed?: number — Jitter speed, world units/s: each axis of a spark's launch velocity is drawn from −speed to speed. Default 3.
  • life?: number — Spark lifetime, seconds; each lives 0.6 to 1.4 times it. Default 0.45.
  • size?: number — Spark size, world units, shrinking to nothing over its life. Default 0.2.
  • colors?: ReadonlyArray<string> — Palette picked per spark. Default the trail's own colours.
  • add?: boolean — Additive blend. Default the trail's own.
  • gravity?: number — Downward acceleration. Default 0.
  • drag?: number — Velocity damping per second — sparks skitter to rest. Default 1.5.
  • twinkle?: number — Brightness flicker 0..1, about five times a second; electric crackle reads at 0.6. Default 0.6.
  • facing?: 'velocity' | 'camera' — How a spark faces: 'velocity' (the default) runs its dash along the way it flies, so a shower of them reads as streaks thrown off the trail; 'camera' faces the camera, each at its own turn, for a round glint or a star.
  • stretch?: number — With 'velocity', extra length per unit of speed, world units: a fast spark is a long streak. Default 0.04.
  • frame?: string — The atlas frame. Default particle:spark, a streak; particle:soft or particle:star for a glint.
  • lit?: boolean — Take the scene's lights, as a particles-3d emitter's lit: for dull grit, smoke flecks or dark ash that should darken in shadow. Default false: a spark is light, drawn exactly its colour.
  • max?: number — Most sparks alive at once, the emitter's pool: past it no more are shed until some die. One step that would shed more than a quarter of it is taken as a jump and sheds none. Default 1024.

TrailSpec

interface TrailSpec

Everything a trail can be told. Every field is live on the handle.

  • follow?: Handle — Follow this object each step; the trail releases itself when the target dies.
  • offset?: { x?: number; y?: number; z?: number } — Feed offset from the followed object — an exhaust point rather than a centre.
  • x?: number — Start x when nothing is followed.
  • y?: number — Start y.
  • z?: number — Start z.
  • width?: number — Ribbon width at the head, world units. Default 0.8.
  • life?: number — Seconds a recorded point survives. Default 0.5.
  • colors?: ReadonlyArray<string> — Head→tail colour stops, 1–8. Default white.
  • add?: boolean — Additive blend — fire, magic, light. Default true.
  • taper?: boolean — Width tapers to nothing at the tail. Default true.
  • alpha?: number — Alpha at the head. Default 0.8.
  • spacing?: number — Minimum distance between committed history points; the live head still follows every movement. Default width × 0.25.
  • points?: number — Most points held. Default 96.
  • turbulence?: number — Noise-driven sideways flutter at the tail, world units. Default 0.35.
  • erode?: number — Noise erosion toward the tail, 0..1 — the ribbon shreds into wisps instead of fading as a solid strip. Default 0.55.
  • core?: number — White-hot centre-line intensity, 0..1; best with add. Default 0.5.
  • fiber?: number — Fibre-texture depth, 0..1 — 0 is a clean ribbon of light (Tron), 1 fully streaked (fire, smoke). Default 1.
  • hard?: number — Edge hardness, 0..1 — 0 is a soft gaussian falloff (gas), 1 a crisp flat-topped band (neon). Default 0.
  • crackle?: number — Electric glint crawling INSIDE the ribbon, 0..1 — lightning, plasma. Independent of sparks; they compose. Default 0.
  • facing?: 'view' | 'up' — 'view' faces the camera (comets, magic); 'up' is a vertical base-anchored wall — the Tron light-cycle trail. Default 'view'.
  • sparks?: TrailSparks — Shed particles along the laid path.
  • layer?: LayerName — The layer.