So I’ve already covered how HTTP is great and how it gives us a great way of delivering assets to our applications (even those not based in the browser), but let us actually look at how we might use it from within a browser based game.
Asynchrony
First things first, this is a really interesting cookie – what is the following code actually doing?
var image = new Image();
var texture = gl.createTexture();
texture.image = image;
image.onload = function(){
// Set up the gl texture
};
image.src = "texture.gif";
And how about this code?
var model = new Model();
$.get('/models/teapot.js', function(data) {
model.setData(data);
});
If you’re familiar with how Javascript works then you’ll know that after image.src, code is going to carry on being executed while the resource loads, and then in the callback provided you’ll get given the data which you can then do stuff with.
Think about that for a second, what we’re saying here is that the game can start while content streams in the background and when those resources have loaded they’ll magically start being used?
Well no – not really, WebGL itself is quite tolerant and while you can set null buffers and null textures all over the place with no dire consequences, it’s not really recommended and you get a truly awful user experience while this all happens.
Most of the WebGL games I’ve come across fall into this pit, and for a few minutes you’re sat there wondering what all the pretty colours and odd shapes on the screen are supposed to represent as the game carries on running oblivious to the fact that none of its assets are loaded yet.
Asset Management
So it’s obvious that you have to actually think about asset management when writing browser based games (especially with WebGL when you have large models and textures to download).
My strategy for this is no different to the strategy used when dealing with assets like these in an offline game, I wrap the resources in types (Model and Texture), and stick code for pulling those assets out (or pushing them in) to a resource manager of some sort which can report back to the application the number of textures/models currently waiting download, so some sort of loading screen can be presented to the user.
All in all, this takes about 50 lines of code for a minimal implementation, because the code for actually loading those assets, the code for actually creating the textures, dealing with the memory, dealing with the fact that they’re all being loaded in the background (potentially on different threads) has already been written for you in the engine executing the JavaScript.
var texture = resources.getTexture('texture.gif');
var model = resources.getModel('teapot.js');
resources.onAllAssetsLoaded(function(){
game.Start();
});
Packaging your resources
So this is an interesting one, in most games I’ve looked at, you tend to have a specialised packed file format in order to have a single handle to a single file which you can easily seek through without going through the expense of seeking multiple files, perhaps compressed – all to minimise loading time when pulling data off disk and sticking it in ram.
You’re limited here to a certain extent, but we can learn from some of this in that we can sensibly package our data.
So, here are some largely uninteresting suggestions that should be obvious to most of us :-)
- We can package our models up via minification/packaging scripts
- We can package our code up similarly
- You can do the same with textures (mega-textures in the browser, has anybody investigated the performance of this?)
The art of unloading your resources
So, what happens when you download your resources What are you actually doing? And just what is a resource anyway?
Now we’re into vaguely interesting territory, if you’re writing a game where all your resources fit comfortably into memory then you haven’t got to worry about anything I have to say here, but if you’re planning on writing something that will be open for more than 15 minutes of casual gaming, and traverse a few miles of level then you’ll want to think about stuff like this when dealing with your resources.
The lifecycle of a resource looks something like this:
- Load the resource from the server/cache as a file [Disk space]
- Load that resource into memory as an meaningful object [Ram]
- Use that resource wherever it is needed
- Dispose of that resource when you no longer need it
- Go to 1 or 2 again when necessary
So, 1, 2 and 3 are well covered by all the tutorials out there on the web, but what of 4 and 5? What do we have to take into consideration when working in our browser with these?
First up, you don’t have deterministic garbage collection in Javascript, you have methods on your WebGL context to create and destroy resources like so:
gl.createBuffer();
gl.deleteBuffer();
gl.createTexture();
gl.deleteTexture();
Which is nice, because it gives us a way of clearing up the memory used between the device and the host, but it doesn’t actually clear up the memory used in loading those textures/buffers in the first place.
Consider the following:
var vertexData = new Float32Array(10000 * 3);
How do you clear that up? How do you delete it? Well it’s quite obvious really when you think about it, with a simple call to
vertexData = null;
Now, this is overly simplistic, garbage collection is up to the individual implementation of the engine concerned, and they’re hardly simple reference counting implementations – but my point is simply that you should be careful with how you’re loading your resources, and who is referencing what. (Another point towards wrapping the big pieces of data behind some nice classes and a resource manager).
This does however make the way we download these models somewhat important.
Consider the following model format that the WebGL exporter gives us (Note: I need to commit some changes to those soon, I have quite a few additions)
var BlenderExport = BlenderExport || {};
BlenderExport.Cube = {};
BlenderExport.Cube.Vertices = [3,3, etc]
BlenderExport.Cube.Indices = [0,1,etc]
What actually happens when you include this file into the application? Well – the file is executed, and when it is excecuted, we’re given a global variable called BlenderExport which contains a variable called Cube which contains a big pile of data as arrays of primitives.
Will this ever get cleared up? Any code could potentially reference this at some point, so it’s unlikely, it’s in the global namespace – anything could reference it at any time, I don’t think it’s going to go out of scope (Please correct me if I’m wrong here! hah – challenge my ignorance indeed).
What I prefer to do is to stick my models in plain old JSON format like so:
{
Vertices: [3,4,3, etc],
Indices: [3,3,4]
}
This can be pulled down via a call to $.get(”) and parsed with JSON.parse(), resulting in a local variable which can be given to my Model object containing the data it needs to hydate itself. The actual file will get cached so subsequent calls to $.get(”) will pull the data from wherever the browser stores such things, so the model can be destroyed if it isn’t used any more, but also safely re-requested whenever it is needed.
Textures are obviously simple enough, because we have no control over them (Remove the reference to the image, say goodbye to the memory).
Summary
Anyway, all I’m saying is that you need to think a tad if you’re going to create anything other than a casual throw-away game in the browser with WebGL and JavaScript, it’s not actually all magic and you should be thinking about how you load/destroy your resources over time rather than just simply loading and letting things happen and not thinking about how you’re going to keep memory usage sane.
If you want to read some related material on various patterns for encapsulating state/logic safely, then wander over to