Asset Management for Web Games

| by Chandler Prall

Many of today’s computer games have intense graphics and interfaces with many moving parts. This has been possible on the desktop and console platforms by the use of high-density media such as DVDs and also fast read/write speeds provided by RAID setups and solid state drives. As more games are created to be played on the web we must provide players with great gaming experiences, without making them wait for hours for assets to download. A lot of progress has been made recently which allows web based games to have more control over when their content is downloaded and how it is cached.

How to package and compress all of the data necessary for web sites has been addressed countless times, but the sheer volume of content in games emphasizes the lag we see between opening a game and being able to play it. Most people will leave if a web site doesn’t load within a few seconds, and although games are allowed more time there is still a point when potential players will close the window because they are tired of waiting. Let’s explore some different ways to speed up loading times and to keep content around on users’ computers longer. After a quick review of some traditional methods for handling lots of static content such as scripts and images, we will take a look at some of the new HTML5 APIs we can use to give users a smoother and more pleasant experience.

Traditional Techniques

If you are a seasoned web developer then the techniques in this section should be old news to you. For everyone else I suspect you have at least heard of them even if you don’t yet know what they are. Many developers who want to create HTML5 games are coming from other languages and platforms to see what JavaScript and games on the web are all about. This is a quick introduction to some existing methods which ensure data is transferred efficiently, and if you want to learn more about any specific technique the internet is full of articles and tutorials dedicated to each of these steps.

CDNs

One of the easiest and most useful ways to help assets download faster is by serving them from a CDN - or Content Delivery Network. This can be as simple as creating a subdomain on an existing server to host your static files, or you can use a third party such as Akamai, CloudFront, or Amazon Web Services to provide those files to users via high-capacity networks located all around the world. Using a CDN can give quicker load times because of the connection throttling done in browsers. Most browsers limit the number of connections they make; around 5 connections per server, depending on the browser. They do this to prevent overloading a server with requests or saturating the user’s bandwidth. The negative impact this can have is when you have some large images on a page the user may be forced to wait until those images are finished downloading before downloading the CSS or JavaScript that the page needs. Moving these files to a dedicated server increases the number of active requests browsers make when accessing your game’s assets.

Server Controlled Caching

In order to reduce the number of requests a browser will make on subsequent visits you can tell them how long to store the files in cache. This is done by sending additional response headers when serving your game’s content. These response headers tell the browser to use a cached version of the file until a set time has passed. Once a user has your content in cache, the files will be accessed from the user’s computer instead of downloading everything all over again. All popular server software (Apache, Internet Information Services, etc) make it easy to automatically include these headers. Here is a very simple Apache configuration that controls how a browser caches different types of files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Turn on Expires and set default to 0 seconds (no cache)
ExpiresActive On
ExpiresDefault A0
 
# Set up caching on image files for 1 week
<filesMatch "\.(gif|jpg|jpeg|png)$">
ExpiresDefault A604800
Header append Cache-Control "public"
</filesMatch>
 
# Set up 1 day caching on commonly updated files
<filesMatch "\.(html|js|css)$">
ExpiresDefault A86400
Header append Cache-Control "proxy-revalidate"
</filesMatch>
 
# Force no caching for dynamic files
<filesMatch "\.(php)$">
ExpiresActive Off
Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform"
Header set Pragma "no-cache"
</filesMatch>

A second way to tell a browser to use the cached version of a file is through the use of entity tags - or “ETags” – which are short strings identifying a file’s contents. These tags are often generated by taking the MD5 or SHA1 hash of the file’s contents. Like the cache control above, ETags are sent between the browser and server as HTTP headers. When a server responds to a request it can also specify an ETag which the browser then stores with the file in cache. On subsequent requests for that file the browser sends the ETag it was previously given to the server. The server will compare the ETag sent by the browser to the one it has for the file requested and if it determines that the ETags match, responds with a status of 304 Not Modified. This status lets the browser know it should use the file already in stored in cache. Even though a request is still made the ETag has prevented the file from being transferred and frees the connection to download other resources instead.

Image Optimization

I’ll admit - I am a bit of a fanatic when it comes to optimizing image and script file sizes. I have spent more time than I am willing to admit reducing image sizes as much as possible. While I do not suggest obsessing over how big your files are it is important to keep the size of your assets in mind. There is no magic bullet to reduce file sizes but there are a few ways to reduce the impact your content has on loading times and user experience.

  • Don’t forget about GIFs. If your image has 300 colors or less and doesn’t need alpha transparency it is very possible that saving it as a GIF will result in the smallest image size without taking away from the quality.
  • Use a lower quality setting when saving JPGs. I can’t think of any time when I have saved a JPG at the highest quality, and it is rare when I need them above 90. Images in games are usually moving around quickly and small imperfections in quality are often hard to notice
  • Use an image optimization utility, especially with PNGs. Most of the time JPGs have unnecessary information saved along with the image data and programs like jpegoptim can remove this data without affecting the image quality. PNGs can be optimized in a number of different ways and Photoshop is notorious for being bad at reducing PNG file size. There are a few good optimizers for PNG images but the best I have found - by far - is pngout.
  • Run your JavaScript and CSS through minifiers such as Google Closure or YUI Compressor. These tools can be used online or downloaded to run from your computer. Through various methods they shrink the size of your scripts and styles, often by 15-25%.

