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
- All physics values are pixels per 50Hz tick. The engine updates in fixed 20ms steps, so
gravity: 0.14means the same thing on every machine. Handy tuning maths: jump height ≈jumpVel² / (2 × gravity), airtime ≈2 × jumpVel / gravityticks. - Colours are palette indices, not hex.
C64.YELLOW, not#B8C76F. Sixteen colours; that's the machine. - 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 sidePLATFORM— jump up through it, land on top of itHAZARD— kills on contactCONVEYOR— drags anything standing on it (dir: 1or-1on the tile, optionalbeltSpeed)CRUMBLE— decays while stood on, then gives way (optionalhp, 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.