Phaser AE

Colour toolkit

Free packv3.0.0-3Logic

Colour as data on `game.color`. Hex strings in, hex strings out, which is exactly the form a tint takes, so a team colour, a damage flash, a rainbow trail or a label that stays readable on its tile is one call rather than hand-rolled arithmetic.

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

Released October 14, 2026 · updated October 20, 2026

Colour toolkit: 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

color

function color(): Pack<{ color: ColorApi }>

The color pack: puts colorApi on game.color. No columns, steps, hooks or options; requires nothing.

Helpers

contrast

function contrast(a: Hex, b: Hex): number

The WCAG contrast ratio between two colours, 1 (identical) to 21 (black on white). Order does not matter. WCAG asks 4.5 for body text and 3 for large or bold text; below 3 nothing is comfortably readable. Alpha is ignored — contrast is a question about what is actually painted.

gradient

function gradient(from: Hex, to: Hex, count: number): Hex[]

count RGB stops inclusive from from to to; one stop returns from.

hsv

function hsv(h: number, s = 1, v = 1): Hex

Hue in degrees (wraps), saturation and value in 0..1 (clamped; default 1) → #rrggbb.

lerp

function lerp(a: Hex, b: Hex, t: number): Hex

RGB blend from a to b at t (clamped to 0..1). Alpha is dropped. Throws on a malformed hex.

luminance

function luminance(hex: Hex): number

WCAG relative luminance, 0 (black) to 1 (white). Not the v of HSV and not the average of the channels: the channels are gamma-decoded and then weighted the way an eye weights them, so pure green reads as far brighter than pure blue. This is the number every legibility question actually turns on.

rainbow

function rainbow(count: number, opts: ColorOptions = {}): Hex[]

count evenly spaced hues that loop seamlessly (the last is one step short of the first).

ramp

function ramp(fromHue: number, toHue: number, count: number, opts: ColorOptions = {}): Hex[]

count hues spread inclusively from fromHue to toHue (degrees).

readableOn

function readableOn(background: Hex, candidates: ReadonlyArray<Hex> = ['#000000', '#ffffff']): Hex

The ink to write on background: whichever candidate contrasts with it most. Defaults to black or white, which is the choice a label on a coloured chip, tile, badge or bar is really making.

Picking this by eye is how a palette ends up with one unreadable tile in it. Pale tints need dark ink and saturated ones need light, the crossover is not where a v > 0.5 guess puts it, and the failure only ever shows on the one value nobody screenshotted.

const ink = game.color.readableOn(tileTint);                          // '#000000' or '#ffffff'
const ink = game.color.readableOn(tileTint, ['#776e65', '#fffaf2']);  // a warm pair, still chosen for you

wheel

function wheel(opts: ColorOptions = {}): Wheel

A Wheel at the given saturation and value (both default 1).

Functions

brighten

function brighten(hex: Hex, amount: number): Hex

Add amount (−1..1) to the value channel, clamped; negative darkens.

lifeRamp

function lifeRamp(ramp: RampName | ReadonlyArray<Hex>): ReadonlyArray<Hex>

A named life ramp's stops, or a custom list passed straight through, so code can take either. Throws on an unknown name.

pack

function pack(hex: Hex): number

Pack to the roster's 0xRRGGBBAA.

parse

function parse(hex: Hex): [number, number, number, number]

#rgb, #rrggbb or #rrggbbaa → [r, g, b, a] in 0..1. A malformed string is a loud error (a silent white hides a typo forever).

rampAt

function rampAt(r: Float32Array, t: number, out: Float32Array, o: number): void

Sample a baked ramp at t into out[o..o+2].

It lives here, beside RAMPS, because BOTH particle systems need it and a ramp sampled two slightly different ways is a 2D effect and its 3D twin that do not match.

rgb

function rgb(r: number, g: number, b: number, a?: number): Hex

