Skip to content

Instantly share code, notes, and snippets.

@Paratron
Created April 3, 2013 09:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Paratron/5299864 to your computer and use it in GitHub Desktop.
Save Paratron/5299864 to your computer and use it in GitHub Desktop.
//Cargo Asset Loader
//===================
//This file defines the API for a HTML5 asset loader that is mainly designed to be used for games
//but could be used for any kind of HTML5 application that requires some kind of asset management.
//@version: 1 (April 1st, 2013)
//@author: Christian Engel <hello@wearekiss.com>
//The Cargo asset loader should deliver the following number of features:
// * Loading all needed assets (huh, obvious)
// * Differenciating between mandatory and optional assets
// * Making it easy to switch to lowRes and highRes versions of assets, depending on the environment
// * Providing a rich event list to enable the game to react to different situations
// * Providing an easy interface for accessing loaded assets
//The biggest difference to conventional asset loaders is, that Cargo don't just loads a number
//of assets and calls a callback after that has been done. Cargo should enable the developer to
//get the game loaded as quickly as possible by loading mandatory assets at first, maybe even with
//low resolution, then loading optional assets, or higher resolutions after that.
//A player doesn't care if the game does additional downloads in the background after it has been
//started, but he DOES care about having a long waiting time at the beginning.
//This means that developers need to approach developing their games a bit different. Asset loading
//with Cargo will always be asyncronous and deferred and will even continue when the user is
//accessing a game menu, or is already playing.
//When a user is in the main menu and assets for the first level are not yet fully loaded, the game
//needs to show a loading screen again, but the progress could already be very advanced since all
//the time the user has spent in the main menu has been used to pre-load the next assets.
//Another thing is on-the-fly updating of assets during a running game. Cargo can be configured
//or advised to load low-res versions of the assets to get the game running really quick.
//When the game is already running, optional assets or high-res assets are loaded and Cargo
//notifies the game logic about asset updates and additions via the included event system.
//-------------------------------------------------------------------------------------------
//At first, require a reference to the cargo asset loader via AMD or commonJS.
var cargo = require('cargo');
//Set up the cargo object with the defaults you'd like to use.
//Heads up! If you require the cargo element again at a later point in your
//program flow, you don't need to re-configure it again.
cargo.config({
basepathGlobal: 'lib/', //A basepath to be put in front of every relative URL.
basepathImage: 'img/', //Basepath for all image files. Appended to global basepath.
basepathSound: 'snd/', //Basepath for all sound files. Appended to global basepath.
basepathText: 'txt/', //Basepath for all text files. Appended to global basepath.
parseJSON: true, //Should the content of *.json files be parsed to objects? Default: true
parallels: 5, //How many parallel downloads should cargo do?
timeout: 2000 //After how much milliseconds with no response should an asset be considered as timed out?
lowResPattern: '{filename}@low.{extension}', //The default schema for low-res versions of images.
highResPattern: '{filename}@2x.{extension}', //The default schema for high-res versions of images.
cacheBusterPattern: '{filename}.{version}.{extension}', //The pattern for the cache-busting mechanism for where to apply version numbers.
imageTypes: ['png', 'jpg', 'gif'], //Extensions for assets to be recognized as an image.
soundTypes: ['mp3', 'aac', 'ogg'], //Extensions for assets to be recognized as a sound.
textTypes: ['txt', 'html', 'css', 'json', 'js'] //Extensions for assets to be recognized as plaintext.
});
//About the lowRes and highRes patterns
//-------------------------------------
//Since Cargo is able to load the same asset in multiple resolutions, the package definitions
//have to hint Cargo that other resolutions are available.
//The patterns are used to help the developer to not being required to specify the URL for every
//version of the asset, but only saying "true" to a version. Cargo then uses the given pattern to
//build the URL by itself.
//About cache busting
//-------------------
//Dealing with the browsers cache to load
//The idea behind cargo is, that cargo is able to load assets in packages.
//For example, you define a package "ui", which you need to create your user interface from,
//or you need another package "level1" which contains all assets you need for your first game level.
//You can define one or more packages before you start loading them.
//Define a single package by passing in its JSON structure, or directly pass an array of package objects,
//or a URL to a JSON file containing the package definition(s).
cargo.definePackage({
id: 'level1' //The identifier of this package to access it at a later point.
mandatory: {
'ship1': { //A detailed asset definition.
src: 'player-ship.png', //The URL to the asset. Since this is an image, it will be prefixed with basepathGlobal and basePathImage
fsize: 128, //Optional definition of the filesize of the asset. Used to calculate more accurate progress.
fsize_hi: 256, //Equivalent to fsize, but for the hiRes version.
fsize_lo: 64, //Equivalent to fsize, but for the loRes version.
hi: true, //By setting the "hi" property to true, Cargo uses the lowResPattern to guess the filename of the hiRes image.
lo: 'player-ship.0.5.png' //The lowRes version is specifically defined here.
version: 1 //Optionally specify a version number of the asset and increase it after changes to bust the browser cache.
},
'game_bkg': 'some_background.jpg', //A shorthand asset definition. No low/high res version given.
'bkg_music': 'soundtrack.ogg', //Will be recognized as sound and prefixed with basepathGlobal and basepathSound.
'enemy_defs': 'enemies.json' //If the option parseJSON is set to true, the JSON file will be parsed automatically.
},
optional: {
//Use this like the "mandatory" property. Optional assets won't be loaded until all mandatory assets are done loading.
}
});
//After a package has been defined, its not instantly loaded.
//You may specify a great number of packages before loading anything. The idea is, that you can load one or more packages together.
cargo.load({
packages: ['ui', 'base_assets', 'level1'], //This tells cargo which packages have to be loaded to finish the load.
optionals: false, //Pass false here, to completely ignore optional assets from loading.
incremental: true, //Pass true to automatically increase the resolution.
loadHighRes: false, //Setting this to "false" prevents incremental to load highRes assets.
progress: progressHandler,
finish: doStuff,
optionalFinish: doMoreStuff
error: errorHandler
});
//Setting the incremental option to "true" will cause Cargo to download the lowRes version of all mandatory
//assets first, then continue with switching to standard, then continue with switching to highRes (if possible).
//This will cause a overall bigger traffic impact, but will get the players into the game much quicker
//and delivering better graphics after some time.
//The cargo.load() method returns a loading object which enables you to react to different events.
//In this example, three packages are being loaded. During the process, the progressHandler() function is
//being called on every update and gets passed the current loading progress.
function progressHandler(percent, calculatedSpeed, calculatedDuration){
$('.loadingLabel').text(percent);
}
//The calculatedSpeed and calculatedDuration arguments may not contain values at the beginning.
//Both are calculated during the download process, when fsize hints are given in the package
//definitions. The speed is given in kb/s, the calculated remaining duration is given in seconds.
function errorHandler(fails){
$.post('errortracker.php', fails);
}
//The "fails" attribute is an object that contains the keys of all assets that have been failed
//loading, as well as the HTTP statuscodes of the fail(s).
//Now lets see how we can actually work with the assets.
function doStuff(){
var pkg,
ctx;
//We're getting a reference to the package object here for faster interaction.
pkg = cargo.getPackage('ui');
ctx = $('#canvas')[0].getContext('2d');
//Entering some kind of game loop.
myEngine.onFrame(function(){
//This draws the asset "ship1" at the position x:1, y:1 in a canvas.
ctx.drawImage(pkg.get('ship1'), 1, 1);
});
}
//Calling the get() method of a package will return the asset with the given key.
//Now its getting interesting:
pkg.setResoluion('high'); //high, standard, low
//This makes cargo download the high resolution version of every asset if neccessary.
//After that, the get() method automatically returns the highRes version without further
//configuration.
//Listen to the changeResolution event to get notified to resolution changes that are
//made manually or automatically:
pkg.on('changeResolution', function(res){
//Maybe modify your game loop.
});
//The res attribute can be "low", "standard" or "high"
//Handling optional assets
//------------------------
//Well, the finish handler is being called after all mandatory assets have been loaded.
//But whats with the optional assets?
//You may have already spotted the "optionalFinish" option for the load() method.
//This callback is being called after all optional assets of a package have been loaded.
//When the callback has been called, you can be sure that all optional assets are available for use.
function doMoreStuff(){
//Prepare your game for using the optional assets
}
//Optionally, you can check the "optionalsAvailable" boolean property of your package object
//to do a quick check inside your game loop:
function myGameLoop(){
var pkg,
ctx;
pkg = cargo.getPackage('game_assets');
ctx = $('#canvas')[0].getContext('2d');
while(doLoop){
ctx.drawImage(pkg.get('player'), 10, 50);
if(pkg.optionalsAvailable){
ctx.drawImage(pkg.get('player_decoration'), 10, 50);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment