Game Loop in JavaScript
Loops in JS.
There are several posibilities to organize loops in JavasCript, the most common are:
- for statement
- do … while statement
- while statement
They are pretty known, therefore, I think, I will not describe them here. What I really want to point here is that these statements do not perfectly suit requirements of game development. There are a lot of things that should be built around these statements manually; the main of them is frames synchronization (for more details on the synchronization please see Computer Graphics article).
If the default language loops can not help us, what should we do? And, here is the answer: use setInterval and requestAnimationFrame functions.
setInterval function.
What should we know about this function?
The setInterval() method, offered on the Window and Worker interfaces, repeatedly calls a function or executes a code snippet, with a fixed time delay between each call.
— MDN web docs.
Fixed time delay between each call
sounds amazing! It means that we can setup frames rate and be sure that each call will redraw a game state with the same interval. Lets see how we can create a game loop with its help:
//js
setInterval(function(){
processInput();
changeState();
render();
},
1000/60); //60 frames per second
Cool, We’re done! But wait… Lets see, 1000 / 60 = 1.6666666666666666... ms
that is not equal to 1/60th of second. It will be rounded to some closest value, but not the same. Moreover, if your code requires more than 1.6666666666666666... ms
to be finished, the next loop turn will be run as soon as the previous is finished. In such a way, the frame rate will be affected. To examine this behavior, I propose to create a simple html page and add the following.
<!--HTML-->
<div class="js-example">
<span class="clear-frame-rate">FPS: 0</span>
</div>
<script type="text/javascript">
var last;
var draw = function(){
var time = performance.now();
var frameTime = (time - last).toFixed(2);
last = time;
var span = document.getElementsByClassName('clear-frame-rate')[0];
span.innerText = 'FPS: ' + Math.round(1000/Number(frameTime));
}
var int = setInterval(draw, 1000/60);
</script>
If everything is done correctly, you should see random numbers as in this interactive example:
Unlike our code, the interactive example can emulate delay in 20 milliseconds to provide you with understanding what frame rate will be in case where execution of code takes more that 1/60th of a second.
Anyway, the shown frame rates (with and without a delay) can lead to flikers and frame skips as it was discussed in the Computer Graphics article. Also, we need to think how we can synchronize several animations on the same page, each of which is run in its own setInterval function.
How to deal with it? Nohow! Because it is not needed, JavaScript provides another amazing function called requestAnimationFrame.
requestAnimationFrame function.
What is it?
The window.requestAnimationFrame() method tells 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.
— MDN web docs.
I have re-read this definition several times and each time its meaning slinked away. Let’s dive deeper here to undestand it better.
Each page opened in a browser has a document associated with a browsing context. Each document has a list of animation frame callbacks and single animation frame callback identifier. Initially, the list is empty and the identifier equals zero. When we define requestAnimationFrame function in our code, we pass a callback as an argument of the function. This call back is pushed to the animation frame callbacks list. In the definition this process is mentioned as tells the browser that you wish to perform an animation
.
At the same time the browser regularly schedules a single task that samples all animations in the browser context. The task loops through each page and checks its visibility property. If a page is visible, the tasks look into the list of animation frame callbacks and invoke them side by side. If there are many callbacks or CPU is hard loaded, the browser can select such a frame rate so that all of the animations can be run as smoothly as possible. When the task is finished, the animation frame callback identifier is increased by one. In the definition this process is mentioned as the browser call a specified function to update an animation before the next repaint
.
Do you see all the benefits provided by the function?
- We don’t need to worry about the frames synchronization
- All animations will be run at the same time
- If our page is not active right now, the animation will not be run at all
Lets see how it works. For this we need to remove setInterval function and re-write our draw method.
//js
var draw = function(time){
var frameTime = (time - last).toFixed(2);
last = time;
var span = document.getElementsByClassName('clear-frame-rate')[0];
span.innerText = 'FPS: ' + Math.round(1000/Number(frameTime));
if (cFrame < mFrames){
window.requestAnimationFrame(draw);
}
}
If you run the code, you should see something like this:
Game Loop.
Now we understand that the best way to organize a game loop in JavaScript is to utilize requestAnimationFrame. Let’s see how it will look like:
//js
var gameLoop = function(){
processInput();
changeState();
render();
window.requestAnimationFrame(gameLoop);
}
window.requestAnimationFrame(gameLoop);
Pretty simple, don’t you find?