Palagpat Coding

Fun with JavaScript, HTML5 game design, and the occasional outbreak of seriousness

Let's Make a Canvas Library: States

Tuesday, January 15, 2013

Welcome to another edition of Let's Make a Canvas Library, our funkalicious tutorial series all about the basics of building a personal code library for making neat stuff with HTML5 Canvas.

(As always, the aim of this series is primarily educational, not to build an amazing library that everyone and their dog is going to want to use. But for anyone interested in following along, or anyone who likes the API we're building and wants to use it, it's available for forking and download on my Github account as TangleJS.)

Today we're going to talk about state machines.

State of the State

One of the most useful things I learned in my first semester of college computer science (back when dinosaurs roamed the earth and CS 121 used Pascal as its introductory language) was the concept of the Finite-state machine (FSM for short). To oversimplify, an FSM is a set of program states and a set of transition rules for moving between them. A state machine can only be in one state at a time, and its current state determines how it reacts to any inputs it receives.

As a simple example, the Wikipedia entry for FSMs describes a state machine that models the operation of the humble turnstile, like you might see at a state fair. A turnstile has two basic states, locked and unlocked, and two rules for transitioning between them: a coin will move the turnstile from its locked state to its unlocked state, and pushing the arm to pass through the turnstile moves it from the unlocked state back to locked again. Note that putting more coins into a turnstile that is already unlocked simply keeps it unlocked, and the act of pushing on a locked turnstile similarly doesn't cause it to change its state (i.e. it stays locked):

Transition diagram for a finite state machine modeling a turnstile

Simple state diagram for a turnstile. Original image by Chetvorno, retrieved from Wikimedia Commons

Another common way to visualize a state machine is via a state transition table, like so:

Initial StateInputs
coinpush
LockedUnlocked-
Unlocked-Locked
State transition table for a simple turnstile

In this kind of table, every state is listed, along with every possible input, and for each combination of current state and input that cause a state transition, the new state is listed. So far so good?

State of Play

“So,” I hear you asking, “What does this have to do with games or canvas...?”

Quite a lot, it turns out. First, your game itself will likely have a state machine (with states like loading, title screen, options menu, playing, paused, and game over):

Initial StateInputs
Enter key"assets loaded" event"Player die" event
Loading-Title-
TitlePlaying--
PlayingPaused-Game Over
PausedPlaying--
Game OverTitle--
State transition table for basic game logic

In this hypothetical game logic, the Loading state doesn't handle any input at all, the Title state allows you to press Enter and start playing the game, and that same key input allows you to switch back and forth between the Playing and Paused states. The Playing state also handles a "player died" event, which causes the game to go to a Game Over state.

Likewise, your game will probably have entities (more on this in a future article) such as a player, obstacles, and enemies, and each of these may have its own state machine. Take, for example, this overly-simplified (and abbreviated) state machine for the main character in a side-scrolling platformer game (think Super Mario):

Initial StateInputs
Up arrowRight arrow"state end" event"touch enemy" event
IdleJump upRun forward-Die
Jump Up-Jump ForwardFallDie
Jump Forward--Fall ForwardDie
FallDouble JumpFall ForwardIdleSquash enemy
Fall ForwardDouble Jump-IdleSquash enemy
Run ForwardJump Forward-IdleDie
Die--Trigger "player die" event-
State transition table for a platformer hero

You may have noticed that I've been including a couple of different kinds of inputs in these state transition tables: direct user inputs, such as the player pressing a key on the keyboard, and other, more abstract events such as "player die" and "state end".

At this point in the article, I originally went on a several-paragraph tangent about how complicated input and event handling are, when I realized that the subject is complex enough it deserves its own blog post. So for now, let's just assume our state machines have a fixed set of inputs (some user-driven, others event-driven), and we'll save the actual capture of those inputs for another day.

State of Control

It's pretty simple to start using the "state machine" paradigm in your JavaScript code. A naive, ad-hoc approach might look something like this:

  var state = 0;
  var assets = new AssetCache(),
      canvas = document.querySelector('canvas'),
      context = canvas.getContext('2d');

  /* (specify assets to load) */

  function _update() {
    switch(state) {
      case 0: // loading
        if (assets.ready()) changeState(1);
        break;

      case 1: // title
        // (check user inputs and change state accordingly)
        break;

      /* logic for the rest of the states */

      default:
        break;
    }
  }

  function _render() {
    switch(state) {
      case 0: // loading
        context.fillStyle = "#ffffff";
        context.fillText('Loading...', 100, 175);
        break;

      case 1: // title
        var imgSplash = assets.getAsset('title');
        if (imgSplash) {
          context.drawImage(imgSplash, 0,0, 640,480);
        }
        break;

      /* rendering code for other states */

      default:
        break;
    }
  }

  function changeState(stateNum) {
    console.log( "Entered state", stateNum );
    state = stateNum;
  }

  // set up main loops
  Tangle.init(_update, _render);
  Tangle.play();

