Tutorial 2 · Enemies & Coins

A character in a room isn't a game until there's a reason to move and a reason to be careful. Here's what we're building — grab all three coins, avoid the red guardian:

This is the smallest complete game: a goal, a hazard, and a win state. It builds straight on Tutorial 1.

Entities: the things that aren't tiles

Tiles are the fixed world. Entities are everything that moves or can be picked up. The engine ships two you'll use constantly:

import { Patroller, Item } from '../../engine/entities.js';

An entity is deliberately simple — anything with an update() and a draw(). No framework, no scene graph.

Step 1 — a patrolling guardian

Patroller is a hazard that walks a set path. This one bounces back and forth along the floor between two x-positions:

const guard = new Patroller(map, {
  x: 120, y: 62,
  axis: 'x', mode: 'range', min: 40, max: 200,
  speed: 0.8, color: C64.LIGHT_RED,
});

There are three patrol behaviours: mode: 'range' bounces between min and max; mode: 'edge' walks until it reaches a wall or the end of a platform, then turns (the classic Goomba); and axis: 'y' bobs up and down (a piston). Touching any of them is deadly.

Step 2 — coins to collect

Item is a pickup. Give it a position and a colour:

const coins = [
  new Item({ x: 92,  y: 12, color: C64.YELLOW }),
  new Item({ x: 30,  y: 28, color: C64.YELLOW }),
  new Item({ x: 196, y: 28, color: C64.YELLOW }),
];
let got = 0;

Step 3 — check collisions in the loop

Both entity types expose a hits(player) test. This is the entire game rule, and it lives right in your update step:

update() {
  input.tick();
  if (input.pressed('reset')) player.respawn();
  player.update(input);

  guard.update();
  if (guard.hits(player)) player.die();              // touched the guardian → respawn

  for (let i = coins.length - 1; i >= 0; i--) {
    if (coins[i].hits(player)) { coins.splice(i, 1); got++; }   // grabbed a coin
  }

  if (got >= coins.length && !won) win();            // all collected → victory
}

Walking backwards through the coins array is the safe way to remove items mid-loop — splicing forwards would skip the next element. player.die() bumps the death counter and returns the player to its spawn; player.respawn() (bound to the R key here) does the same without the death.

Step 3 — draw them

Entities draw themselves; you just call them, back-to-front, after the map:

render() {
  video.clear(C64.BLUE);
  map.draw(video, camera.x, camera.y);
  for (const c of coins) c.draw(video, camera.x, camera.y, tick);
  guard.draw(video, camera.x, camera.y);             // guardian over coins
  player.draw(video, camera.x, camera.y);            // player on top
}

The tick you pass to c.draw is just a frame counter — the coin uses it to twinkle. Draw order is paint order: whatever you draw last sits on top.

What you've learned

That hits()-in-the-loop pattern is the seed of the whole game-rules system. In a bigger game, "touch a guardian → die" and "touch a coin → collect" become declared rules on the player object rather than hand-written ifs — that's the direction the engine is heading, and you can read the plan in the Build Log. But under the hood it's always this: entities with an update, a draw, and a hits.

Next

Time for the tiles that make a platformer feel retro — belts that carry you, floors that crumble, ladders that climb.

Tutorial 3 · The Trick Tiles →