Spicy Yoghurt | Last updated: 9 March 2019 | HTML5 game tutorial
Create a proper game loop
Create a proper game loop in JavaScript and learn about frame rates. Measure and display fps to see your loop in action. Request animation frames and have your own loop running by the end of this HTML5 game tutorial.
Why do you need a game loop?
In the previous tutorial, you've created an application that draw's a rectangle on an HTML5 canvas. This is super nice and all, but that drawing operation is only performed once. If you want make it appear as if the rectangle is moving, and eventually create an actual game, you need to draw more. A lot more.
To achieve this, you'll need a loop to repeatedly execute your game logic. In the world of games, this loop is called a game loop. It'll be the core of your game and trigger the drawing operations. You'll no longer be drawing a single static image, but many images will be drawn after each other, creating motion.
Here's an example with a moving circle. It shows five frames in a row, each with a slightly different position of the circle. The five frames resemble five iterations of your game loop logic. Moving the circle in small steps, one quickly after another, creates the effect of motion.
The benefits of a high frame rate
As you might know, with games, people talk about how many fps they can reach with their gaming rig. Fps stands for frames per second. Each time you draw your game objects on the screen, counts as a frame.
Fps is the number of times per second you can draw your game on the screen. In general, the better your hardware the higher your fps will be. And the higher your fps, the smoother your game will play. Also, the delay between frames will become smaller, making the visible state of the game more up to date. This gives you a possible advantage in game. That's why professional gamers want to squeeze out every frame they can get and spend a lot on the best possible hardware.
The ideal frame rate for your game loop
Humans can process 10 to 12 frames per second individually. Any more frames than that and you will perceive it as being a motion and that is what you want for your game. Of course, a game running at 12 fps would be extremely choppy. You might see it as a motion, but you need more frames if you want to make it look smooth. Movies generally use 24 fps, but most games run at even more frames than that. If more is better, why not just aim to run your game at 120 fps and make it ultra-smooth?
Well, there is also the refresh rate of your display to take into account. Running a game at 120 fps on a display that's 60Hz (that's a refresh rate of 60 times per second) won't do much good. You'll have more frames to draw than your display can handle. The excess frames will never be displayed and only cost you system resources. Modern displays support a higher refresh rate, so you'll need one of those if you want to fully utilize all 120 frames each second. But an HTML5 game is not just for high-end desktops rigs with great displays, it has to run and perform well on mobile devices too.
A fps that is equal to that of the refresh rate of your display would be ideal. It will offer the smoothest experience and the game state will be up to date the most. And at the same time, you want to limit the maximum fps in some amount, to prevent your game from using too much system resources and be a battery drainer on mobile devices.
So, to sum it all up, this is what you'll be looking for with your game loop when creating an HTML5 game:
- A frame rate that is as high as possible for smoothness
- A frame rate that isn't higher than the screen refresh rate
- A frame rate that doesn't uses too much system resources
Let's check out some options and see how well they fit these requirements. You are going to find the perfect loop for your game and learn about common mistakes.
Set-up a game loop
Ok, so you need a game loop? Why not just use this simple while loop in your JavaScript code?
// A bad game loop
while (running) {
draw();
}
The loop just starts up and draws indefinitely, until something tells it to stop running, like when pausing the game. So, you will get the maximum potential out of your system and reach as much fps as your hardware allows.
But there is a small problem. JavaScript runs on a single thread per browser tab. With the while loop, every bit of available system resources will go into performing the drawing operations, again and again. This will render your browser unable to perform other tasks, like managing user input or other important events. When you let it run for a while, it will hang your browser and eventually give the famous 'this page is not responding' warning.
To fix this, you'll need to give the browser some air while performing the game loop. You could use something like setInterval() to loop with a set amount of time for each frame. This will put some time between each drawing operation, giving the system space to perform other tasks than drawing.
// Another bad game loop
setInterval(gameLoop, 16);
function gameLoop() {
draw();
}
With an interval of 16ms, this game loop will reach about 60fps. This will perform better than the while loop but there is no guarantee that the browser is ready to perform a repaint when the gameLoop() function is triggered. You might calculate frames that are never put on your display. How to fix this?
The proper JavaScript game loop
You need a way to give the browser some air while looping and time your game loop to be in-sync with the browser repaint. Luckily there is a solution. You can use window.requestAnimationFrame() to tell the browser you want to request a repaint for an animation, or in this case a game.
// The proper game loop
window.requestAnimationFrame(gameLoop);
function gameLoop() {
draw();
window.requestAnimationFrame(gameLoop);
}
The browser will perform a callback function on its own time. Meaning it won't hang the system. Also, when a browser tab is no longer focused, the browser might reduce the number of callbacks, to reduce system load. This will improve the battery life of the device it is run on, for example.
Do you remember the paragraph about frame rates? Well, the browser takes care of picking a suitable fps for the device it is running on. This usually means your game loop will run at 60fps, but will generally match your display refresh rate. The requestAnimationFrame() function takes care of that.
The method meets all the requirements for a proper game loop: the loop runs at a frame rate that will make the game look smooth, takes in account the refresh rate of the screen and reduces the use of system resources. It's the perfect match to use for your own HTML5 game.
How to use requestAnimationFrame()?
The function window.requestAnimationFrame() accepts a callback function as argument. You can pass your gameLoop() function in there. When the browser is ready it will execute that function.
You will have to start the game loop by calling window.requestAnimationFrame() once, and then keep on calling it inside the loop. It's easier to understand in the next example:
The callback from requestAnimationFrame() is given one argument, and that's a time stamp containing the current time. Its value is the same as you would get from calling performance.now(), but it's given for free this way. You can access the timestamp from your gameLoop() function.
You won't be doing anything with the time stamp right now. You'll learn more about using time as a factor in your game loop in the next tutorial about canvas animations. It's easier to explain things there, when you have moving objects. Just remember you can use it later on and that it's important for calculating motions.
Check out the running game loop
Let's try out your new game loop. When running this code, you see a big difference compared to the previous tutorial with only one call to the draw() function. draw() is now called many times in a row. The rectangle is redrawn roughly 60 times per second, depending on the device you run the loop on.
Here's a schematic view of your game loop. It will be expanded with more tasks as you progress through this tutorial series.
The color of the rectangle is still random. So, a new random color is picked every frame. That's why you now see a flashy rectangle that is switching very fast between red and blue. This makes it easier to see the game loop is actually working (but isn't very pleasant to look at).
Measure and display fps
Well done, you've made a game loop and it's working. But wouldn't it be nice if you could measure its performance and check out how much frames per second it produces? Let's try to calculate the current fps by using the timestamp parameter provided in the game loop.
First start by calculation the time that has passed since the previous frame. You can do that by subtracting the timestamp of the previous frame from the current timestamp. The time that's left is the number of milliseconds since the last frame.
Divide this number by 1000 to go from milliseconds to seconds. It takes this number of seconds to produce one frame. Divide 1 by this number to get the number of frames per second.
Here is an example of a very simple implementation that uses a fillText() drawing operation, like handled in the canvas drawing tutorial, to draw the fps as text to the screen:
let secondsPassed;
let oldTimeStamp;
let fps;
function gameLoop(timeStamp) {
// Calculate the number of seconds passed since the last frame
secondsPassed = (timeStamp - oldTimeStamp) / 1000;
oldTimeStamp = timeStamp;
// Calculate fps
fps = Math.round(1 / secondsPassed);
// Draw number to the screen
context.fillStyle = 'white';
context.fillRect(0, 0, 200, 100);
context.font = '25px Arial';
context.fillStyle = 'black';
context.fillText("FPS: " + fps, 10, 30);
// Perform the drawing operation
draw();
// The loop function has reached it's end. Keep requesting new frames
window.requestAnimationFrame(gameLoop);
}
You can run the code to check the fps meter and see if your game loop is functioning properly. If you view this page on a desktop device, it should hit a nice 60 frames per second.
The fps is updated every frame, so it might get a bit hard to read, but it's fine for this demonstration. If you like, you could put more time into it and think of ways to make the fps get updated less often. For example, you could buffer the fps and only update it once every second. But for now, this is all on fps meters.
What's next?
That's all on game loops for now. Your JavaScript game loop is in place and the rectangle gets redrawn many times per second. You understand the relation between fps and a game loop and you can measure the fps of your own game. If you have any questions, feel free to ask them in the comment section. You can download the example code here.
Spicy Yoghurt is here to help you create your own games and inspire you. Building the website takes a lot of spare time and coffee.
We supply the spare time and you can support us with a cup of coffee!
In the next step of the tutorial, the rectangle will come more alive. You will be learning how to add motion and make animations on the HTML5 canvas. Also, you'll learn about the effects of frame rate on animations.
Leave a comment