Persistent Data using Local Storage

Have you ever wanted to save content just for one user? Maybe you have wanted to save progress made in a game or some custom content a player put together. In the past this has required storing the information in cookies or in a database. However, with the new Web Storage specification it is painless to save both persistent and session based data on the user’s machine. More commonly known as Local and Session Storage, Web Storage is supported by all major browsers, including Internet Explorer 8! I won’t go into the details of how this new storage option works because there are already some excellent articles on the topic; instead, let’s look at some of the cases we could take advantage of this type of storage.

First there are some limitations to keep in mind. To prevent web sites from filling up hard drives around the world, browsers set a limit on the amount of disk space you can use per domain; most browsers won’t allow more than 5MB, although the W3 specification does allow browsers to allow more space if they want to. If you exceed this limit when saving data the browser will throw an error, and to make matters worse it is not possible to ask how much space is available or already used. A second limitation is that everything is stored as a string. This means that any time you retrieve a value it must be cast or parsed into whatever type it needs to be. Because of this, integers and floats occupy much more space than would be expected, and objects must be converted to JSON before saving. Finally, Web Storage contents is not always deleted at the same time cookies or cached files are; while browser vendors are encouraged to provide an easy way to clear the data saved in Web Storage, it is a good idea to keep track of the values used by your code and remove any which are no longer needed. This will keep more of that 5MB free for you to use while limiting the impact you have on a user’s computer.

Let’s examine the kind of data Web Storage should be used for. Any information that does not need to be shared with the server can go right into Web Storage. It can serve as a replacement for traditional cookies if the values are needed client-side instead of on the server. Much more enticing, using Web Storage is a painless way to save and restore game information without having to push the data to your server or prompting the user to download the save file. Other uses could involve storing generated content that does not need to be shared between users; item such as in-game avatars, procedural content, and character data can be saved for later or repeated use, removing your need to store these assets on the server and avoiding network requests for those items. Another use could be to record the events in a game and use them to influence what occurs in subsequent games.

Application Caching Through Manifests

So far we have examined ways to instruct browsers on how to cache content but not really what or when to cache. Games and other applications which use many and/or large files must preload their assets in order to avoid lag when first accessed. We see this in today’s desktop and console games – a loading screen with progress indicator, flashy background image, and maybe some small gameplay tips to keep us interested while we wait. Flash games usually do the same, and the ability to track download progress is built directly into the Flash environment and language.

Prefetching assets is closely tied to cacheing as both result in your assets being immediately available. Relying on cacheing alone means that the first time a user visits your page, content will be downloaded only when it is immediately needed. When some method of prefetching is included, all of the content used in your application is downloaded and cached before it is needed, so when the time comes to display content it is ready to go. Of course if all your assets are downloaded before your game starts there can be a long waiting period while the data is transferred. A popular way to avoid such waits is to only load content for the near future. If a game has multiple levels then load just a few at a time; by always prefetching assets needed in the next level players won’t be forced to sit through loading screens between levels, and they won’t have to wait for all of the content to finish downloading before ever starting.

Until recently, prefetching content required creating a script that would create Image objects and point their src attribute at the files you wanted to download. Differences between browsers made this process difficult to perfect because in addition to creating the Image object, sometimes you would need to add it to the DOM in order to trigger the download. Additionally it has been cumbersome and even impossible in some instances to prefetch any any content which isn’t an image, such as HTML and JSON files.

Thankfully, recent versions of all major browsers - including Internet Explorer 10 - support a feature officially named Offline Web Applications, which is more often referred to as Application Cache. This new API was created to give online applications the ability to define lists of files that are needed so they can be used even when the user doesn’t have internet access. This can be useful for more than just making applications available offline, however. Websites and games written for mobile devices need to be ready for the possibility that the user will lose their internet connection and should make sure the user can still access all of the necessary content. When using this new form of cache you are in control of what the browser caches and when it downloads that content.

Application caching works by creating a list of files, referred to as a manifest, which are required by your application. This manifest file must be served with the mime type text/cache-manifest and is linked to from an HTML page by adding the manifest attribute to the page’s <html> tag, as in this example:

1
2
3
4
<!DOCTYPE html>
<html manifest=”/path/to/manifest.appcache”>
  [  … page content … ]
</html>

There are three sections you can define in an application manifest, in any order: CACHE, FALLBACK, and NETWORK. The CACHE section lists all files to be cached and made available to the browser when offline. Entries under FALLBACK describe fallbacks for resources which normally require an internet connection. NETWORK is a tricky section and may at first not have an obvious use. The Mozilla Developer Network documentation says “Files listed under the NETWORK: section header in the cache manifest file are white-listed resources that require a connection to the server. All requests to such resources bypass the cache, even if the user is offline. Wildcards may be used.” Your first reaction to this may be similar to mine: why would you list a resource to always be requested, doesn’t the same thing happen by just not caching it or using a fallback for it? However, according to the specifications for offline web applications, if you do not list a file under any of the three sections then it will not be available at all to your application once it is cached. If this seems strange or you cannot believe it is true I encourage you to check out this demo – the two images will load fine on the first page load but are not loaded on subsequent page views until you clear your browser’s application cache. Keep this in mind if there is a resource that you want to use but never store it in the cache.

