๐Ÿพ purr 0.4.2

purr.

A small 2D game framework you script in tigr. Open a window, draw a sprite, run it on desktop or in the browser. No boilerplate, no build step.

purr ยท bounce.tg

The whole game, top to bottom.

purr runs your script once, calls init to set things up, then calls update and draw every frame. Those three functions are the whole interface.

// bounce.tg โ€” 100 bouncing balls in 25 lines

bounce.tg

balls := [];

init := fn() {
  balls = for[] (i,0..100) {
    ${
      p: Vec.random(0, 0, Gfx.width(), Gfx.height()),
      v: Vec.random(-1, -1, 1, 1),
      r: Random.int(1, 10),
      c: Color.random()
    }
  };
};

update := fn(dt) {
  for (ball, balls) {
    ball.p = Vec.add(ball.p, ball.v);
    if ball.p.x < 0 || ball.p.x > Gfx.width() { ball.v.x *= -1 };
    if ball.p.y < 0 || ball.p.y > Gfx.height() { ball.v.y *= -1 };
  };
};

draw := fn() {
  Gfx.clear(24, 30, 23);
  Gfx.blend('add');
  for (ball, balls) {
    Gfx.color(ball.c);
    Gfx.circle_fill(ball.p.x, ball.p.y, ball.r);
  };
};
          

Why purr

What you actually get.

Here are a couple of purr's cool features.

Hot reload

Save the file and the running game picks up the change, state intact. Drop the console down with the backtick key to read or tweak values live while it plays.

Build for desktop and web

The same script exports to Windows, macOS and Linux, or to a single bundle that runs in the browser.

Screenshots and GIFs

Press F8 for a PNG, F9 to record a GIF. Built in, so grabbing a clip for a devlog takes one key.

See it move

A few small examples

Every snippet below is a complete program. Click one to run it, right here on the page.

Hello, purr ๐Ÿ‘‹

Clear the screen, pick a colour, print some text and draw an image. About as small as a program gets.

hello.tg

msg := 'hello, purr!';
img := null;

init := fn() {
  img = Gfx.load_image('hello_world.png');
};

draw := fn() {
  Gfx.clear(43, 34, 28);
  Gfx.color(246, 201, 154);
  msg_x := int((Gfx.width() - Gfx.text_width(msg)) / 2);
  Gfx.print(msg, msg_x, 50);
  img_x := int((Gfx.width() - Gfx.image_size(img).w) / 2);
  Gfx.draw(img, img_x, 100);
};
hello.tg

Move a kitten

Read bound actions and move a position by the frame's delta time. Rebind the keys later without touching this code.

walk.tg

cat := ${ x: 160, y: 90 };

update := fn(dt) {
    speed := 120 * dt;
    if Input.down('right') { cat.x = cat.x + speed };
    if Input.down('left')  { cat.x = cat.x - speed };
    if Input.down('up')    { cat.y = cat.y - speed };
    if Input.down('down')  { cat.y = cat.y + speed };
};

draw := fn() {
    Gfx.clear(43, 34, 28);
    Gfx.color(229, 130, 56);
    Gfx.rect_fill(cat.x - 6, cat.y - 6, 12, 12);
};
walk.tg

A little garden ๐ŸŒผ

Each click plants a patch. A green thread tweens every blossom up with an easing curve, waits a beat, then pops it back into the soil.

flowers.tg

TAU := Math.PI * 2;

flowers := [];

plant := fn(cx, cy, stagger) {
  f := ${
    x: cx,
    y: cy,
    size: 6 + rand() * 11,
    petals: Random.int(5, 8),
    rot: rand() * TAU,
    scale: 0,
    petal: Color.hsv(rand() * 360, 0.5 + rand() * 0.4, 0.95),
    eye: Color.hsv(48 + rand() * 12, 0.85, 1.0),
    dead: false,
  };

  Array.push(flowers, f);

  go fn() {
    wait(stagger);
    Tween.to(f, 'scale', 1, 0.55, 'out_back');
    wait(0.9 + rand() * 0.5);
    Tween.to(f, 'scale', 0, 0.30, 'in_back');
    f.dead = true;
  };
};

plant_patch := fn(cx, cy) {
  for (i, 0..Random.int(5, 9)) {
    off := Vec.random(-60, -60, 60, 60);
    plant(cx + off.x, cy + off.y, i * 0.04)
  };
};

draw_flower := fn(f) {
  Gfx.push();
  Gfx.translate(f.x, f.y);
  Gfx.rotate(f.rot);
  Gfx.scale(f.scale, f.scale);

  Gfx.color(f.petal);
  for (i, 0..f.petals) {
    a := (i / f.petals) * TAU;
    Gfx.circle_fill(Math.cos(a) * f.size, Math.sin(a) * f.size, f.size * 0.62);
  };

  Gfx.color(f.eye);
  Gfx.circle_fill(0, 0, f.size * 0.55);

  Gfx.pop();
};

update := fn() {
  if Input.mouse_pressed('left') {
    plant_patch(Input.mouse_x(), Input.mouse_y());
  };

  flowers = Array.filter(flowers, fn(f) { !f.dead });
};

draw := fn() {
  Gfx.clear(24, 30, 23);
  for (f, flowers) {
    draw_flower(f);
  };

  Gfx.color(120, 140, 118);
  Gfx.print('click to plant a patch', 12, 12);
};
flowers

The toolkit

Twelve modules, always in scope.

Each is a flat namespace of functions, no classes to wire up and nothing to import. Pick one to jump into its reference.

Browse the full reference โ†’