Key Takeaways
- The game loop in JavaScript is a technique used to render animations and games with changing state over time, involving a function that runs repeatedly, taking user input, updating the state for the elapsed time, and then drawing the frame.
- The ‘requestAnimationFrame’ method is an API used for rendering animations, which requests the browser to call a specified function as soon as it can before the next repaint occurs, providing a timestamp of when the callback started firing.
- User input can be integrated into the game loop by keeping track of which keys are pressed, listening to all keydown and keyup events, and updating the x and y positions based on the pressed keys, ensuring the object stays within the boundaries.
- More complex animations can be created by storing additional vectors for movement and rotation, updating the rotation based on the left/right pressed keys, updating movement based on the up/down keys and rotation, and updating position based on the movement vector and the boundaries of the canvas.
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 <canvas>
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.
Responding to User Input
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!
Frequently Asked Questions (FAQs) about Game Loop in JavaScript
What is the purpose of a game loop in JavaScript?
The game loop is a fundamental part of any game. It’s a loop that continuously runs during the game, updating the game state and rendering the changes on the screen. In JavaScript, the game loop is used to create animations by updating the game elements and re-rendering them on the canvas. It’s the heart of every game, controlling the flow and ensuring that the game runs smoothly.
How does the requestAnimationFrame function work in a game loop?
The requestAnimationFrame function is a built-in JavaScript method that is used to create smooth animations. It works by telling the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. This method is more efficient than using setInterval or setTimeout for animations, as it pauses when the user navigates to another browser tab, hence not wasting their processing power.
What is the difference between a fixed and a variable game loop?
A fixed game loop updates at a set frequency, regardless of how fast or slow the computer or device is. This ensures consistency in the game’s speed across different devices. On the other hand, a variable game loop adjusts its speed based on the device’s performance. It updates as often as it can, taking into account the frame rate. While a variable game loop can provide smoother animations on high-performance devices, it can lead to inconsistencies in the game’s speed.
How can I handle user input in a game loop?
User input in a game loop can be handled using event listeners. You can set up event listeners for key presses, mouse movements, and other user interactions. These event listeners can then update the game state based on the user’s actions. It’s important to remember to handle user input in the update part of the game loop, not the render part, to ensure that the game state is updated before it’s rendered on the screen.
How can I optimize my game loop for better performance?
There are several ways to optimize your game loop for better performance. One way is to use the requestAnimationFrame function, which optimizes the game loop by pausing it when the user navigates to another browser tab. Another way is to limit the number of updates per second, which can prevent the game loop from running too fast on high-performance devices. You can also optimize your game loop by minimizing the amount of work done in each iteration, such as by avoiding unnecessary calculations or function calls.
How can I handle different frame rates in my game loop?
Handling different frame rates in a game loop can be challenging, but it’s essential for ensuring that your game runs smoothly on different devices. One way to handle different frame rates is to use a variable game loop, which adjusts its speed based on the frame rate. Another way is to use delta time, which is the time difference between the current and the previous frame. By multiplying your game update values by delta time, you can ensure that your game runs at the same speed regardless of the frame rate.
What is the role of the update function in a game loop?
The update function in a game loop is responsible for updating the game state. This includes updating the positions of game elements, checking for collisions, handling user input, and so on. The update function is called in each iteration of the game loop, before the render function. It’s important to keep the update function as efficient as possible to ensure that the game loop runs smoothly.
How can I use the game loop to create animations?
You can use the game loop to create animations by updating the game elements in each iteration of the loop and then re-rendering them on the canvas. For example, you can create an animation of a moving object by updating the object’s position in each iteration of the game loop and then drawing the object at its new position. The speed of the animation can be controlled by the speed of the game loop.
Can I use the game loop for game logic and AI?
Yes, the game loop can be used for game logic and AI. The update function in the game loop is the perfect place to implement game logic, such as checking for collisions, updating scores, or changing game states. AI can also be implemented in the update function, where you can update the actions of AI-controlled characters based on their current state and the state of the game.
How can I stop or pause the game loop?
You can stop or pause the game loop by stopping the requestAnimationFrame function. This can be done by storing the ID returned by requestAnimationFrame in a variable and then passing this ID to the cancelAnimationFrame function. This will stop the next request for an animation frame, effectively pausing the game loop. To resume the game loop, you can simply call requestAnimationFrame again.
Hello. I'm a front end web developer from Melbourne, Australia. I enjoy working on the web, appreciate good design and working along side talented people I can learn from. I have a particular interest in visual programming so have fun working with SVG and Canvas.