Expect this Unit to last about 2 hours. To demonstrate what you should have at the end of this Unit, check out the Demo for Unit 6.
Games need all kinds of content: images (textures), sound, data files for animation or object configuration or level data.
Many times, a single asset will be used multiple times. 10 enemies all using the same sprite. A single texture containing all the frames of a player's run animation. Typically we save memory by only have one version of the asset loaded and letting each object only have a reference to it.
This is especially important with online games. With HTML5, whenever we want an asset, we have to request it from the server. If we aren't smart about caching it, then each time we try to use an image or a data file then we will be unnecessarily requesting data from the server.
The Asset Manager's job is to be a general purpose asset loader with the following key feataures:
- Knows how to load assets of different types without the user specifying what type it is
so it knows by file extension if it is an Image file, or an Audio file, or a data files
- Keeps loaded assets cached so they are easy to access at run-time
so files don't unnecessarily get re-download from the server
- Caches assets by either filename or a user-friendly key
so we can have a shorthand way to refer to an asset, which makes it easier to replace assets without changing game code
- Simple API that supports loading batches of assets together
so if we want to load all the assets needed for a certain level, it's easy to do it in a single call
- Provides notifications through a callback whenever assets are finished loading
so we can do something in gameplay like start a new level, create a sprite, etc
We'll be adding these features directly onto the Engine's definition through the addition of just a few functions!
Loading assets - API
Let's start with the API: We want it to be super easy to load an asset without the user caring about which asset type it is. Our public facing generic loading function, called load(), should take in a list of assets to load concurrently and dispatch each one to the correct loading fuction.
The three types of assets we will be concerned with at this moment are Images, Audio and Data files, so we'll write three internal loading functions: loadAssetImage(), loadAssetAudio(), loadAssetOther(). All perform the same task with a different asset type. The function parameters are:
Determining what type of asset to load
So, given that the user is only going to use the load() function, we'll need to figure out what type of asset it is based on the file extension.
Let's write a handy helper functions for determining asset types based on the file extension. We can also write a function that gives us the filename with the extension removed.
These functions are assigned to the Engine namespace, and not the .prototype, meaning they act as static functions (just like GetSingleton) and not as member functions for an Engine instance.
Loading assets - Implementation
Since loading an asset is essentially just a request to the server to send a file to us, the process is asynchronous and we must use callbacks to be notified if and when the file loading has completed.
If you've worked in HTML to display an image or play a sound, then you're familiar with the Images and Audio objects, which will be the kinds of objects we use to both request the asset from the server and utilize it in game.
Once the Image has loaded, we call the success callback passing along the key parameter here so it can be cached.
A great way to deal with this is to have the asset loader strip off the file extension and postfix the one that is actually supported. The way we check that is by calling the Audio.canPlayType with the mime type associated with the file extension. It also means that your server should have multiple formats of the audio available to be requested by the client.
We also add an extra feature to disable loading audio if we have marked sound as disabled (muted by the player).
Now that we've seen the implementation of our individual asset loaders, let's take a look at the general-purpose one that the user will actually use!
The basic structure of the code is to loop over asset hash and determine the asset's type to dispatch appropriate loader function. It also supports a progress callback for each item, so we can show the user something like a loading bar, and caching the assets by storing them in a hash.
Before we start loading any assets, we setup a callback for handling an error, and for tracking progress internally to this function. Each time an asset is successfully loaded, we store it in a hash array called .cachedAssets so it can be utilized later.
We cache loaded assets by an optional user-friendly key name, ie. "player_run_anims_128x128.png" could be cached unded the name "playerrun" so it's easier to reference in game code, especially since we could change the file type to a .bmp and the game could could care less. If no key is provided the system will just use the filename.
To support this we make the assetList super flexible. It can be of any of these types:
- a string - a filename of a single asset to load (the key will be the same as the filename)
- an object - can represent multiple assets, where each property is the asset key and the value is the filename
- an array of strings, or of objects, or a mix of both - so the user could concatenate multiple sets of assets before passing them all over to be loaded together.
This function uses Underscore extensively, which you should be comfortable enough with by now to look up and understand it's usage here.
Requesting cached assets - API and Implementation
Well, this one is so straight forward I won't even bother separating the API from the implementation.
We already know how we're storing our cached assets, so for retrieving them we need only to write a function that does some safety checking first.
Look at this test code and make sure you understand what's going on.
Question: If you remember we don't load assets that we already have cached, and the system just pretends it succeeds as normal. But assets are cached by the key, so technically you could have the same asset file cached multiple times under different key names. Can you tell how many times "title_music.mp3" gets loaded here?
Part 1. Create and add the assets.js file to your project in the /js_engine sub-directory.
Part 2. Fill out assets.js with the code from this Unit.
Part 3. Create a new subdirectory to store game assets in, such as /gamedata. Put a few sample assets in there (an image file, an audio file, a JSON file)
Part 4. Load an Image asset. If you are comfortable with manipulating the DOM directly or through jQuery, try displaying that image in the HTML page after it's finished loading.
Part 5. Load an Audio asset. Once it's loaded play it
Part 6. Load a JSON data file (If testing locally, this is where you'll need to be using a web hosting application like Mongoose)
Part 7. Try loading a batch of assets, and update a loading bar (one idea is that you could use HTML and CSS to display and update a loading bar).