Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Pixel art as code

pxl is a small JavaScript language for pixel art: a .px.js file declares sprites and anims as grids, shapes and patches, and pxl paints them. A game imports a pxl file the way it imports any module, and the engine’s Vite plugin paints its sheet as the page loads, so no PNG or JSON enters the game’s folder. A save to the file redraws every sprite of its sheet in place, with no reload, and each sprite carries on from its place in its clip. Read Sprites first: an imported sheet is a sprite sheet like any other.

Keep the file under src/sprites/, such as src/sprites/pickups.px.js, and pass its import to defineSpriteSheet with the pixels per metre your camera draws at:

packages/engine/test/outside/pxl/sprites.ts
import { defineSpriteSheet } from "@spawnite/engine/2d";
import pickups from "./pickups.px.js";
// The coin and the heart pickups.px.js draws, at 32 pixels a metre: a
// Sprite's clip="coin" turns the coin, and clip="heart" holds the heart.
export const pickupsSheet = defineSpriteSheet({
...pickups,
pixelsPerMetre: 32,
});

The import gives the sheet’s image, pxl’s JSON as data, and one clip for each sprite and anim the file names:

  • An anim plays its frames with their own durations, and holds its last frame when the anim says loop: false.
  • A static sprite is a clip of its one frame, which shows for 1 second when playClip plays it once.

A clip of your own replaces the file’s of the same name: clips: { ...pickups.clips, coin: { loop: ClipLoopMode.Once } }.

A file that declares one sheet() imports as that sheet, packed as pxl’s own export packs it. A file with no sheet() imports as every sprite and anim it names, in the order it names them, one pixel apart. A name that starts with _ is a part that only stamps, and a scene() stays out of the sheet. A file that declares two sheets is refused: give each sheet its own file, and share their parts through include() of a file that declares none.

The plugin writes the import’s types beside the file, pickups.px.d.ts, so clip="coins" on a Sprite is a type error. Commit the file with the art. The dev server writes it as it starts and again on each save, and spawnite pxl types writes it without one. A build writes none, and warns of a file whose types are out of date. A created game’s typecheck and check scripts run that command first. spawnite pxl types --check writes nothing and fails when a file’s types are out of date.

Name a pxl file .px.js, never .px.ts: TypeScript reads a .px.ts file in place of the types its import is given.

A mistake in the file fails its import at the file, line and column pxl names, such as an unknown colour key or an anim with too many frames. A file that takes over 10 seconds to run and draw, such as one with a loop that never ends, fails its import too. On a dev server the page shows the error over the game and keeps drawing the last sheet that built. A build stops.

A sprite named like another’s frame is refused too, such as a sprite coin_0 beside an anim coin, because both would name the same frame of the sheet.

pxl is MIT licensed, and the engine runs a copy of it. What your file paints is your game’s art. A sheet the asset library publishes from a pxl file carries the platform license.