Skip to content

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.

ts
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.

ts
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:

ts
// 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 fn throws.

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.

ts
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.
ts
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.

Next: Render targets and shaders