A small library for running code with separated update and render functions with RequestAnimationFrame. The Update function can be paused, resumed, or run at customisable rates by passing in a number to multiply the RAF delta by (slow-motion, double speed etc.).
This library exposes separate update and render functions to encourage separation of game state logic and rendering logic. Where entities in your animation or game are and how they are rendered are separate concerns, so by separating the code into different functions we can avoid coupling them unnecessarily.
The update function is called from inside requestAnimationFrame, but we do not want the update rate to be tied to the render rate. An animation should run at the same rate whether viewed on a 30hz monitor or a 244hz monitor.
To ensure consistent animation regardless of render speed the update
function is passed a dt
parameter that
represents the time in seconds since the last time the update function was called. Any calculations that result in
something moving should be multiplied by this value to guarantee that it will move that far per second.
Let's say you want a square to move right at 100 pixels per second. If you assume 60fps you divide 1000 by 60 to get 16.6, so you write
function update() {
box.x += 16.6;
}
On a computer with a 60hz monitor you'll see the behaviour you're expecting, maybe with a little hitching but eh that's fine. Then you view it on a 144hz monitor and your square has gone zooming off the screen because it's add 16. 6 pixels to it 144 times a second instead of 60.
So what you do instead is you define all of your units in units per second and multiply that by the time since the last update!
function update(deltaTime) {
box.x += 100 * deltaTime;
}
On a 60hz monitor deltaTime
will be 0.166 and 100 * 0.166 = 16.6 pixels per update.
On a 144hz monitor deltaTime
will be 0.024, bringing us 2.4 pixels per update.
The delta time is calculated every frame so if your browser runs one frame slower than the others for whatever reason the dt will change appropriately and everything should remain consistent. This also holds for the browser being unable to keep up with the max refresh rate - if you create 1,000,000 boxes your animation will likely run at far fewer frames per second but the box movement will continue at the same rate.
import * as simpleGameloop from 'simple-gameloop';
/**
* @param dt - time in seconds since last call to update function
*/
const update = function update(dt) {
// your update logic here
};
const render = function render() {
// your render logic here
};
const gameLoop: GameLoop = simpleGameloop.createLoop({
update,
render,
});
createLoop
returns an object with the following functions:
-
pause()
- blocks the update function -
play()
- unblocks the update function -
destroy()
- cancels the current animation frame, halting playback completely -
setSpeed(number)
- speed up or slow down the update call by multiplying the delta by the given number
There is also a helper function for creating a canvas element of a given size.
const canvasObject = simpleGameloop.createCanvas({
containerSelector: '.canvasContainer',
classes: 'canvas other-class',
width: 500,
height: 500,
});
This will create a 500x500 canvas inside the .canvasContainer
element with the classes canvas
and other-class
.
canvasObject
contains references to:
-
canvas
the canvas element -
context
the canvas context (e.g.canvas.getContext('2d')
).
There is a demo that you can run with npm run example
.
If you want your simulation to be deterministic then you want a fixed update rate. This can be done by tracking how much time has progressed and calling update when X time has passed, passing X in as the delta.