Over the last year I’ve seen a good number of published attempts at HTML/JavaScript games, but it is pretty appalling to me how poorly these things run. The games either stutter chaotically or just simply run way too slow to be of any use (or fun!) It seems clear that the folks doing the programming are very used to machine-native coding where anything goes and you can stuff tons of huge graphics effects in because everything’s being rendered in hardware. Moore’s law is usually pointed to as a reason that this doesn’t matter — next year systems will be fast enough to digest all of that JavaScript at 30fps — but that’s just an excuse for programmers to code poorly.
The first problem is the misunderstanding of Object Oriented Programming. In one sense, OOP is an additional layer of structure laid into the code. Once upon a time coders had to lay these structures out on paper before entering the resulting code into a card reader or perhaps more recently, a 6502 assembler. As far as the computer goes, it’s all the same thing — OOP doesn’t have to exist in the code or even at all, it’s merely a way for us to abstract the programming process in an easy-to-understand way.
Though we can’t directly calculate the CPU cycles required to execute a given function in JavaScript, we can easily show how some code-paths are inherently more time-consuming than others. Let’s take an example of a loop which renders all of the game entities to an HTML canvas element — first in the style of OOP with classes and the like.
gameObject.renderSprites() {
var i;
for(i=0; i < this.sprites.length; ++i) {
if(this.sprites[i].getPosX() < 0 || this.sprites[i].getPosX > this.screenWidth()) {
this.sprites[i].doStuff();
}
... etc ...
}
}
The first problem here is we’re calling a function to return our data instead of just grabbing it ourselves. Why would we want to prevent access to the data that we’re using? Is your class really checking if the caller has access, and why would you care? Let’s fix this first off.
if(this.sprites[i].x ...
Okay, now there’s the problem of dereferencing… each call to our sprite object requires the browser to look up the object gameObject, then the property sprites[] and finally it has to find item i within that array. Considering the number of times a working game will refer to a given entity in this sort of loop (often in the hundreds) this is an extremely wasteful practice. How about we simply make a copy of the item we’re going to work on as a local variable, use it for our manipulations and then copy it back:
gameObject.renderSprites() {
var i,j;
for(i=0; i < this.sprites.length; ++i) {
j = this.sprites[i];
if(j.x < 0 || j.x > this.screenWidth()) {
j.doStuff();
}
this.sprites[i] = j;
}
}
That’s starting to be pretty efficient, but we’re still having to dereference the gameObject. There’s really no need for this in JavaScript unless you’re terrified someone will open up FireBug and look at all of your variables in the root node. Oh dear, just look at all those variables that could have been snugly wrapped together in a less obtrusive object! Well this just won’t do — get rid of the gameObject and while you are at it get rid of any function calls that you can:
renderSprites() {
var i,j,sl = sprites.length;
for(i=0; i < sl; ++i) {
j = sprites[i];
if(j.x < 0 || j.x > 512) {
j.theta = 2*(1 - j.theta);
}
sprites[i] = j;
}
}
Notice also the effective speed increase by only asking for the length of the array sprites once as variable sl. Many programmers never realize that the original for() loop takes up to n^2 times longer simply because of the need to recount the array.
As has been shown here, a game coder cannot just hope that the magic of jsMinifier(TM) will in any way increase the efficiency of the code. It is good to shorten variable names and get rid of white-space but be wary of allowing someone else touch your now extremely fast bit of JavaScript!
…to be continued..