Skip to content

Learn how to create and customize 2D sprites.

What you’ll learn

  • How to create, position, anchor, and scale a Sprite2D
  • How to opt sprites into the lighting and shadow pipeline (lit, castsShadow, shadowRadius)
  • How layers and z-index control render order independently of scene-graph order

The Sprite2D class is the foundation for 2D rendering in three-flatland. If your scene only ever needs static images on screen, this is the only class you need to learn.

Anatomy of a Sprite2Dhow the key properties map onto the rendered quad in world spaceworld space (orthographic, +Y up)unit quad (1 x 1 plane)texturesampled across the quadposition (x, y) = anchor pivotanchor offset positions thequad relative to positionanchor (pivot, 0..1)[0, 0] bottom-left[0.5, 0.5] center (default)[0.5, 0] bottom-center[1, 1] top-rightthe point the quad pivotsand rotates aroundtint (Color)multiplies the sampledtexture color per fragment(1,1,1) = unchangedtexture/ framescale.x (width px)scale.y(h px)draw order — layer, then zIndex (back to front)BACKGROUNDrenders firstENTITIESdefault layerUIrenders lastwithin a layer, zIndex breaks ties —e.g. sprite.zIndex = position.y for top-down Y-sorting.
Anatomy of a Sprite2D — anchor sets the pivot the quad hangs from, scale gives it pixel size, tint multiplies the sampled texture, and layer plus zIndex decide draw order.
import { Sprite2D, SpriteGroup } from 'three-flatland';
const sprite = new Sprite2D({
texture: myTexture,
anchor: [0.5, 0.5], // Center anchor
});
spriteGroup.add(sprite);

Sprites inherit from Three.js Mesh and support standard transform properties:

sprite.position.set(100, 200, 0);
sprite.rotation.z = Math.PI / 4; // 45 degree rotation
sprite.scale.set(2, 2, 1); // 2x scale

The anchor point determines the sprite’s origin for positioning and rotation. Values range from 0 to 1.

AnchorDescription
[0, 0]Bottom-left corner
[0.5, 0.5]Center (default)
[0.5, 0]Bottom-center
[1, 1]Top-right corner

Sprite geometry is a unit plane — display size comes from scale. Set scale.set(width, height, 1) in pixels:

const sprite = new Sprite2D({ texture: myTexture });
sprite.scale.set(64, 64, 1);

To sample a sub-region of a texture atlas, pass a frame from a SpriteSheet:

const sprite = new Sprite2D({
texture: sheet.texture,
frame: sheet.getFrame('player_idle_0'),
});

For batched rendering with many sprites, use SpriteGroup:

import { SpriteGroup } from 'three-flatland';
const spriteGroup = new SpriteGroup();
scene.add(spriteGroup);
spriteGroup.add(sprite1);
spriteGroup.add(sprite2);
// In animation loop — no update() call needed
renderer.render(scene, camera);

For simple cases with few sprites, you can add them directly to the scene without SpriteGroup.

Sprites integrate with the active LightEffect and the SDF shadow pipeline through four properties. All four are also accepted as constructor options and as JSX props on <sprite2D>.

PropertyTypeDefaultPurpose
litbooleantrueReceive lighting from the attached LightEffect.
receiveShadowsbooleantrueAllow lit fragments to be darkened by the SDF shadow trace.
castsShadowbooleanfalseContribute the sprite’s silhouette to the SDF occlusion pre-pass.
shadowRadiusnumber | undefinedundefined (auto)Per-instance occluder radius in world units.
const hero = new Sprite2D({ texture, castsShadow: true })
hero.lit = true // (default) reacts to lights
hero.receiveShadows = false // opt out of being darkened by shadow zones

In React:

<sprite2D
texture={myTexture}
castsShadow
lit
receiveShadows={false}
/>

lit, receiveShadows, and castsShadow are bit flags packed into instanceSystem.z of the interleaved instance buffer (see Batch Rendering for material authors).

shadowRadius is the world-space size of the sprite as a shadow occluder. It’s consumed by SDF shadow tracers as the self-silhouette escape distance — the value the trace steps “out of” when the sample point starts on top of the caster.

When left undefined (the default), transformSyncSystem auto-derives it each frame from max(|scale.x|, |scale.y|) so it tracks scale changes (including animation frames whose source sizes differ). Set it explicitly when:

  • The visible body is tighter than the quad’s bounds (e.g., a sprite with transparent padding shouldn’t use its full bounding-box scale as the silhouette radius).
  • The anchor pushes the silhouette off-center and you need a different effective size.
sprite.shadowRadius = 12 // override
sprite.shadowRadius = undefined // back to auto

Use the Layers enum to control render order:

import { Layers } from 'three-flatland';
sprite.layer = Layers.BACKGROUND; // Renders behind other sprites
sprite.layer = Layers.ENTITIES; // Normal render order for game entities
sprite.layer = Layers.FOREGROUND; // Renders in front
sprite.layer = Layers.UI; // UI layer (renders last)

Available layers (in render order):

  • Layers.BACKGROUND - Background elements
  • Layers.GROUND - Ground/terrain
  • Layers.SHADOWS - Shadow sprites
  • Layers.ENTITIES - Game entities (default)
  • Layers.EFFECTS - Particles and effect sprites
  • Layers.FOREGROUND - Foreground elements
  • Layers.UI - User interface