Skip to main content
← Make a Platformer

Lesson 1 of 4

The Game Loop

A canvas, a red rectangle, and arrow keys that move it. Every later stage starts from this code.

What you will learn

  • <canvas>
  • getContext
  • update and draw
  • requestAnimationFrame
  • keydown and keyup
  • fillRect
  • Up is a bigger number

What this stage does

Click the game, then press the left and right arrow keys. The red rectangle moves. It stops at both edges of the canvas. There is no gravity yet, so nothing pulls it down.

The player object

These three numbers are the `player` object in mario.js. Move them and look at the file. The numbers change there too. Height grows the player upward. The feet stay on the ground.

5

How many pixels the player moves each frame.

40px
50px

Make a giant. The feet stay on the ground. The player grows upward.

Which way is up

A canvas counts y downward. At the top of the screen y is 0. It gets bigger as you go down. That is upside down from a ruler, so this game does not do it. Here y counts up from the ground. A rectangle's x and y are its bottom-left corner. So player.y is where the player's feet are.

mario.js, the line that flips y round
function drawRect(x, y, width, height, colour) {
    ctx.fillStyle = colour;
    ctx.fillRect(x, canvas.height - y - height, width, height);
}

drawRect does the flipping. It is the only place in the game that has to. Everywhere else up is simply a bigger number. That is true for jumping, falling, platforms, and the camera.

The three functions

Every game in this course is these three functions. They run one after another, over and over.

FunctionIts one job
update()Change the numbers. Where is the player now? It draws nothing.
draw()Paint the current numbers onto the canvas. It decides nothing.
gameLoop()Call both, then ask the browser to call it again on the next frame.
mario.js, the loop
function gameLoop() {
    update();
    draw();
    requestAnimationFrame(gameLoop);
}

gameLoop();

requestAnimationFrame runs the function again on the next frame. That is about 60 times a second.

How the keyboard works

A keydown event fires once. But holding a key down should move the player on every frame. So the game does not act on the event. Instead it stores which keys are held down. Then update() reads that list on every frame.

const keys = {};

window.addEventListener("keydown", (e) => { keys[e.code] = true;  });
window.addEventListener("keyup",   (e) => { keys[e.code] = false; });

// ...then, inside update():
if (keys["ArrowLeft"] || keys["KeyA"])  player.x -= player.speed;
if (keys["ArrowRight"] || keys["KeyD"]) player.x += player.speed;

Build it step by step

  1. Make the canvas

    Make luigi.html with one <canvas id="game" width="800" height="450"> and a <script src="mario.js"> tag. Open it. You see an empty canvas.

  2. Draw one rectangle

    In mario.js, get the canvas and its drawing context. Then call fillRect once. Reload. The rectangle appears near the top, because fillRect counts y downward. Change the 50 to 300 and reload. It drops.

    const canvas = document.getElementById("game");
    const ctx = canvas.getContext("2d");
    
    ctx.fillStyle = "red";
    ctx.fillRect(100, 50, 40, 50);   // x, y, width, height
  3. Turn y the right way up

    Write drawRect and call that instead of fillRect. Use the same four numbers. The rectangle moves to the bottom of the canvas, 50 pixels up from the floor. Draw the ground the same way. drawRect(0, 0, canvas.width, groundTop, "green") fills everything below groundTop.

    function drawRect(x, y, width, height, colour) {
        ctx.fillStyle = colour;
        ctx.fillRect(x, canvas.height - y - height, width, height);
    }
    
    const groundTop = 50;
    
    drawRect(100, 50, 40, 50, "red");   // 50 pixels up from the bottom
  4. Put the numbers in an object

    Replace the four numbers with a player object that has x, y, width, height, and speed. Nothing changes on screen. That is what a refactor looks like.

  5. Split the code and loop it

    Move the drawing into draw(). Add an empty update(). Write gameLoop(). Nothing changes on screen yet. But the page now redraws 60 times a second. To prove it, put player.x += 1 inside update(). Watch the rectangle slide away.

  6. Read the keyboard

    Add the keys object and the two listeners. Then add the arrow-key checks inside update(). The rectangle now moves when you press a key.

  7. Stay on screen

    At the end of update(), stop the player leaving the canvas. That is two if statements, one for each edge.

    if (player.x < 0) player.x = 0;
    if (player.x > canvas.width - player.width)
        player.x = canvas.width - player.width;

Try these changes

Make these changes in the files beside this guide. Each one only uses what this lesson has already covered. Tick each one when it works.

Check yourself

Three quick questions on this lesson. Get them all right to finish the lesson and earn a pack.

Loading your answers

Teacher's versionHow to run this lesson, what to check, and any answers. Students do not need this.

How to teach it

  • Draw one rectangle with four literal numbers before anything else. Change each number and reload. Moving the numbers teaches the student what x, y, width, and height mean.
  • Run steps 2 and 3 one straight after the other. The same four numbers put the rectangle near the top, then near the bottom. Seeing that happen is the whole lesson about coordinates. Do not explain the flip first. A student who has not yet watched it move will not understand.
  • Step 4 looks like it does nothing. Insist on the player.x += 1 test. Without it the student has no evidence the loop is running.
  • Comment out the sky fill and let the smear happen. Ask what is missing before you explain.
  • Ask why the code stores keys in an object. Ask why it does not move the player inside the keydown handler. Try the wrong version if there is time. The movement is jerky and it stalls. That is the answer.