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);
}
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();
}
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();
}
Written by Christian Weber
Related protips
4 Responses
Very nice! Didn't know this. To bad the cross browser compatibility is not 100% yet =(
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);
};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.
i think your clearRect syntax should be like this cx.clearRect(0,0,cw,ch);