Tutorial 1 · Hello, Platform

Here's the finished article — run it. Arrow keys or A/D to move, Z / Space / ↑ to jump. Everything below builds exactly this.

Thirty lines of code produced that. Let's build it piece by piece.

The six modules

The engine is a folder of plain ES modules. This game imports six of them:

import { createLoop } from '../../engine/loop.js';   // the 50Hz heartbeat
import { Video }  from '../../engine/video.js';       // the 320×200 screen
import { Input }  from '../../engine/input.js';       // the joystick
import { Camera } from '../../engine/camera.js';      // follows the player
import { Player } from '../../engine/player.js';       // the character
import { TileMap, SOLID, PLATFORM } from '../../engine/tilemap.js';
import { C64 } from '../../engine/palette.js';         // the 16 colours

No npm, no bundler. The browser loads them directly.

Step 1 — describe the room

A room is an array of strings plus a legend that says what each character means:

const map = new TileMap({
  rows: [
    '..............................',
    '................=====.........',
    '.......=====..................',
    '....................===.......',
    '##############################',
  ],
  legend: {
    '#': { flags: SOLID,    kind: 'brick',    base: C64.RED,    top: C64.LIGHT_RED },
    '=': { flags: PLATFORM, kind: 'platform', base: C64.YELLOW, top: C64.WHITE },
  },
  tileSize: 8,
});

. is empty air. # is solid brick — it blocks you from every side. = is a platform — you can jump up through it but you land on top. Those behaviours come entirely from the flags field; the engine reads them, you never write collision code.

Step 2 — describe the character

A player is a physics profile — ten numbers that define how it feels to move — plus a spawn point:

const player = new Player(map, {
  inertia: true,     // momentum: speed builds up and bleeds away
  maxSpeed: 1.6, accel: 0.12, friction: 0.09, airControl: 0.7,
  gravity: 0.14, jumpVel: 3.2, minJumpVel: 1.2, variableJump: true,
  fixedJump: false, maxFall: 4, color: C64.YELLOW,
}, { x: 24, y: 78 });

Every value is in pixels per tick (the engine runs at a fixed 50 frames a second, like a PAL C64). Want a floatier jump? Lower gravity. Want instant, momentum-free movement like Jet Set Willy? Set inertia: false. The Movement Lab lets you feel each number on a slider.

Step 3 — the screen, joystick and camera

const video  = new Video(stage, { border: C64.LIGHT_BLUE });
const input  = new Input().attach(window);
const camera = new Camera(video.width, video.height, map.pixelWidth, map.pixelHeight);

Video makes the 320×200 canvas (integer-scaled, pixel-perfect, with the C64's light-blue border). Input is a one-button joystick — four directions and jump, keyboard-mapped. Camera will keep the player in view and stop at the room's edges.

Step 4 — the loop

This is the heart of it. update() runs 50 times a second in fixed steps; render() draws once per animation frame:

createLoop({
  update() {
    input.tick();                                    // sample the joystick
    player.update(input);                            // move & collide
    camera.follow(player.x + player.w/2, player.y + player.h/2);
  },
  render() {
    video.clear(C64.BLUE);                           // VIC-II blue screen
    map.draw(video, camera.x, camera.y);             // the room
    player.draw(video, camera.x, camera.y);          // the character
  },
}).start();

That's the whole game. player.update(input) does all the hard work — acceleration, gravity, jumping, and tile collision against the map — driven by the profile you handed it.

The one rule to remember

The update step is a fixed 50Hz. That's why every physics number is "per tick" and why a jump looks identical on a slow laptop and a 144Hz monitor — the simulation never speeds up or slows down, only the drawing does. Get comfortable with that and everything else in the engine follows.

Next

You have a character in a room. Now let's give the room a point — something to collect and something to avoid.

Tutorial 2 · Enemies & Coins →