Components in 0..1 (clamped) → #rrggbb, or #rrggbbaa when a is given.

sample

function sample(stops: ReadonlyArray<Hex>, t: number): Hex

A multi-stop gradient sampled at t.

shift

function shift(hex: Hex, degrees: number): Hex

Rotate the hue by degrees, keeping saturation and value.

toHsv

function toHsv(hex: Hex): { h: number; s: number; v: number }

A hex colour → { h, s, v } with h in 0..360 and s, v in 0..1. Throws on a malformed hex.

withAlpha

function withAlpha(hex: Hex, alpha: number): Hex

The same colour with an alpha byte from alpha in 0..1 → #rrggbbaa.

Constants

colorApi

const colorApi: ColorApi

The single ColorApi instance; color() attaches this same object.

RAMPS

const RAMPS: Record<RampName, ReadonlyArray<Hex>>

The named life ramps: head is birth, the last stop is death.

SAMPLES

const SAMPLES: 64

Steps in a baked ramp: a table holds SAMPLES + 1 RGB entries, which rampAt reads.

Types

ColorApi

interface ColorApi

What game.color presents: every exported function of this module, by the same name.

  • parse: typeof parse — Hex → [r, g, b, a] in 0..1; throws on a malformed string.
  • rgb: typeof rgb — Components 0..1 → hex.
  • hsv: typeof hsv — Hue, saturation, value → hex.
  • toHsv: typeof toHsv — Hex → { h, s, v }.
  • lerp: typeof lerp — RGB blend at t.
  • shift: typeof shift — Rotate hue by degrees.
  • withAlpha: typeof withAlpha — Attach an alpha byte.
  • brighten: typeof brighten — Add to the value channel.
  • wheel: typeof wheel — A hue wheel at fixed S and V.
  • rainbow: typeof rainbow — Evenly spaced hues that loop.
  • ramp: typeof ramp — Hues inclusive between two angles.
  • gradient: typeof gradient — RGB stops inclusive between two hexes.
  • RAMPS: typeof RAMPS — The eight named life ramps, birth to death.
  • lifeRamp: typeof lifeRamp — A named life ramp's stops, or a custom list passed through.
  • sample: typeof sample — A multi-stop gradient at t.
  • pack: typeof pack — Hex → the roster's 0xRRGGBBAA.
  • luminance: typeof luminance — WCAG relative luminance, 0..1 — gamma-decoded and eye-weighted, not HSV's v.
  • contrast: typeof contrast — The WCAG contrast ratio between two colours, 1..21.
  • readableOn: typeof readableOn — The candidate ink that contrasts with a background most; black or white by default.

ColorOptions

interface ColorOptions

Saturation and value for the palette builders (wheel, rainbow, ramp).

  • saturation?: number — 0..1; default 1 (fully saturated).
  • value?: number — 0..1; default 1 (full brightness).

Hex

type Hex = string

A hex colour string: #rgb, #rgba, #rrggbb or #rrggbbaa. The form a handle's tint takes.

RampName

type RampName = 'fire' | 'spark' | 'ember' | 'smoke' | 'ice' | 'magic' | 'toxic' | 'rainbow'

The eight named LIFE RAMPS — the colours a particle walks from birth to death.

They are here rather than in a particle pack because a ramp is a colour vocabulary, not a particle feature: the same eight name a trail's gradient, a beam, a light that cools, a bar that heats. All but rainbow run BRIGHT to DARK, which is what makes a burst read as energy leaving rather than arriving.

Wheel

interface Wheel

A hue wheel at fixed saturation and value, from wheel().

  • hue(deg: number): Hex — The colour at a hue in degrees (wraps).
  • at(t: number): Hex — The colour at t in 0..1 around the wheel (t · 360 degrees).
  • colors(count: number, t0?: number, t1?: number): Hex[] — count colours spread inclusively from t0 (default 0) to t1 (default 1) around the wheel.