This article was peer reviewed by Andrew Ray and Sebastian Seitz. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
The “game loop” is a name given to a technique used to render animations and games with changing state over time. At its heart is a function that runs as many times as possible, taking user input, updating the state for the elapsed time, and then drawing the frame.
In this short article you’ll learn how this fundamental technique works and you’ll be able to start making your own browser based games and animations.
Here’s what game loop in JavaScript looks like:
function update(progress) {
// Update the state of the world for the elapsed time since last render
}
function draw() {
// Draw the state of the world
}
function loop(timestamp) {
var progress = timestamp - lastRender
update(progress)
draw()
lastRender = timestamp
window.requestAnimationFrame(loop)
}
var lastRender = 0
window.requestAnimationFrame(loop)
The requestAnimationFrame method requests that the browser call a specified function as soon as it can before the next repaint occurs. It’s an API specifically for rendering animations but you can also use setTimeout
with a short timeout for a similar result. requestAnimationFrame
is passed a timestamp of when the callback started firing, it contains the number of milliseconds since the window loaded and is equal to performance.now().
The progress
value, or time between renders is crucial for creating smooth animations. By using it to adjust the x and y positions in our update
function, we ensure our animations move at a consistent speed.
Updating the Position
Our first animation will be super simple. A red square that moves to the right until it reaches the edge of the canvas and loops back around to the start.
We’ll need to store the square’s position and increment the x position in our update
function. When we hit a boundary we can subtract the canvas width to loop back around.
var width = 800
var height = 200
var state = {
x: width / 2,
y: height / 2
}
function update(progress) {
state.x += progress
if (state.x > width) {
state.x -= width
}
}
Drawing the New Frame
This example uses the element for rendering the graphics but the game loop can be used with other outputs like HTML or SVG documents too.
The draw
function simply renders the current state of the world. On each frame we’ll clear the canvas and then draw a 10px red square with its center at the position stored in our state
object.
var canvas = document.getElementById("canvas")
var width = canvas.width
var height = canvas.height
var ctx = canvas.getContext("2d")
ctx.fillStyle = "red"
function draw() {
ctx.clearRect(0, 0, width, height)
ctx.fillRect(state.x - 5, state.y - 5, 10, 10)
}
And we have movement!
See the Pen Game Loop in JavaScript: Basic Movement by SitePoint (@SitePoint) on CodePen.
Note: In the demo you might notice that the size of the canvas has been set in both the CSS and via width
and height
attributes on the HTML element. The CSS styles set the actual size of the canvas element that will be drawn to the page, the HTML attributes set the size of the coordinate system or ‘grid’ that the canvas API will use. See this Stack Overflow question for more information.
Next we’ll get keyboard input to control the position of our object, state.pressedKeys
will keep track of which keys are pressed.
var state = {
x: (width / 2),
y: (height / 2),
pressedKeys: {
left: false,
right: false,
up: false,
down: false
}
}
Let’s listen to all keydown and keyup events and update state.pressedKeys
accordingly. The keys I’ll be using are D for right, A for left, W for up and S for down. You can find a list of key codes here.
var keyMap = {
68: 'right',
65: 'left',
87: 'up',
83: 'down'
}
function keydown(event) {
var key = keyMap[event.keyCode]
state.pressedKeys[key] = true
}
function keyup(event) {
var key = keyMap[event.keyCode]
state.pressedKeys[key] = false
}
window.addEventListener("keydown", keydown, false)
window.addEventListener("keyup", keyup, false)
Then we just need to update the x and y values based on the pressed keys and ensure that we keep our object within the boundaries.
function update(progress) {
if (state.pressedKeys.left) {
state.x -= progress
}
if (state.pressedKeys.right) {
state.x += progress
}
if (state.pressedKeys.up) {
state.y -= progress
}
if (state.pressedKeys.down) {
state.y += progress
}
// Flip position at boundaries
if (state.x > width) {
state.x -= width
}
else if (state.x < 0) {
state.x += width
}
if (state.y > height) {
state.y -= height
}
else if (state.y < 0) {
state.y += height
}
}
And we have user input!
See the Pen Game Loop in Javascript: Dealing with User Input by SitePoint (@SitePoint) on CodePen.
Asteroids
Now that we have the fundamentals under our belt we can do something more interesting.
It’s not that much more complex to make a ship like was seen in the classic game Asteroids.
Our state
needs to store an additional vector(an x,y pair) for movement as well as a rotation for the ships direction.
var state = {
position: {
x: (width / 2),
y: (height / 2)
},
movement: {
x: 0,
y: 0
},
rotation: 0,
pressedKeys: {
left: false,
right: false,
up: false,
down: false
}
}
Our update
function needs to update three things:
- rotation based on the left/right pressed keys
- movement based on the up/down keys and rotation
- position based on the movement vector and the boundaries of the canvas
function update(progress) {
// Make a smaller time value that's easier to work with
var p = progress / 16
updateRotation(p)
updateMovement(p)
updatePosition(p)
}
function updateRotation(p) {
if (state.pressedKeys.left) {
state.rotation -= p * 5
}
else if (state.pressedKeys.right) {
state.rotation += p * 5
}
}
function updateMovement(p) {
// Behold! Mathematics for mapping a rotation to it's x, y components
var accelerationVector = {
x: p * .3 * Math.cos((state.rotation-90) * (Math.PI/180)),
y: p * .3 * Math.sin((state.rotation-90) * (Math.PI/180))
}
if (state.pressedKeys.up) {
state.movement.x += accelerationVector.x
state.movement.y += accelerationVector.y
}
else if (state.pressedKeys.down) {
state.movement.x -= accelerationVector.x
state.movement.y -= accelerationVector.y
}
// Limit movement speed
if (state.movement.x > 40) {
state.movement.x = 40
}
else if (state.movement.x < -40) {
state.movement.x = -40
}
if (state.movement.y > 40) {
state.movement.y = 40
}
else if (state.movement.y < -40) {
state.movement.y = -40
}
}
function updatePosition(p) {
state.position.x += state.movement.x
state.position.y += state.movement.y
// Detect boundaries
if (state.position.x > width) {
state.position.x -= width
}
else if (state.position.x < 0) {
state.position.x += width
}
if (state.position.y > height) {
state.position.y -= height
}
else if (state.position.y < 0) {
state.position.y += height
}
}
The draw
function translates and rotates the canvas origin before drawing the arrow shape.
function draw() {
ctx.clearRect(0, 0, width, height)
ctx.save()
ctx.translate(state.position.x, state.position.y)
ctx.rotate((Math.PI/180) * state.rotation)
ctx.strokeStyle = 'white'
ctx.lineWidth = 2
ctx.beginPath ()
ctx.moveTo(0, 0)
ctx.lineTo(10, 10)
ctx.lineTo(0, -20)
ctx.lineTo(-10, 10)
ctx.lineTo(0, 0)
ctx.closePath()
ctx.stroke()
ctx.restore()
}
That’s all the code that we need to re-create a ship like in Asteroids. The keys for this demo are the same as in the previous one (D for right, A for left, W for up and S for down).
See the Pen Game Loop in JavaScript: Recreating Asteroids by SitePoint (@SitePoint) on CodePen.
I’ll leave it to you to add the asteroids, bullets and collision detection 😉
Level Up
If you have found this article interesting you will enjoy watching Mary Rose Cook live-code Space Invaders from scratch for a more complex example, it’s a few years old now but is an excellent intro to building games in the browser. Enjoy!