This works fine, and I built my first couple of game projects using this approach. It just starts to feel a little copy/paste-y after a while, and any process involving copy/paste of big chunks of code is going to be susceptible to error.

It also violates the DRY principle, because you've got multiple switch statements (one for update() and one for render()), and are spreading the logic for your states across (at least) those two functions.

If we were to abstract this into a reusable StateManager class, it might look something like this:

  var assets = new AssetCache(),
      states = new StateManager(),
      canvas = document.querySelector('canvas'),
      context = canvas.getContext('2d');

  /* (specify assets to load) */

  states.addState({
    id: 0, title: 'Loading',
    tick: function(me) {
      // If all assets are loaded, return 1 to tell 
      // StateManager to change to state 1. No change otherwise.
      return (me.assets.ready()) ? 1 : null;
    },
    render: function(me, ctx) {
      ctx.fillStyle = "#ffffff";
      ctx.fillText('Loading...', 100, 175);
    }
  }); // end of state 0
  
  states.addState({
    id: 1, title: 'Title',
    tick: function(me) { /* update any title entities */ },
    render: function(me, ctx) {
      var imgSplash = me.assets.getAsset('title');
      if (imgSplash) {
        ctx.drawImage(imgSplash, 0,0, 640,480);
      }
    }
  }); // end of state 1

  // (other states omitted)

  // StateManager event callbacks
  function stateChange(data) {
    console.log( "Entered state", data.id, ":", data.title );
  }
  states.events.changeState.watch(stateChange);

  
  // set up main loops
  function _update() { states.tick(this); }
  function _render() { states.render(this, _context); }
  Tangle.init(_update, _render);
  Tangle.play();

So now each state's logic is in a single place, and if you wanted to, you could even externalize each state definition into its own .js file, if they get big enough to be unwieldy. In most cases like this, the game's update() and render() loops just defer to the StateManager's tick() and render() methods, making the main Game.js code a lot more streamlined.

Another approach might be to make this a functional mixin instead of a creatable class, if you prefer that coding style. Either way, the point is to encapsulate your state logic and make states a little more self-contained.

State of the Art*

Finally, let's look at the demo! As in the previous articles in the series, I've created a simple demo that leverages Statemanager in conjunction with the parts we've already built (AssetCache and Tangle Core) to show how they can be used to start fleshing out some actual game logic.

