Sprites
draw.sprite(frameOrImage, x, y, options?) draws an image, or part of one, at a position. It's the call most games make most often, and it doesn't allocate.
// whole image, centred on (160, 90)
draw.sprite(hero, 160, 90);
// rotated and tinted
draw.sprite(hero, 160, 90, { rotation: 0.3, tint: "#f0f" });Images and frames
The first argument is either an image (HTMLImageElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap or a render target) or a Frame: a rectangle of an image, { image, x, y, w, h }.
import type { Frame } from "spryte";
const sheet = await load("/hero-sheet.png"); // load() from Getting started
// A 4-frame walk cycle on a 16×16 grid
const walk: Frame[] = [0, 1, 2, 3].map((i) => ({ image: sheet, x: i * 16, y: 0, w: 16, h: 16 }));
function render(time: number) {
const frame = walk[Math.floor(time / 100) % walk.length]!;
draw.sprite(frame, player.x, player.y);
}A texture atlas works the same way: build one Frame per region from your atlas data, all pointing at the same image.
Options
| Option | Default | |
|---|---|---|
origin | [0.5, 0.5] | Anchor as a fraction of the frame size. [0, 0] is the top-left corner, [0.5, 1] the bottom centre. |
scaleX, scaleY | 1 | Scale on each axis. |
rotation | 0 | Radians, around the origin. |
flipX, flipY | false | Mirror on an axis, around the origin. |
alpha | 1 | Opacity, multiplied with the global draw.alpha. |
tint | white | Colour multiplier. |
flash | 0 | 0 to 1 mix toward white, for hit flashes. |
smooth | draw.smooth | Linear filtering for this sprite only. |
// A character standing on the ground, facing left, flashing after a hit
draw.sprite(frame, x, groundY, {
origin: [0.5, 1],
flipX: facing < 0,
flash: hitTimer > 0 ? 1 : 0,
});
// A coin that pulses
const pulse = 1 + Math.sin(t * 8) * 0.1;
draw.sprite(coin, x, y, { scaleX: pulse, scaleY: pulse });
// A ghost: half transparent, tinted blue
draw.sprite(ghost, x, y, { alpha: 0.5, tint: "#88aaff" });tint takes any spryte colour: "#rgb", "#rgba", "#rrggbb", "#rrggbbaa", a number 0xRRGGBBAA, or any CSS colour. See colours.
Reuse one options object
sprite() reads the options and doesn't keep them, so reuse one object in a hot loop instead of creating one per sprite:
import type { SpriteDrawOptions } from "spryte";
const opts: SpriteDrawOptions = { rotation: 0, alpha: 1 };
for (const enemy of enemies) {
opts.rotation = enemy.angle;
opts.alpha = enemy.fade;
draw.sprite(enemyFrame, enemy.x, enemy.y, opts);
}Calling draw.sprite(img, x, y) with no options doesn't allocate either.
Draw order
Call order is draw order: later calls paint over earlier ones. There's no z-index or scene graph, so draw layers in the order you want them:
draw.begin("#000", 320, 180, scale);
drawBackground();
for (const e of enemies) draw.sprite(enemyFrame, e.x, e.y);
draw.sprite(playerFrame, player.x, player.y);
drawHud();
draw.end();If you need depth sorting, sort your own array before the loop. spryte batches your calls automatically; draw.drawCalls shows how many GPU calls a frame took.
Pixel snapping
With smooth off, unrotated sprites are placed on whole pixels. Nearest sampling at fractional positions makes edges gain and lose a pixel between frames, which reads as flicker. Rotated sprites aren't snapped. Pixel art has the details.
Changing pixels
If you draw from a canvas and change its pixels, call draw.invalidate(canvas) to re-upload it. Images that never change need nothing.
Next: Bulk sprites