The 2D frame
A 2D game plays in the engine’s 3D world: y up, the ground on x and z, metres as the unit. Three values settle how its art sits in that world, and every 2D piece reads the same three:
- The plane the game plays on,
Plane2D. - Pixels per metre, how many of the art’s pixels make one metre.
- Exact colours, from a nearest-filtered image loader and the
pixellook.
Unity, Unreal and PlayCanvas run 2D the same way, inside their 3D engine: a sprite is a quad in the 3D scene. Camera2D holds the frame and draws it.
The 2D layer is an entry of its own, @spawnite/engine/2d, so a 3D game downloads none of it. Camera2D, the sprites, the tile maps and the parallax layers come from it. The frame they share stays in the root entry, @spawnite/engine: Plane2D, Frame2DTrait, useSpriteTexture and the pixel look’s entry, @spawnite/engine/looks/pixel. A 2D game imports from both:
import { Plane2D } from "@spawnite/engine";import { Camera2D, defineSpriteSheet, Sprite } from "@spawnite/engine/2d";The plane
Section titled “The plane”Plane2D names the two planes a 2D game plays on:
| Plane | Axes | The camera looks | Screen right | Screen up | For |
|---|---|---|---|---|---|
Plane2D.Ground |
x, z | down, along -y | +x | -z | A top-down game |
Plane2D.Side |
x, y | along -z | +x | +y | A side-scroller |
An image’s top row lies toward screen up on either plane. A top-down game keeps the engine’s ground, so its characters walk on the map with the engine’s controller, its NPCs navigate it, and its bodies fall onto it, as in a 3D game.
Pixels per metre
Section titled “Pixels per metre”Pixels per metre is how many of the art’s pixels make one metre of the world: Unity’s and PlayCanvas’s pixels per unit. The engine’s value is 16, the tile most pixel art packs draw at, so a 16 by 16 tile is one metre square and a 32 by 16 sign is 2 m by 1 m.
Every sheet, tile set and camera in a scene shares one value. Art drawn at a different value from the camera’s lands off the camera’s pixel grid and shimmers as it moves, so a development build throws an error naming both values, and a built game logs it. A value of 0, below it, or not a finite number is refused the same way.
Loading an image
Section titled “Loading an image”useSpriteTexture(image, options) loads a sprite’s or a tile set’s image for a material’s map. It loads the image in sRGB with no mipmaps and nearest filtering, so each pixel of the art draws as a square. Every view that names the same file shares one texture, and the texture is freed when the last of them unmounts. It suspends while it loads, so mount it under a Suspense.
The image is a URL or a painted definePixelTexture. A painted one is painted on first use from the game’s texture seed, its colour and coverage alone, as a sheet painted in code is. The following sample loads a URL:
import { useSpriteTexture } from "@spawnite/engine";
// A 32 by 16 pixel sign at the engine's 16 pixels a metre: 2 m by 1 m,// nearest-filtered, cut out where the art is transparent.export function PixelSign() { const texture = useSpriteTexture("/assets/sign.png"); return ( <mesh position-y={0.5}> <planeGeometry args={[2, 1]} /> <meshBasicMaterial map={texture} alphaTest={0.5} fog={false} /> </mesh> );}For art that is not pixel art, pass { filter: TextureFilter.Linear }, Godot’s name: each screen pixel then blends the four nearest pixels of the art. A file that does not load throws an error naming it. A failed file stays failed until the page loads again.
The sign’s material is the one a sprite draws with:
- Unlit,
MeshBasicMaterial, so the art’s colours are not shaded. - Cut out at half alpha,
alphaTest={0.5}, so it writes depth and the depth buffer sorts it. - Unfogged,
fog={false}, since a 2D camera’s height above the ground is no distance.
The pixel look
Section titled “The pixel look”The pixel look draws an unlit pixel as the file holds it by day, and darkens it with the exposure at night. Import it from @spawnite/engine/looks/pixel and hand it to a Landscape’s look, as any look.
It maps the frame with no tone curve, ToneMappingMode.LINEAR, and turns off fog, bloom, ambient occlusion, the vignette, the character light and the sun’s shadow. It keeps the exposure and the grade. Unity’s 2D renderer and Godot’s canvas draw sprites the same way by default. Pick it for any 2D game, pixel art or smooth.
A 3D look, such as noon, maps the whole frame with ACES, sprites included, so it moves every colour in the art. A material’s toneMapped: false does not stop that: the look’s post-processing maps the frame after every material has drawn.
Three things still change a pixel under pixel:
- A grade other than none, which a look you base on
pixelmay set. - The quality level’s screen-space anti-aliasing, at High, which smooths the steps along an edge in the art. A pixel-perfect Camera2D takes none.
- Cloud shadows, when a look you base on
pixelturns them on:{ base: pixel, cloudShadows: {} }drifts soft shade over the ground and the sprites.