Vector graphics are convenient in games, especially for a programmer
with little artistic skill such as myself. But there is only so much
you can do with them. Sooner or later, you're going to need raster
images, and those have an interesting characteristic: being external
to the code, they have to be loaded explicitly; we call them assets.
Now, in the general case, that's not an issue; you simply load the
assets before starting the game. But web browsers load images
asynchronously. And so it happens that the tech demo for Ballistic Snowballz was only displaying a blank background on first page load.
The solution was obvious: write a preloader.
The loader proper turned out to be easy to write. Worked out of the
box, too. No, I can't believe it either, but it did. Then again, we're
talking 24 lines of code:
var Utils = {};
Utils.ImgLoader = function () {
this.onprogress = function () {};
this.oncompletion = function (media) {};
this.loaded = this.total = 0;
this.load = function (media) {
var loader = this;
for (i in media) {
if (typeof media[i] != "string") continue;
this.total++;
var fn = media[i];
media[i] = new Image();
media[i].src = fn;
media[i].onload = function () {
loader.loaded++;
loader.onprogress();
if (loader.loaded == loader.total)
loader.oncompletion(media);
}
}
}
}
Using it turned out to be a little trickier, but worked in the end.
Snowballz.GameScreen = function (model, context, width, height) {
// ... snip ...
this.pine_tree = this.snowman = new Image(); // Placeholder object.
this.loader = null;
this.load_media = function() {
var scr = this;
this.loader = new Utils.ImgLoader();
this.loader.onprogress = function () {
if (scr.game_paused) scr.render();
}
this.loader.oncompletion = function (media) {
scr.pine_tree = media.pine_tree;
scr.snowman = media.snowman;
scr.loader = null;
if (scr.game_paused) scr.render();
}
this.loader.load({
snowman: "assets/plastic-snowman.png",
pine_tree: "assets/plastic-pine-tree.png",
});
}
// ... snip ...
}
Then there is the code that calls load_media()
before
the game starts, the code that shows progress, and the code that makes
sure the media is loaded before trying to use it. This last part is
important if you're going to use the images on a canvas element, like
I do: canvases, being native components, don't check their arguments
nearly as thoroughly as you might be used to.
Come to think of it, an even better approach would be to not even
instantiate the GameScreen object until all the media is loaded, but I
didn't want to make too many changes; this is only a prototype after
all. I'll do it right the first time around for the next game.