Using the engine

Retro-64 is a folder of plain ES modules — no npm, no build step, no framework. Copy public/retro64/ to any static host (or serve it locally) and you have everything. This page is the complete quick-start; the engine's inner workings are covered milestone by milestone in the Build Log.

The 40-line game

A complete, playable platformer:

<!doctype html>
<div id="game"></div>
<script type="module">
  import { createLoop } from './retro64/engine/loop.js';
  import { Video }  from './retro64/engine/video.js';
  import { Input }  from './retro64/engine/input.js';
  import { Camera } from './retro64/engine/camera.js';
  import { Player } from './retro64/engine/player.js';
  import { TileMap, SOLID, PLATFORM } from './retro64/engine/tilemap.js';
  import { C64 } from './retro64/engine/palette.js';

  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 },
    },
  });

  const video  = new Video(document.getElementById('game'));
  const input  = new Input().attach(window);
  const camera = new Camera(video.width, video.height, map.pixelWidth, map.pixelHeight);
  const player = new Player(map, {
    maxSpeed: 1.7, accel: 0.09, friction: 0.06, airControl: 0.7,
    gravity: 0.14, jumpVel: 3.4, minJumpVel: 1.3,
    variableJump: true, fixedJump: false, maxFall: 4, color: C64.YELLOW,
  }, { x: 16, y: 20 });

  createLoop({
    update() { input.tick(); player.update(input); camera.follow(player.x, player.y); },
    render() { video.clear(C64.BLUE); map.draw(video, camera.x, camera.y); player.draw(video, camera.x, camera.y); },
  }).start();
</script>

That's the whole thing. A level is strings plus a legend; a character is ten numbers; the loop is two callbacks.

The three rules

  1. All physics values are pixels per 50Hz tick. The engine updates in fixed 20ms steps, so gravity: 0.14 means the same thing on every machine. Handy tuning maths: jump height ≈ jumpVel² / (2 × gravity), airtime ≈ 2 × jumpVel / gravity ticks.
  2. Colours are palette indices, not hex. C64.YELLOW, not #B8C76F. Sixteen colours; that's the machine.
  3. Rooms are character grids on 8×8 tiles. One screen is exactly 40×25 characters. Wider rooms scroll automatically; one-screen rooms pin the camera.

The module map

Module What it gives you
engine/loop.js createLoop({update, render}) — fixed 50Hz timestep
engine/video.js Video — 320×200 canvas, integer scaling, palette drawing
engine/input.js Input — one-button joystick (keyboard-mapped, dir/jumpHeld/pressed)
engine/palette.js PALETTE, C64 — the 16 colours by name
engine/tilemap.js TileMap + tile flags — collision grid and room rendering
engine/camera.js Camera — follow + clamp scrolling
engine/player.js Player — the profile-driven controller
engine/entities.js Patroller, Item — enemies and pickups (milestone 2)
engine/world.js World — flick-screen multi-room games (milestone 2)

Tile flags

Combine flags with | if a tile needs more than one behaviour:

  • SOLID — blocks from every side
  • PLATFORM — jump up through it, land on top of it
  • HAZARD — kills on contact
  • CONVEYOR — drags anything standing on it (dir: 1 or -1 on the tile, optional beltSpeed)
  • CRUMBLE — decays while stood on, then gives way (optional hp, in ticks)
  • LADDER — climbable; press up or down while overlapping

The physics profile

Every number the Player reads, in one place. This is where game feel lives:

{
  inertia: true,       // false = instant start/stop, purest 8-bit movement;
                       // true  = momentum via accel (speed-up) & friction (slow-down)
  maxSpeed: 1.7,       // top walking speed
  accel: 0.09,         // per-tick speed gain      (inertia: true only)
  friction: 0.06,      // per-tick speed loss with no input   (ditto)
  airControl: 0.7,     // 0..1 — steering strength mid-air (0 = none)
  gravity: 0.14,       // downward pull per tick
  jumpVel: 3.4,        // takeoff impulse (≈41px high at this gravity)
  variableJump: true,  // release early to cut the jump short
  minJumpVel: 1.3,     // the capped ascent for a tapped jump
  fixedJump: false,    // true = arc committed at takeoff (Jet Set Willy)
  maxFall: 4,          // terminal velocity
  climbSpeed: 1.1,     // ladder speed (milestone 2)
  color: C64.YELLOW,   // suit colour of the placeholder sprite
}

Want to feel what each number does before writing code? That's exactly what the Movement Lab is for.

🕹️ Open the Movement Lab