Transforms and clipping
spryte keeps a 2D transform that applies to every draw call: sprites, bulk sprites, shapes and text. You change it with translate, scale and rotate, save and restore it with push and pop, and begin() resets it each frame.
draw.push();
draw.translate(ship.x, ship.y);
draw.rotate(ship.angle);
draw.sprite(hull, 0, 0);
draw.sprite(turret, 0, -6, { rotation: ship.aim });
draw.circle(0, 10, 3, "#ffd36b"); // engine glow
draw.pop();Everything between push() and pop() is drawn relative to the ship, so its parts move and turn together.
The transform stack
| Method | |
|---|---|
push() | Save the current transform. |
pop() | Restore the last saved transform. Throws without a matching push(). |
translate(x, y) | Move the origin. |
scale(x, y?) | Scale. y defaults to x. |
rotate(rad) | Rotate around the current origin, in radians. |
Each call composes with what's already there, in the order you make them. push() and pop() save only the transform: the global alpha, blend mode and clips aren't part of it. The stack is preallocated, so push() and pop() don't allocate.
Cameras
withCamera(camera, fn) applies a camera's view to the draws inside fn, then restores the transform. A camera is any object with a view() method that returns a matrix [a, b, c, d, e, f], where a point maps to x' = a·x + c·y + e and y' = b·x + d·y + f.
const camera = {
x: 0,
y: 0,
zoom: 2,
view(): [number, number, number, number, number, number] {
// centre (x, y) in a 320×180 view, zoomed
return [this.zoom, 0, 0, this.zoom, 160 - this.x * this.zoom, 90 - this.y * this.zoom];
},
};
function render() {
draw.begin("#000", 320, 180, scale);
camera.x = player.x;
camera.y = player.y;
draw.withCamera(camera, () => {
drawWorld(); // world space
draw.sprite(hero, player.x, player.y);
});
drawHud(); // screen space, outside the camera
draw.end();
}Drawing the HUD after withCamera keeps it fixed on screen.
Clipping
clip(x, y, w, h, fn) restricts the draws inside fn to a rectangle, then removes the clip:
// A scrolling list inside a panel
draw.rect(20, 20, 120, 80, "#222");
draw.clip(20, 20, 120, 80, () => {
for (let i = 0; i < items.length; i++) {
draw.text(items[i]!, 26, 24 + i * 14 - scrollY);
}
});- The rectangle is in current units, so it follows the transform.
- Clips are axis-aligned on screen. Under rotation, the clip is the rotated rectangle's screen bounds.
- Nested clips intersect: an inner clip never draws outside an outer one.
- The clip is removed even if
fnthrows.
Global alpha
draw.alpha is a multiplier for everything drawn after you set it. It multiplies with each sprite's own alpha and with colours' alpha.
draw.alpha = menuFade; // 0 to 1
drawMenu();
draw.alpha = 1;begin() resets it to 1 each frame.
Blend modes
draw.blend(mode) sets how later draws combine with what's underneath. There are two modes:
"normal": ordinary alpha blending, the default."add": additive, for light, fire, sparks and glow. Bright things pile up toward white.
drawWorld();
draw.blend("add");
draw.sprites(spark, sparkPos, live, { tint: "#ffd36b" });
draw.sprite(lightCone, lamp.x, lamp.y, { alpha: 0.6 });
draw.blend("normal");
drawHud();begin() resets the blend mode to "normal" each frame. Set it back yourself if more draws follow in the same frame.
Default filtering
draw.smooth is the default image filtering for sprites that don't pass their own smooth option. It starts as the smooth you gave createRenderer. Changing it affects draws made after the change.