Make a Platformer

Lesson 4 of 4

A Camera That Follows

The world becomes wider than the screen. Move the view instead of the player.

What you will learn

  • World and screen positions
  • ctx.save and ctx.restore
  • ctx.translate
  • Limiting the camera
  • A deadzone

Before you start

Platforms and Collisions. The level now runs past the right edge of the canvas.

What this stage does

Walk to the right. The view follows you. Walk back to the start and the view stops at the edge of the world. Jump high and the view moves up, but only after you leave the middle of the screen.

Two sets of positions

NameWhat it means
World positionWhere a thing really is. The world is 2000 wide. A platform at x: 1500 is always at 1500.
Screen positionWhere a thing appears on the 800 pixel canvas.

The camera is the difference between the two. camera.x and camera.y say where the bottom-left corner of the screen is in the world — the screen is just another rectangle. It costs three lines in draw(). Everything drawn between save() and restore() is shifted. Everything outside stays still, which is what you want for a sky.

// Sky first, not shifted, so it never scrolls
ctx.fillStyle = "skyblue";
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.save();
ctx.translate(-camera.x, camera.y);

//   ...draw the platforms and the player at their WORLD positions...

ctx.restore();   // put the camera back, ready for the next frame

Left and right: centre, then limit

// Try to keep the player in the middle of the screen
camera.x = player.x + player.width / 2 - canvas.width / 2;

// Do not scroll past either edge of the world
if (camera.x < 0) camera.x = 0;
if (camera.x > worldWidth - canvas.width)
    camera.x = worldWidth - canvas.width;

Without the two limits, walking to the start of the level shows empty space beside the world. This is the same two-line pattern you used to keep the player on screen in stage 1. Here you apply it to the view.

Up and down: the deadzone

Centring the view up and down as well makes it jump on every hop. That is unpleasant to play. Instead, the camera only moves when the player leaves a band in the middle of the screen. deadzoneY is how tall that band is, above and below the centre. Small hops move nothing. Climbing high pulls the view along.

The camera's numbers

Set the deadzone to 0 and jump. That is the lurching version. Put it back to 80 and jump again.

80px

How far you can move up or down before the view follows. Set it to 0 and jump — that is the version without a deadzone.

2000px

How far right you can walk before the view stops.

15

Jump high enough to push past the deadzone.

const playerMiddleY = player.y + player.height / 2;
const screenMiddleY = camera.y + canvas.height / 2;

// Went above the band: pull the camera up
if (playerMiddleY > screenMiddleY + camera.deadzoneY) {
    camera.y = playerMiddleY - canvas.height / 2 - camera.deadzoneY;
}

// Went below the band: push the camera down
if (playerMiddleY < screenMiddleY - camera.deadzoneY) {
    camera.y = playerMiddleY - canvas.height / 2 + camera.deadzoneY;
}

// Do not scroll below the bottom of the world.
// The sky above has no limit, so there is no top edge to stop at.
if (camera.y < 0) camera.y = 0;

The left edge of the world is 0 and so is the bottom, so both limits are the same line of code with a different letter in it.

Build it step by step

  1. Make the world too big

    Add platforms past x: 800 and set worldWidth to 2000. Change the player's limit from canvas.width to worldWidth. Play. You can walk off the right of the screen and vanish. The level is there. You just cannot see it.

  2. Move the view by hand

    Add const camera = { x: 0, y: 0 }. Add the save, translate, and restore lines to draw(). Set camera.x = 400 by hand and reload. The world slides across. Nothing follows anything yet, but you can see how it works.

  3. Follow the player

    Replace your hand-set value with the centring line in update(). The view now follows you. It also scrolls past the ends of the world.

  4. Limit the camera

    Add the two if statements for left and right. Walk to both ends and check that the view stops.

  5. Add vertical follow, twice

    First set camera.y with the same centring line as camera.x. Jump. The whole screen lurches every time. Now replace it with the deadzone version and jump again. Feeling both is worth more than any explanation.

Try these changes

Make these in the files beside this guide. None of them need anything the lesson has not covered.

  • Make the camera slide towards its target instead of snapping. Use `camera.x += (target - camera.x) * 0.1`.
  • Draw a distant background that scrolls at half the camera speed.
  • Add a goal flag at the end of the world and a message when the player reaches it.
  • Show the player's world position in a corner that does not scroll. Draw it after `ctx.restore()`.
Teacher's versionHow to run this lesson, what to check, and any answers. Students do not need this.

How to teach it

  • Step 2 is the one to slow down on. Setting camera.x by hand separates "what does translate do" from "how does following work". Trying to teach both at once is where students get lost.
  • The signs confuse almost everyone. Stand at the board and move a piece of paper left while saying "the camera went right". It lands faster as a gesture than as an explanation. Do the same upward for the vertical one, which is a plus because drawRect has already turned the drawing over.
  • Do step 5 in the wrong order on purpose. A lurching camera is unpleasant within two jumps, and the student then asks for the deadzone themselves.
  • Draw the deadzone as a horizontal band on the board and mark the player inside it, above it, and below it. The two if statements map straight onto that picture.