Lesson 1 of 4
The Game Loop
A canvas, a red rectangle, and arrow keys that move it. This is the skeleton for every later stage.
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.
How many pixels the player moves each frame.
Make a giant. The feet stay on the ground and the player grows upward.
Which way is up
A canvas counts y downward. At the top of the screen y is 0, and it gets bigger as you go down. That is upside down from every ruler you have ever used, so this game does not do it. Here y counts up from the ground, and a rectangle's x and y are its bottom-left corner. So player.y is where the player's feet are.
function drawRect(x, y, width, height, colour) {
ctx.fillStyle = colour;
ctx.fillRect(x, canvas.height - y - height, width, height);
}drawRect does the flipping, and it is the only place in the game that has to. Everywhere else — jumping, falling, platforms, the camera — up is simply a bigger number.
The three functions
Every game in this course is these three functions, running in a circle.
| Function | Its 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. |
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 writes down which keys are held, and update() reads that list on every pass.
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
Make the canvas
Make
luigi.htmlwith one<canvas id="game" width="800" height="450">and a<script src="mario.js">tag. Open it. You see an empty box.Draw one rectangle
In
mario.js, get the canvas and its drawing context. Then callfillRectonce. Reload. The rectangle appears near the top, becausefillRectcountsydownward. 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, heightTurn y the right way up
Write
drawRectand call that instead, with the same four numbers. The rectangle jumps 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 belowgroundTop.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 bottomPut the numbers in an object
Replace the four numbers with a
playerobject that hasx,y,width,height, andspeed. Nothing changes on screen. That is what a refactor looks like.Split the code and loop it
Move the drawing into
draw(). Add an emptyupdate(). WritegameLoop(). Nothing changes on screen yet, but the page now redraws 60 times a second. To prove it, putplayer.x += 1insideupdate()and watch the rectangle slide away.Read the keyboard
Add the
keysobject and the two listeners. Then add the arrow-key checks insideupdate(). The rectangle now moves when you press a key.Stay on screen
At the end of
update(), stop the player leaving the canvas. That is twoifstatements, 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 in the files beside this guide. None of them need anything the lesson has not covered.
- Change the player's colour, size, and speed.
- Change the ground colour, or make it thicker by raising `groundTop`.
- Add a second rectangle that moves on its own, with no keys.
- Make the player wrap around the edges instead of stopping.
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 so the student learns what x, y, width, and height mean by moving them.
- Run steps 2 and 3 back to back. The same four numbers put the rectangle near the top, then near the bottom. Seeing that happen is the whole lesson about coordinates. Explaining the flip first, to a student who has not yet watched it move, does not land.
- Step 4 looks like it does nothing. Insist on the
player.x += 1test. 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 instead of moving the player inside the
keydownhandler. Try the wrong version if there is time. The movement is jerky and it stalls, which is the answer.