iygcpa
Last Updated: April 22, 2017
·
9.912K
· bahlor
7977b31672f2861a35b7979447df2801

Gameloop - the correct way

On many pages on the web, people still tend to use setInterval() as main function of a gameloop. This is not the best way to do this. Modern browsers are capable of telling you, as soon as they're ready to render new output using requestAnimationFrame.

Basic example using setInterval()

var canvas = document.getElementById('canvas'),
    cw = canvas.width,
    ch = canvas.height,
    cx = null,
    fps = 30,
    bX = 30,
    bY = 30,
    mX = 10,
    mY = 20;

function gameLoop() {
    cx.clearRect(0,0,cw,cw);

    cx.beginPath();
    cx.fillStyle = 'red';
    cx.arc(bX, bY, 20, 0, Math.PI * 360);
    cx.fill();
    if(bX >= cw || bX <= 0) { mX*=-1; }
    if(bY >= ch || bY <= 0) { mY*=-1; }

    bX+=mX;
    bY+=mY;
}

if (typeof (canvas.getContext) !== undefined) {
    cx = canvas.getContext('2d');

    setInterval(gameLoop, 1000 / fps);
}

jsFiddle Demo

The same using requestAnimationFrame

var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}

var canvas = document.getElementById('canvas'),
    cw = canvas.width,
    ch = canvas.height,
    cx = null,
    fps = 30,
    bX = 30,
    bY = 30,
    mX = 150,
    mY = 300,
    lastTime = (new Date()).getTime(),
    currentTime = 0,
    delta = 0;

function gameLoop() {
    window.requestAnimationFrame(gameLoop);

    currentTime = (new Date()).getTime();
    delta = (currentTime - lastTime) / 1000;
    cx.clearRect(0, 0, cw, cw);

    cx.beginPath();
    cx.fillStyle = 'red';
    cx.arc(bX, bY, 20, 0, Math.PI * 360);
    cx.fill();
    if (bX >= cw || bX <= 0) {
        mX *= -1;
    }
    if (bY >= ch || bY <= 0) {
        mY *= -1;
    }

    bX += (mX * delta);
    bY += (mY * delta);

    lastTime = currentTime;
}

if (typeof (canvas.getContext) !== undefined) {
    cx = canvas.getContext('2d');

    gameLoop();
}

jsFiddle Demo

Another method limiting the rendering to a maximum fps

var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame =
      window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}

var canvas = document.getElementById('canvas'),
    cw = canvas.width,
    ch = canvas.height,
    cx = null,
    fps = 30,
    bX = 30,
    bY = 30,
    mX = 10,
    mY = 20,
    interval     =    1000/fps,
    lastTime     =    (new Date()).getTime(),
    currentTime  =    0,
    delta = 0;

function gameLoop() {
    window.requestAnimationFrame(gameLoop);

    currentTime = (new Date()).getTime();
    delta = (currentTime-lastTime);

    if(delta > interval) {

        cx.clearRect(0,0,cw,cw);

        cx.beginPath();
        cx.fillStyle = 'red';
        cx.arc(bX, bY, 20, 0, Math.PI * 360);
        cx.fill();
        if(bX >= cw || bX <= 0) { mX*=-1; }
        if(bY >= ch || bY <= 0) { mY*=-1; }

        bX+=mX;
        bY+=mY;

        lastTime = currentTime - (delta % interval);
    }
}

if (typeof (canvas.getContext) !== undefined) {
    cx = canvas.getContext('2d');

    gameLoop();
}

jsFiddle Demo

Say Thanks
Respond

3 Responses
Add your response

7774
9314495210ae73eb424b73eb1c69b2e7

Very nice! Didn't know this. To bad the cross browser compatibility is not 100% yet =(

over 1 year ago ·
7775
7977b31672f2861a35b7979447df2801

@alebles

Thanks! But with the polyfill by Erik Moeller you're quite ready for older browsers without requestAnimationFrame and up to date browsers, as this includes an alternative setInterval fallback. :)

// requestAnimationFrame polyfill by Erik Moeller
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame =
      window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame)
    window.requestAnimationFrame = function(callback, element) {
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
        var id = window.setTimeout(function() { callback(currTime + timeToCall); },
          timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    };

if (!window.cancelAnimationFrame)
    window.cancelAnimationFrame = function(id) {
        clearTimeout(id);
    };
over 1 year ago ·
28831

When I copied your code, the JS++ compiler gave me this error:

"if (typeof (canvas.getContext) !== undefined) {"

"[ ERROR ] JSPPE5000: Cannot convert void' tostring' at line 44 char 35 at main.jspp"

I think you meant to use the string "undefined" instead of the undefined value. After I fixed this, it worked and I get a red bouncing ball. You should really consider using JS++ to catch errors like this.

7 months ago ·