(Maybe next time we'll have enough building blocks in place to actually make something interesting!)

As was the case with the last few components we've built, StateManager can be used in a variety of applications, canvas or no canvas, and should work in pretty much any browser that's not a pre-version-8 Internet Explorer.

Next time we'll take more time talking about how to capture and deal with user inputs and game events. See you then!

Resources

Let's Make a Canvas Library: Game Loop

Sunday, January 6, 2013

And... hiatus is over! Sorry for the delay, it's been a crazy Autumn.

Welcome back to Let's Make a Canvas Library, our tutorial series focused on the principles of building a good personal code library for writing games and other cool stuff with HTML5 Canvas. As a reminder, last time I said we'd cover:

“the basics of building a game loop, and actually use AssetCache to build something interesting.”

So, let's talk about game loops.

(remember, my goal with this series is mainly educational, not to build the jQuery of game libraries. But for anyone interested in following along and messing with what we build, you can grab the TangleJS repo on Github)

Anatomy of a Game Loop

A basic game loop

A basic game loop. Image by LazyEyeOzzy, retrieved from Wikimedia Commons

Almost every type game you'll ever play has some form of game loop at its core; most will look quite similar to the above diagram. After any necessary startup (loading resources and such), the game loop takes care of a few basic things:

  1. Handle player input
  2. Update the state of the game and any objects
  3. Render the game state back to the user

Each of these steps break down into sub-steps: handling player input implies collecting it and processing it. Updating game state may include things like running enemy AIs, calculating new positions for moving elements, and collision detection. And user output may include graphics, sounds, or even haptic feedback (like vibration).

The traditional way to achieve these types of loops in JavaScript is via the setInterval method:

Game.fps = 50;

Game.run = function() {
  Game.update();
  Game.draw();
};

// Start the game loop
Game._intervalId = setInterval(Game.run, 1000 / Game.fps);

(Example taken from nokarma.org)

This works pretty well, and many people have used it to great effect. Recently, though, a few of the browser makers have decided to give us a new and improved API called requestAnimationFrame (You can tell it's still pretty new, because the browser vendors still have their own proprietary-named versions: mozRequestAnimationFrame, webkitRequestAnimationFrame, etc). Its purpose is to streamline the management of in-browser animations, since the browser can internally optimize things like repaint cycles that aren't visible from the JavaScript layer. Plus, if your browser tab is hidden, the browser can effectively pause your animation for you, leading to better performance, memory usage, and so on.

Sounds good, right? Here's how we might change our loop to use this new API instead of setInterval:

Game.run = function() {
  Game.update();
  Game.draw();
};

function animate() {
  Game.run();
  requestAnimationFrame( animate );
}

// Start the game loop
animate();

I have a couple of thoughts when I see a game loop like this. One is that it violates the DRY principle, because you have to have this weird animate() function, whose sole purpose is to recursively call itself via requestAnimationFrame, then call Game.run(). Ick.

The second thing I notice is that there's no way to specify frames per second anymore, like there was in the first example. The main reason for this is that requestAnimationFrame is optimized by the browser, so the rate at which it gets called depends on processor speed, the amount of system resources available, and whether the tab is active. For the rendering portion of our game loop, that's just fine — there's no point in burning up your processor drawing to a tab that's not even visible. But what about the update portion of the loop? Should it be bound to the speed at which things are being drawn to the screen? Logic (and indie game developer Chandler Prall) would say no. So, let's try again:

Game.fps = 50;

function render() {
  Game.draw();
  requestAnimationFrame( render );
}

// Start the game's render & update loops
render();
Game._intervalId = setInterval(Game.update, 1000 / Game.fps);

That seems better. Now the draw code can leverage the browser's optimization path via requestAnimationFrame, and the update code can run at our specified rate. If you wanted to get a little more advanced, you could probably try and offload your update loop to a Web Worker, but that's well beyond the scope of this article.

There are a couple of things that this last version of the code doesn't address, which I often find myself wanting in my JavaScript game projects: the ability to dynamically show a realtime FPS count, and the ability to pause and resume the game. Both can be pretty easily accommodated with a simple helper class to abstract this basic level of game management, so I've added one to Tangle, which I'm calling Tangle Core. Here's the meaty bit (the real thing is wrapped in an AMD module, but this gives you the gist):

var tangleCore = {
  init: function game_init(updateFunc, renderFunc, fpsFunc) {
    _paused = true;

    if (fpsFunc) {
      if (typeof fpsFunc == "function") {
        _fpsCallback = fpsFunc;
      } else {
        console.error("[Tangle.main] >> Provided argument fpsFunc is not a function.");
      }
    }

    if (updateFunc && typeof updateFunc == "function") {
      _updateLoop = updateFunc;
    } else {
      _updateLoop = null;
      console.error("[Tangle.main] >> Provided argument updateFunc is null or not a function.");
    }

    if (renderFunc && typeof renderFunc == "function") {
      _renderLoop = function() {
        _checkFPS();  // calls fpsFunc with the latest calculated FPS
        renderFunc();
        _renderLoopId = requestAnimationFrame(arguments.callee);
      }
    } else {
      _renderLoop = null;
      console.error("[Tangle.main] >> Provided argument renderFunc is null or not a function.");
    }
  },

  isPaused: function isPaused() {
    return (_paused == true);
  },

  pause: function pauseMain() {
    if (!_paused) {
      _paused = true;
      clearTimeout(_updateLoopId);
      cancelAnimationFrame(_renderLoopId);
    }
  },

  play: function playMain() {
    if (_paused) {
      _paused = false;
      _updateLoopId = setInterval(_updateLoop, 1000 / 60);  // aim for 60 FPS
      _renderLoopId = requestAnimationFrame(_renderLoop);
    }
  }
};

// to use:
tangleCore.init(Game.update, Game.draw);
tangleCore.play();

Now we're starting to get somewhere! Less boilerplate code in our games == WIN.

Rubber, Meet Road

As with the last post, I've created a simple demo that shows off this new functionality.

As with AssetCache, Tangle Core should be usable in all modern browsers, plus Internet Explorer 8. Once we dive into the specifics of Canvas this may not be the case, but the basic components we've been building so far have applicability in other contexts beyond Canvas-based games. The AMD approach allows us the flexibility to cherry-pick the parts of Tangle we want to use without locking us into a particular programming paradigm.

Next time we'll talk about Screens, States, and how to avoid making your game loops look like a plate of spaghetti.

Resources

Let's Make a Canvas Library: Assets

Monday, July 16, 2012

Welcome back to Let's Make a Canvas Library, our tutorial series focused on the principles of building a good personal code library for writing games and other cool stuff with HTML5 Canvas. Today we're going to focus on assets: images and sounds.

Like the blog series it's modeled on, my aim is primarily educational, not to come up with an awesome library that every person on the planet is going to want to use. That said, I'll be committing each improvement to a new Github repo I'm calling Tangle, for anyone who wants to follow along.

Asset Management

preloading splash message

Unless you're really embracing your retro muse, just about any game you set out to build is going to have assets: the images and sounds that give it its unique flavor. For many games, these assets will take a while to load, especially over low-bandwidth connections like those usually found on mobile devices. That being the case, you're going to want to preload those files before starting the main game loop. And since this is something you're going to do over and over (and over) again, on basically every game you build, it makes sense to make it a part of your library.

In my first few games, my preloading routine was limited to just images and javascript modules (when I needed sounds, I did those separately, using SoundManager2 or something similar). For a while, my preload process had two phases: first, I'd pass a list of images and modules to the Preloader class, which would then call dojo.require() on the modules, and pass the list of images along to a global ImageCache object to preload separately. When all the code and images were loaded by the Dojo loader and ImageCache, then the Preloader class would signal completion back to the calling routine. In retrospect, it probably involved a few too many steps of indirection:

//
// (in main.js)
//
dojo.addOnLoad(function init_loader(){
    dojo.require("loc.Preloader");
    dojo.addOnLoad(preload);  // when loc.Preloader is loaded, run preload()
}); // end of init_loader()

function preload() {
    window.loader = new loc.Preloader({
      images: {
        title: "res/title.png",
        map:   "res/map.png",
        floor: "res/floor.png",
        tiles: "res/underworld_tiles.png",
        items: "res/items.png",
        link:  "res/link.png"
      },
      modules: ["id.MapQuest"]
    })

    if (loader.ready()) {
        // if everything was cached (or we're on a really fast network),
        // then we're ready to proceed.
        init_game();
        delete window.loader;
    } else {
        // still waiting for things to preload; listen for onReady
        var listener = dojo.subscribe("loc.Preloader.onReady", function() {
            dojo.unsubscribe(listener);
            init_game();
            delete window.loader;
        });
    }
} // end of preload()

function init_game() {
    window.game = new id.MapQuest({
        ...
    });
}

// ------------------------------------------------
// (in Preloader.js)
//
dojo.provide("loc.Preloader");
dojo.require("loc.ImageCache");
dojo.addOnLoad(function() {
    if (!window.imageCache) { window.imageCache = new loc.ImageCache(); }
});

dojo.declare("loc.Preloader", null, {
    images: {},
    modules: [],
    _isReady: false,
    constructor: function preloader_constructor(args){
        dojo.mixin(this, args);

        for (var i in this.images) {
            // treat item as a key:value pair
            window.imageCache.addImage( i, this.images[i] );
        }
        for (i in this.modules) {
            // load each requested item and then register an OnLoad handler
            //   to be notified when they're all done
            dojo.require(this.modules[i]);
        }
        dojo.addOnLoad(function() {
            if (window.imageCache.ready()) {
                dojo.publish("loc.Preloader.onReady",[]);
            } else {
                this.listener = dojo.subscribe(
                    "loc.ImageCache.onReady",
                    function() {
                        dojo.unsubscribe(this.listener);
                        delete this.listener;
                        this._isReady = true;
                        dojo.publish("loc.Preloader.onReady",[]);
                    }
                );
            }
        });
    },
    ready: function preloader_ready() {
        return this._isReady;
    }
});

// ------------------------------------------------
// (in ImageCache.js)
//
dojo.provide("loc.ImageCache");
dojo.declare("loc.ImageCache", null, {
    constructor: function ImageCache_constructor(args) {
        this._sprites = {};
        this._loadStatus = {};
    },
    addImage: function addImage(name,src) {
        if (name in this._sprites) {
            return;
        } else {
            this._sprites[name] = new Image();
            this._loadStatus[name] = false;
            this._sprites[name].src = src;
            this._sprites[name].onload = dojo.hitch(this, function() {
                this._loadStatus[name] = true;
                if (this.ready()) {
                    dojo.publish("loc.ImageCache.onReady",[]);
                }
            });
        }
    },
    hasImage: function hasImage(name) { return (name in this._sprites); },
    getImage: function getImage(name) {
        if (name in this._sprites) {
            return this._sprites[name];
        } else {
            console.error("Image name '", name, "' not found");
            return null;
        }
    },
    ready: function ready() {
        var retVal = true;
        for (var id in this._sprites) {
            if (id in this._loadStatus) {
                retVal &= this._loadStatus[id];
            } else {
                return false;
            }
        }
        return retVal;
    }
});

Making it Better

While this approach works, there are several ways we can go about improving it:

  1. If you read my previous post, you know that crafting our modules in an AMD-compatible format takes care of code preloading, so that's one less thing we need our Preloader to do.
  2. Notice that both Preloader and ImageCache have an onReady event (implemented using Dojo's publish/subscribe methods). That's a clue that we can probably collapse their functionality into a single class.
  3. While we're making changes, and given the broad browser support for the HTML5 <audio> tag, it's probably worth broadening our cache support from just images to include audio assets as well (ultimately we may want to include other types of media like video or fonts too, but that's not necessary just yet).

Bearing these ideas in mind, here's what a new, streamlined, asset-agnostic, AMD-compatible Preloader might look like (note that I've omitted a few details for the sake of brevity; you can review the whole thing over on GitHub):

define(
    ['atto/core','atto/event'],
    function(atto,CustomEvent) {
        function constructor(args) {
            var _assets     = {},
                _loadStatus = {},
                _events = {
                    error:  new CustomEvent('tangle.assetCache.error'),
                    ready:  new CustomEvent('tangle.assetCache.ready'),
                    loaded: new CustomEvent('tangle.assetCache.loaded'),
                },
                _types = {
                    IMAGE: 1,
                    AUDIO: 2
                },
                _extension_to_type = {
                    png: 1, bmp: 1,  gif: 1, jpg: 1, jpeg: 1,
                    wav: 2, webm: 2, ogg: 2, mp3: 2, aac: 2
                };

            function _assetTypeFromFilename(fileName) { ... }

            function _createLoadCallback(assetName) { ... }

            function _createErrorCallback(assetName) { ... }

            function _addAsset(name, src) {
                if (name in _assets) {
                    _events.error.dispatch({name:name,
                      details:"Asset '"+name+"' is already in the cache"});
                    return;
                } else {
                    var assetType = _assetTypeFromFilename(src);
                    switch (assetType) {
                        case _types.IMAGE:
                            _assets[name] = new Image();
                            atto.addEvent(_assets[name], 'load',
                              _createLoadCallback(name), false);
                            atto.addEvent(_assets[name], 'error',
                              _createErrorCallback(name), false);
                            break;

                        case _types.AUDIO:
                            _assets[name] = new Audio();
                            atto.addEvent(_assets[name], 'canplaythrough',
                              _createLoadCallback(name), false);
                            atto.addEvent(_assets[name], 'error',
                              _createErrorCallback(name), false);
                            break;

                        default:
                            // unsupported asset type
                            _events.error.dispatch({name:name,
                              details:"Asset type '"+assetType+"' not supported."});
                            return false;
                            break;
                    }

                    _loadStatus[name] = false;
                    _assets[name].src = src;
                }
            }

            function _hasAsset(name) { ... }
            function _getAsset(name) { ... }
            function _getAssetType(name) { ... }

            function _ready() {
                var retVal = true;
                for (var id in _assets) {
                    if (id in _loadStatus) {
                        retVal &= _loadStatus[id];
                    } else {
                        return false;
                    }
                }
                return retVal;
            }

            return {
                TYPES        : _types,
                addAsset     : _addAsset,
                hasAsset     : _hasAsset,
                getAsset     : _getAsset,
                getAssetType : _getAssetType,
                ready        : _ready,
                events       : _events
            } // end of public interface
        } // end of constructor

        return constructor;
    } // end AMD callback function
);

Let's take a look at a few of the more interesting pieces here.

AMD Wrapper

define(
    ['atto/core','atto/event'],
    function(atto,CustomEvent) {
        function constructor(args) {

            ...

            return {
                TYPES        : _types,
                addAsset     : _addAsset,
                hasAsset     : _hasAsset,
                getAsset     : _getAsset,
                getAssetType : _getAssetType,
                ready        : _ready,
                events       : _events
            } // end of public interface
        } // end of constructor

        return constructor;
    } // end AMD callback function
);

We've seen this before, it's the same basic AMD-flavored version of the Revealing Module pattern that I showed off in the previous post in this series. As in that case, I'm using AMD's define method to specify my module's dependencies, here a couple of modules from my minimal JavaScript library Atto, and return a factory function that can be used by downstream code to create an instance of my class. So far, so good.

Communicating Events

_events = {
    error:  new CustomEvent('tangle.assetCache.error'),
    ready:  new CustomEvent('tangle.assetCache.ready'),
    loaded: new CustomEvent('tangle.assetCache.loaded'),
}
...
_events.error.dispatch({name:name, details:"Asset '"+name+"' is already in the cache"});
_events.ready.dispatch({});

This is new, but not really worth going into too much detail about here. You can think of Atto's Event class as a slightly more robust implementation of the basic pub/sub pattern, but using object literals instead of strings for the topics. As the browser progresses through the preload process, AssetCache dispatches events that can be watched by the main game code to show a loading progress bar, display a status message, or to know when the assets are available to be used:

var progressBar = new ProgressBar({min: 0, max: assetCount});

cache.events.error.watch(function(data) {
   _log(data.details);
});

cache.events.loaded.watch(function(data) {
   _log('Loaded asset ' + data.name + '.');
   progressBar.setValue(progressBar.getValue() + 1);
});

cache.events.ready.watch(function(data) {
   _log('All assets loaded.');
   progressBar.setValue(progressBar.getMax());
   game.setState(game.states.TITLE);
});

Determining Load Status

That leads us to AssetCache's bread and butter, the code that actually does the asset preloading:

function _createLoadCallback(assetName) {
    return function() {
        _loadStatus[assetName] = true;
        _events.loaded.dispatch({name:assetName});
        if (_ready()) _events.ready.dispatch({});
    }
}
function _createErrorCallback(assetName) {
    return function() {
        _loadStatus[assetName] = false;
        _events.error.dispatch({name:assetName,
            details:"Asset '"+assetType+"' failed to load."});
    }
}
...
var assetType = _assetTypeFromFilename(src);
switch (assetType) {
    case _types.IMAGE:
        // image: this is by far the simple case
        _assets[name] = new Image();
        atto.addEvent(_assets[name], 'load', _createLoadCallback(name));
        atto.addEvent(_assets[name], 'error', _createErrorCallback(name));
        break;

    case _types.AUDIO:
        // audio; this will take a little more magic, due to browser codec issues
        _assets[name] = new Audio();
        atto.addEvent(_assets[name], 'canplaythrough', _createLoadCallback(name));
        atto.addEvent(_assets[name], 'error', _createErrorCallback(name));
        break;

    default:
        // unsupported asset type
        _events.error.dispatch({name:name,
            details:"Asset type '"+assetType+"' not supported."});
        return false;
        break;
}
_loadStatus[name] = false;
_assets[name].src = src;

We have some very basic code to determine file type based on its extension, then create the appropriate DOM element, Image or Audio (we should probably wrap the Audio case in a try/catch block, since we're trying to retain as much cross-browser compatibility as possible, and not all browsers support the Audio tag). Then, we attach a couple of event handlers to the element to keep track of its loading progress, and assign its src attribute to the appropriate URL to initiate the load. The Image element has pretty straightforward load and error events, but Audio, in contrast, doesn't fire a load event when it's finished loading. Instead, you get the canplaythrough event, which fires when the browser decides it has buffered enough data from the file so that if you start playing immediately, it will have the whole thing soon enough to play without stuttering. That's close enough for our purposes.

Audio Support Still Stinks

One note on preloding audio assets: AssetCache's support for audio files will depend on your browser and operating system. On my Windows 7 laptop, Google Chrome (v20) can load and play back both the mp3 and ogg audio files, but Firefox (v13) only supports the ogg file, and IE9 only supports the mp3! Neither audio file is loadable on my locked-down work computer, where I'm stuck with IE8, and I couldn't get either one to load on my stock Android 2.3 browser either. I haven't delved any deeper into the mobile issue, but I suspect that the audio object may have actually loaded, but not fired the canplaythrough event. Or maybe it couldn't load either format, I'm not entirely sure.

We'll revisit this crazy audio situation in a later tutorial to find a better cross-browser solution. You'd think that this would be an ideal use case for a library: it would be so much nicer if your app code could just specify a single, named audio asset, and let the library figure out the right file format to load based on browser and platform capabilities. In theory, the audio element exposes a method that reports on your browser's ability to play various file types, but it's notoriously vague and generally unhelpful, so relying on it to determine a caching strategy may not be very helpful.

Demo Time!

I've set up a simple demo page that shows off AssetCache's basic functionality, albeit not yet in the context of an actual game.

AssetCache should work in all modern browsers, and as I said, it even has limited support for Internet Explorer 8. Even though Tangle is just a reference implementation and not a truly production-ready library, as a general principle I try to build things that are as broadly usable as possible, so in theory parts of Tangle could be used to build a game that gracefully degrades on older, less-capable browsers. After all, the whole principle of modular dependency loading that AMD enables means that if you want to cherry-pick certain pieces that you find useful and not use the rest of the library, you're free to do so.

Next time we'll cover the basics of building a game loop, and actually use AssetCache to build something interesting.

Resources

Let's Make a Canvas Library: Modules

Wednesday, July 4, 2012

Hello and welcome to part 1 of Let's Make a Canvas Library, a tutorial series focused on the principles of building a good personal library for working with HTML Canvas.

But first, an apology: I originally announced this blog series immediately after my presentation at UtahJS on the same subject, and promised that part 1 would be going online that Friday. Obviously, it didn't.

So, what happened? I fell in love:

What's so great about AMD?

AMD, officially known as the Asynchronous Module Definition API, has been gaining steam in recent months as a solution for several longstanding issues in the JavaScript world: non-blocking script loading, dependency management, standardized module definition syntax, and global namespace abuse. The Dojo toolkit, which I've been using for the past few years, had a fairly elegant approach to modules that addresses a lot of these concerns, and it's one that I've put to good use in my personal game-development library. However, over the past year Dojo has been moving away from that format and migrating their codebase over to AMD-style modules, which are quickly becoming the de facto standard for client-side JavaScript libraries* (jQuery, MooTools, and Firebug have all built in AMD support recently). While similar in spirit, the AMD API is different enough that I was unwilling to take the time to figure it out and convert my old code, until a recent project at work gave me the opportunity to build something new from scratch.

In short, I'm an AMD convert. Everything that felt hacky to me about JavaScript before, from documenting a module's dependencies in its header comments, to including a pile of link and script tags in my HTML pages in the exact right order to ensure proper dependency preloading, has a solution in the AMD approach. Take, for example, the simple TabContainer widget I built as part of my minimal Atto library. Here's what you had to do in your HTML to use the original, non-AMD version:

<link rel="stylesheet" href="/js/atto/awAccordion.css"/>
<script src="/js/atto/aw-core.js"></script>
<script src="/js/atto/awAccordion.js"></script>
<script>
    aw.core.addLoadEvent(function() {
        var myWidget = new aw.Accordion(aw.core.byId("containerNode"), {});
    });
</script>

There are a couple of things about this not to like. First, the widget won't work without the associated CSS syles, so we either have to explicitly link to the appropriate CSS file in the invoking HTML (ugh), or execute a hack of some kind in the JavaScript to guess the correct URL for the CSS file (tricky, since a JavaScript file doesn't know its own URL location when it's being evaluated), and then load it via Ajax.

The second problem with this approach is that awAccordion.js can't stand on its own, as it relies on a window-level object called aw having been created first by its implicit dependency, aw-core.js. This object would serve as container for the new widget's factory class, but also provided necessary helpers (like _loadResource) that simplified the widget's logic and eliminated redundant code paths for pages that included multiple Atto widgets. To be sure, centralizing reusable code is a good thing, but it's pretty nasty that in order to use this (admittedly very barebones) widget, my HTML page has to reference not one, but three separate files (one CSS and two JavaScript), and use an explicit onLoad event to know when everything was actually ready to use.

Compare that to the code needed to use the updated AMD-compatible version of this same widget:

<script src="/js/require.js"></script>
<script>
    require(["atto/core", "atto/accordion"], function(atto, Accordion) {
        var myWidget = new Accordion(atto.byId("containerNode"), {});
    });
</script>

When using an AMD-compatible loader (there are several; RequireJS and curl are two of the most frequently used), you get two global functions: require() and define(). Although there are a few alternative calling syntaxes, require typically takes two arguments: a list of strings representing the resources you need, and a function to execute when those resources (and any of their dependencies) are all loaded, which eliminates the need for an onLoad event. Usually this callback function should define an argument for each module in the required list, so that the loader has a way to give you a reference to it. For example, in the above code, we're requiring two modules, "atto/code" and "atto/accordion", and the callback names these atto and Accordion (although I could have called them "kumquat" and "rutabaga" if I had wanted to, since these are just references).

One important note: outside of this callback, neither atto nor Accordion are defined or available. This keeps the global namespace clean, but may take some getting used to for programmers who are accustomed to having everything available off of the window or document objects.

The second function provided by an AMD-compatible loader is define, and is used, logically enough, to define a module. Here's a simple example of a widget factory class:

// declare the Atto Accordion widget
define(
    ["atto/core","require"], // explicitly declare Atto Core as a dependency
    function(atto) {
        // make sure the appopriate CSS has been loaded for this widget
        var forWidget = "atto-accordion";
        if (!document.querySelector("style[data-for-widget='"+forWidget+"']")) {
            require(["text!atto/accordion.css"], function(rawCss) { atto.addWidgetCss(rawCss, forWidget); });
        }

        function constructor(rootNode, optionArray) {
            // (private variables and function definitions omitted for brevity)

            return {
                root     : _root,
                addPanel : _addPanel
            } // end of public interface
        } // end of constructor

        return constructor;
    } // end function
);

The typical calling signature for define mirrors that of require: a list of strings representing the module's dependencies, and a function that is executed when those dependencies are loaded, which returns the contents of the module (in this case a constructor function for an Accordion widget). The AMD format allows you to explicitly specify a module name as the first argument to define, but current best practice is to omit it, allowing an optimization tool to fill in the name at compile time.

Okay, I get it, AMD's awesome. So what?

So my first topic for Let's Make a Canvas Library was going to be about asset management and preloading, which I had approached previously in a way that was very strongly tied to Dojo's module loader. When I became an AMD convert, I realized this was no longer the best way to solve the problem of preloading. In fact, the need for the Preloader class, which took an arbitrary list of Dojo module names and image paths and preloaded them all, all but disappeared! We'll still need an AssetCache class to handle preloading of images and sounds, but this post is long enough, so we'll save that for next time.


* Yes, I realize that AMD isn't the only option, and that CommonJS has its fans. Personally, though, I feel like AMD is easier to grok than CommonJS, so there you go.

Let's Make a Canvas Library

Sunday, June 3, 2012

This past Friday I was privileged to speak at the first ever Utah JavaScript conference, UtahJS, in Salt Lake City. My talk centered on the challenges inherent to using HTML5 Canvas to build rich internet games, and advocated the (perhaps radical) notion that instead of using one of the many, many available game frameworks or libraries, aspiring game devs would be better off writing their own:

  • you can be assured that you're not pushing any extra, unnecessary bytes to client devices (especially important on mobile)
  • you have full control over your module's interfaces, so don't have to conform to someone else's coding style
  • you get a better understanding of how things work, which becomes important when you need to debug things that go awry.

The talk was primarily focused on principles, rather than on concrete code examples, although I did have a little time at the end to briefly run through some of the code I'd written for Canvassa, which evolved into Infinite Dungeon, and which I am now porting to Fenjin. I got the sense both from questions and comments during my talk as well as several conversations afterward that people wanted a little more meat. So, let's build something together!

Let's Make a Canvas Library is a new series I'll be writing here on Palagpat Coding. Together, we'll walk through the coding of a simple HTML5 Canvas game, building up our custom game library as we go. Over the coming weeks, I expect to cover the following topics:

  • Preloading & caching game assets (images, sounds, etc)
  • Constructing the game loop
  • Managing sprites
  • Handling interactions between the player, enemies, and stage (i.e. collision detection)
  • Basic physics (and why you probably don't need Box2D)
  • Cross-platform sound
  • Multiple input/control methods

It's likely that this list will change as the game begins to take shape, and some topics will take longer to cover than others. My goal isn't to build something robust and put it on Github for all the world to use; rather, it's to walk you through building something that gets you started with your own library, custom-tailored to the kinds of things you'd want a library to do for you.

You can expect to see the first article in the series this Friday. Until then!