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.
How many pixels the player moves each frame.
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.
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.
| 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 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
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 canvas.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 offillRect. 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 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(). 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 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 += 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. Ask why it does not move the player inside the
keydownhandler. Try the wrong version if there is time. The movement is jerky and it stalls. That is the answer.