Now let’s take a look at a sample manifest file. Imagine we are building a simple game of tic-tac-toe (noughts and crosses for those of you across the pond). Our game uses these five files:

  • index.html
  • game.css
  • game.js
  • images/nought.png
  • images/cross.png

We also want to include a way to keep track of how many games each player has won and lost. Because this data will be stored on a server it will not be available offline, in which case we will display a message to the user informing them of their bad luck. This extra functionality will be made through an AJAX call to highscores.php. The manifest file for our game would look something like this.

1
2
3
4
5
6
7
8
9
10
11
CACHE MANIFEST
# v1.0.0

CACHE:
game.css
game.js
images/nought.png
images/cross.png

NETWORK:
highscores.php

The first line is mandatory for every manifest file, telling the browser that the file is a cache manifest. The second line is a comment; including some kind of version information is recommended so the browser knows when files have changed (this will be explained a little further in a minute). The four files under the CACHE heading are our game files we want cached and available offline. highscores.php is under NETWORK which allows us to read and save how many games players have won or lost. We don’t use FALLBACK because all of our files apart from highscores.php will be cached, and if the user is offline then the failed AJAX request will be caught in the JavaScript code.

Imagine we launch the game and, after stunning success, we decide to update the look and feel with some nicer images. When new players come to the game they will see the glistening new graphics but existing players have the old versions cached. When the browser downloads the manifest file to see if anything has changed it won’t notice a difference because all of the filenames are the same. This is where the manifest version number is important. When a browser loads a page it has saved for offline use it will download the manifest file again, and if the manifest has changed in any way the browser will fetch all of the specified assets again, following the other caching techniques such as cache headers and e-tags. So if we want existing players to see the new content we can simply increment the manifest’s version number. There is one caveat when updating the manifest, however - the browser will display the page and cached assets before it updates anything. This means that after changing a manifest file you will need to refresh the page twice in order to see the changes: the first time will download the manifest and files and a second time to actually see the content.

Another aspect of the offline web applications API is an event system which connects you to the full caching life cycle. Browsers expose a window.applicationCache object which allows your code to add event listeners the same way as DOM events. There are eight events that cover the caching sequence from first checking for the manifest file all the way to notifying of a complete or an unsuccessful finish. Listening for these events lets you know when new content is available, the progress of files being downloaded, and whether or not there were any problems caching the content. Equipped with this information you can adjust the user experience as needed and provide feedback on what the player should expect.

When working with application caching there are a lot of small pieces that can be really confusing and frustrating. If you plan on taking advantage of this new API then I highly recommend you play around with it on a small project before trying to implement it inside of a bigger codebase. This article barely scratches the surface of application caching’s power so I suggest reading these three pages and, if you are feeling really interested, give the specification a good read through.

Appcache Facts by Mark Christian, Dustin Diaz, and Peter Lubbers.

Application Cache is a Douchebag by Jake Archibald

Using the application cache on the Mozilla Developer Network

Offline Web applications at the W3C

Tying it all Together

I hope by now you have seen that there is no one size fits all solution when it comes to caching content and speeding up the user’s experience. When working with HTML5 games and applications there will always be download times to overcome, various caching mechanisms to be conquered, and most importantly there are fans to be won. With modern desktop games using gigabytes of disk space and mobile apps transferring over low-speed, high-latency networks, players have grown accustomed to waiting for their new adventures to be ready. Even though the web carries an expectation that content should be available immediately, as a greater shift toward HTML5 games happens I believe people’s patience and willingness to wait will adapt – up to a point. With the caching mechanisms available to us right now we can provide our users with fluid, one time installs which mimic what they are already used to on other platforms.

Modern browsers yield a lot of control to developers. We must learn to take advantage of the new APIs and use them well in order to give our users the best possible experience. If we fail to do so we risk losing their attention and seeing them navigate away from the page. Studies have found that most people will close pages taking longer than 2 - 4 seconds to load. In the case of online games you can expect most people to wait more than 4 seconds but it is important to respect their time and attention.

To keep players focused and entertained, consider displaying something more interactive than a simple progress bar to indicate loading progress, or load low-quality content at the beginning of a game and include higher resolution assets once the game is in motion. Once someone has opened the page to your game, no more than a few moments should pass before there is some sort of experience drawing the user in. Some games include graphics and animations related to the theme; others show hints or little known commands that can help players. It is your job as a developer to pull users into the world you have created for them. While that world is being prepared create distractions by turning loading sequences into part of the experience, and if you get stuck coming up with ideas try starting any game which takes longer than 10 seconds to load and watch how it handles the wait.

in Platform .

Comments