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' to
string' 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);