Semi-random thoughts and tales of tinkering
So far, our drawings have been still pictures. But games need movement! In this lesson, you'll learn the secret behind all video game animation — the game loop. 🎬
Have you ever made a flip-book? You draw a slightly different picture on each page, and when you flip through them fast, it looks like movement!
Games work the exact same way. The computer draws a picture (called a frame), then erases it, makes tiny changes, and draws it again — about 60 times per second!
The game loop is a function that runs over and over, forever. Each time it runs, it does three things:
function gameLoop() {
// 1. Clear the screen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. Update (move things)
ballX = ballX + 2; // Move the ball 2 pixels to the right
// 3. Draw
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(ballX, ballY, 20, 0, Math.PI * 2);
ctx.fill();
// Ask the browser to call gameLoop again on the next frame
requestAnimationFrame(gameLoop);
}
// Start the loop!
gameLoop();
requestAnimationFrame(gameLoop) tells the browser:
"When you're ready to draw the next frame (about 1/60th of a second from now),
please call my gameLoop function again."
This creates an endless loop: gameLoop calls requestAnimationFrame,
which calls gameLoop again, which calls requestAnimationFrame again...
forever! This is what keeps the game running.
Here's a ball that bounces around the screen using our game loop:
A bouncing ball! It reverses direction when it hits a wall.
The ball has a position (x and y) and a speed
(speedX and speedY). Each frame, we add the speed to the position.
When the ball hits a wall, we reverse its speed (multiply by -1):
let ballX = 100, ballY = 100;
let speedX = 3, speedY = 2;
function gameLoop() {
// Clear
ctx.clearRect(0, 0, 600, 250);
// Update - move the ball
ballX += speedX;
ballY += speedY;
// Bounce off walls
if (ballX < 20 || ballX > 580) speedX = speedX * -1;
if (ballY < 20 || ballY > 230) speedY = speedY * -1;
// Draw the ball
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(ballX, ballY, 20, 0, Math.PI * 2);
ctx.fill();
requestAnimationFrame(gameLoop);
}
gameLoop();
ctx.arc(x, y, radius, startAngle, endAngle) draws a circle!
x, y — Center position of the circleradius — How big the circle is0, Math.PI * 2 — Draws a full circle (from 0 to 360 degrees)
We use ctx.beginPath() before the arc and ctx.fill() after to actually draw it.
Let's make something that looks more like a game — a moving character with a scrolling background! We can have a character walking across a scene with blocks:
A character walking across a block landscape! (It loops around)
let characterX = 0;
function gameLoop() {
ctx.clearRect(0, 0, 600, 250);
// Sky
ctx.fillStyle = '#87ceeb';
ctx.fillRect(0, 0, 600, 250);
// Move the character to the right
characterX += 1.5;
if (characterX > 620) characterX = -30; // Loop around
// Draw ground blocks
for (let x = 0; x < 600; x += 32) {
drawBlock(x, 192, 'grass');
drawBlock(x, 224, 'dirt');
}
// Draw the character at its current position
drawPlayer(characterX, 136);
requestAnimationFrame(gameLoop);
}
ctx.clearRect() line in your own code. You'll see the old drawings
pile up because we never erase them. Always clear first!
Sometimes we want to know exactly how fast our game is running. The number of frames drawn per second is called FPS (Frames Per Second). Most games aim for 60 FPS — that's 60 drawings every second!
let lastTime = 0;
let fps = 0;
function gameLoop(currentTime) {
// Calculate FPS
fps = Math.round(1000 / (currentTime - lastTime));
lastTime = currentTime;
// ... rest of game loop ...
// Show FPS on screen
ctx.fillStyle = '#fff';
ctx.font = '14px Consolas';
ctx.fillText('FPS: ' + fps, 10, 20);
requestAnimationFrame(gameLoop);
}
speedX = 8?-1.5 instead of 1.5)clearRect and see what happens!fillStyle when speed reverses)requestAnimationFrame tells the browser to call our function againclearRect erases the canvas before each new frame