Skip to content
Topic 2 of 5
Theory

The Game Loop

How a picture comes alive

A cartoon is many pictures in a row. A game does the same: about 60 times a second it erases the canvas and draws everything in a new position. The repeating piece of code that does this is called the game loop.

requestAnimationFrame

Asking the browser to call a function before the next screen repaint is requestAnimationFrame:

function frame() {
  // 1. change the positions of objects
  // 2. draw the frame
  requestAnimationFrame(frame);   // and ask for the next frame
}
requestAnimationFrame(frame);

Why not setInterval(frame, 16)? requestAnimationFrame adapts to the screen's refresh rate (60, 120, 144 Hz), doesn't draw for nothing when the tab is hidden, and the frames come out smooth, without stutter. It's the standard for animation in the browser.

You must not make a game loop with while (true) { ... }: such a loop never gives control back to the browser, the page will freeze and nothing will be drawn.

Frame time: dt

Frames don't come evenly: on a fast computer there are 144 per second, on a slow one — 30. If you move the ball "2 pixels per frame", it will fly at different speeds on different computers. That's why speed is given in pixels per second, and each frame the object is moved by speed × dt, where dt is how many seconds have passed since the previous frame.

requestAnimationFrame passes the current time in milliseconds to the function:

let last = performance.now();

function frame(now) {
  const dt = (now - last) / 1000;   // milliseconds → seconds
  last = now;

  ball.x += ball.vx * dt;           // vx — pixels per second
  draw();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

If the ball flies at 120 px/s, in a frame of 1/60 of a second it moves 2 pixels, and in a frame of 1/30 — 4 pixels. Over a second, in both cases — exactly 120.

When the tab is minimized, frames stop, and on return dt turns out huge (minutes!) — objects "teleport" through walls. That's why dt is usually limited: Math.min(dt, 0.1).

State, update and draw

Everything that describes the game at a given moment — positions, velocities, score, lives — is kept in state objects. A frame is split into two parts:

const state = {
  ball: { x: 50, y: 50, vx: 150, vy: 90, r: 10 },
  score: 0,
};

function update(dt) {            // only changes numbers
  state.ball.x += state.ball.vx * dt;
  state.ball.y += state.ball.vy * dt;
}

function draw() {                // only draws what's in the state
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(state.ball.x, state.ball.y, state.ball.r, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillText(`Score: ${state.score}`, 10, 20);
}

let last = performance.now();
function frame(now) {
  const dt = Math.min(0.1, (now - last) / 1000);
  last = now;
  update(dt);
  draw();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

Why split it? update can be tested without a screen — just call it with numbers and look at the result (that's how the course tasks work). And draw doesn't think about the game rules, it only shows the state. It's the same idea as "data → render" in the DOM.

Acceleration and gravity

Velocity can also change over time. Gravity is a constant downward acceleration: every second the falling speed grows by the same amount.

const GRAVITY = 300;          // px/s per second
ball.vy += GRAVITY * dt;      // first change the velocity
ball.y += ball.vy * dt;       // then move with the new velocity

This is how jumps, falls and the beautiful parabolas of firework sparks are made.

Objects appear and disappear

Bullets, sparks and enemies live briefly. They're kept in an array: new ones are added with push, and unneeded ones are removed by keeping only the living ones:

bullets = bullets.filter((b) => b.y > 0);   // those that flew off screen — remove

Summary

  • The game loop: requestAnimationFrame(frame) at the end of every frame; no while (true).
  • Speed — in pixels per second, the shift per frame — speed × dt, dt in seconds and no more than 0.1.
  • The game state lives in objects; update(dt) changes numbers, draw() draws.
  • Gravity: vy += g × dt, then y += vy × dt. Temporary objects — in an array + filter.

Ball Step

Easy+10 XP

A ball is an object { x, y, vx, vy }: the center coordinates and the velocity in pixels per second. Write a function moveBall(ball, dt) that moves the ball over a time of dt seconds: x changes by vx * dt, y — by vy * dt. The function changes the ball object itself and returns nothing.

Below, the starter code already has a game loop that draws the ball — press "Run" and watch it fly.

ball = { x: 10, y: 20, vx: 100, vy: -50 }
moveBall(ball, 0.5)  →  ball.x = 60, ball.y = -5

Connect the connector to solve tasks

Task code runs on your own computer — faster and more reliable than in the browser. This needs the free CodeWithMe connector app. You only connect it once.

Looking for the connector on this computer…

  1. 1
    Download the connector for your system (Windows, macOS or Linux).Download the connectorOn the download page pick the file with your system in its name, e.g. windows-amd64.
  2. 2Run it and choose a folder for your solutions. It will install Python and Node.js there by itself when needed.
  3. 3
    Enter below the 6-digit code shown in the connector window (“Code for CodeWithMe”). If the browser asks about local network access — allow it.

The connector is open source (on GitHub), works only on your computer and runs code only after you enter the pairing code.