20 November 2013
5. Assets management
What are game assets ?
When a developer creates a game, he focuses a lot on the code. But he also has to deal with specific files that make the game more beautiful and pleasant to play with : images, sounds, videos, etc. Usually these files are created by artists and integrated into the game by programmers.
Those files are called "assets" and before a user can play with a game, the assets need to be loaded into the computer's memory (RAM). If you don't load these files in memory, multiple accesses to the hard drive will drastically slow your game down.
But in order to reduce the loading time and keeping the memory low, it's advised to load only the minimum set of files required by the current level. Usually the loading is hidden behind a loading screen when the player start a level, and after that, the game go smooth.
There are some exceptions in sandbox games where the world is huge like GTA. In these games, assets are loaded in real-time depending on the objects around you (assets streaming).
XMoto, in comparison, is a small game with only a handful of assets : each level has a background and sky texture, some sprites (static or animated), some sounds and the textures of the moto and rider parts. To efficiently deal with these files, we will create an Assets Manager whose job is to collect and load all the files needed to display a specific level before starting it.
To understand the following explanations on asset management, we must introduce the concept of "game loop". The main loop of a game is the sequence of operations the game must do on each frame. Since a smooth game run at about 60 frames per second, the main loop is computed 60 times per second by your computer.
The simplified version of the game loop is the following :
update = -> level.input() level.physics() level.display() game_loop = setInterval(update, 1000 / 60)
update() function is defined by 3 other functions :
level.input(): watch if the player presses a key or moves the mouse.
level.physics(): depending on inputs and the physics laws (gravity etc.), the new positions of the game objects are computed.
level.display(): the screen is refreshed with the new object positions.
setInterval(update, 1000 / 60) just tells your browser to execute the
update() function 60 times per second. No need to say that these functions must be really fast and optimized (less than 16ms) since they are called so many times...
Note : this game loop is very naive because the game doesn't work well if the computer is not powerful enough to loop at the speed of 60 frames per second. We'll see later how we can improve that.
To be sure the main loop is not executed before the assets are in memory, we only launch the game main loop after they are loaded.
This goes like this :
level = new Level() level.load_from_file(name) # Load assets for this level before doing anything else level.assets.load( -> update = -> level.input() level.physics() level.display() game_loop = setInterval(update, 1000 / 60) )
level.assets.load( -> just say "load the assets, and then, when they are loaded, execute the following functions". In our example, "the following functions" are our main loop.
But how exactly works the assets loading ?
We choose to use PreloadJS that is part of the CreateJS framework. PreloadJS purpose is to create AJAX calls to get some files and store them into the memory. When those files are loaded, a callback is executed (a callback is a function like the
-> symbol of CoffeeScript) and the rest of the game can be executed.
level.assets.load() that, when all the assets are loaded, calls the function passed as argument (
The code where PreloadJS is used lies in the
Assets.coffee file. Here is a simplified version of it :
Some tips to understand this code :
@queue = new createjs.LoadQueue()is the main object of PreloadJS.
@texturesis an array where all the names of the required textures for this level are being stored by outside calls before calling
load()method (see Section "Get an asset from the asset manager").
@texturesvariable is completed with all the required textures for this level, the
load()method can be called. It loads all the assets using PreloadJS and then execute the callback (the main loop).
itemsis a an array of hashes created to feed PreloadJS.
idis the name of the asset so we can find it back later, and
srcis where the asset is located.
@queue.addEventListener("complete", callback)tells to call the
callbackfunction when the assets loading is completed.
@queue.loadManifest(items)loads all the assets in memory.
remove_duplicate_textures(array)is just an small method that checks that each asset is only loaded one time.
Note : This piece of code is relatively less complicated than the original version. That's because the assets in the original XMoto game are divided into several folders (/Textures/, /Anims/, /Riders/, /Effects/, etc.) and some meta-informations about them were in a
.xmlfile. But you get the idea.
Tell the asset manager to load an asset
When the game in started, one level is parsed and, during this process, a certain number of texture names are collected. Textures of moto, blocks, edges, sprites etc. At this moment, the asset manager is called and the name of the texture is appended to the
@texture array like this :
for block in blocks @assets.textures.push(block.usetexture.id)
Then, after the level is completely parsed, the
assets.load() is executed to load all the related files in memory.
Get an asset from the asset manager
At each frame of the game, assets are drawn on the screen like this :
ctx.drawImage(@assets.get(texture_name), 0, 0, width, height)
@assets.get(texture_name) is a special proxy from the Assets class to get the asset from memory using PreloadJS (
@queue.getResult(name) in Assets.coffee).
We will learn more about ways of drawing sprites and textures on the screen in the next chapter.