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
| Name | What it means |
|---|---|
| World position | Where a thing really is. The world is 2000 wide. A platform at x: 1500 is always at 1500. |
| Screen position | Where 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 frameLeft 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.
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.
How far right you can walk before the view stops.
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
Make the world too big
Add platforms past
x: 800and setworldWidthto 2000. Change the player's limit fromcanvas.widthtoworldWidth. Play. You can walk off the right of the screen and vanish. The level is there. You just cannot see it.Move the view by hand
Add
const camera = { x: 0, y: 0 }. Add thesave,translate, andrestorelines todraw(). Setcamera.x = 400by hand and reload. The world slides across. Nothing follows anything yet, but you can see how it works.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.Limit the camera
Add the two
ifstatements for left and right. Walk to both ends and check that the view stops.Add vertical follow, twice
First set
camera.ywith the same centring line ascamera.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.xby 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
drawRecthas 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
ifstatements map straight onto that picture.