A game can look complicated while doing a very simple thing: read what is happening, update the world, draw the result, and repeat. This sample MDX article walks through that pattern without introducing a game framework.

Update and render are different jobs#

Updating changes the model: a ship moves, a timer advances, or a collision changes a state. Rendering turns the current model into something the player can see.

Keeping those jobs separate makes it easier to ask whether a problem lives in the rules or the picture. A ship can be in the correct place even when its sprite is drawn incorrectly.

interface Ship {
  x: number;
  speed: number;
}

function update(ship: Ship, deltaSeconds: number) {
  ship.x += ship.speed * deltaSeconds;
}

function render(ctx: CanvasRenderingContext2D, ship: Ship) {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.fillRect(ship.x, 40, 12, 12);
}

This example uses a variable timestep. A more demanding physics simulation might benefit from fixed updates, but a small prototype can start here.

Use the frame timestamp#

The callback passed to requestAnimationFrame receives a timestamp. Subtract the previous timestamp, convert milliseconds to seconds, and you have an elapsed time to pass into the update function.

let previous: number | undefined;

function frame(now: number) {
  const delta = previous === undefined ? 0 : (now - previous) / 1000;
  previous = now;

  update(ship, Math.min(delta, 0.05));
  render(ctx, ship);
  requestAnimationFrame(frame);
}

requestAnimationFrame(frame);

The limit prevents one unusually long frame from moving the ship across the whole screen. It is a trade-off: the prototype slows down rather than simulating all elapsed time. It is not a universal physics solution.

Give pause a clear meaning#

Pause should stop the game model from advancing. Returning to a hidden tab should also reset the previous frame timestamp so the next update does not include time spent away.

The rendering loop and the active-playtime tracker have related but different responsibilities. The first keeps the game responsive; the second measures time spent playing. Later, the platform SDK will provide the latter so every game does not need to reinvent it.

Keep the loop easy to inspect#

It helps to start with a handful of explicit functions. You can add a scene manager or engine when you have a concrete need, but neither is required to move a square and test an idea.

During development, I keep a short checklist:

  • Movement uses elapsed time rather than a fixed amount per frame.
  • Inputs update a clear state instead of drawing directly.
  • Pause and tab visibility are handled deliberately.
  • Rendering does not silently change the game rules.

What comes next#

The useful next feature is whichever one tests the mechanic. That might be a collision, a launch window, or a clear restart button.

The loop can stay boring. The game is where the interesting decisions belong.