Hurdles Are Only Hurdles ‘Til They’re Not

I’ve just had a quick flick through my blog posts from last year. It makes me laugh, how much of a song and dance I made about learning Unity. It took a few months of struggle, but I’m in a comfortable spot with it now. Any game I want to create with it, I could do. Anything I don’t know how to do, I can learn. It’s amazing how much confidence you get from writing your own shaders ūüôā

Don’t get me wrong – I still feel like I’m going to give myself RSI, what, with all the pointing and clicking you have to do in the interface (give me a Sublime Text any day!) The UI, being entirely alien to a text-editor jockey like myself, was the main cause of cognitive dissonance – not, as I had expected, the move from JS to C#. Unity is very opinionated in how it does things; in the end, it was simply a matter of agreeing to disagree, and doing things Unity’s way. Sometimes you have to write less than perfect code, rather than the glorious, perfectly encapsulated abstraction you had in mind. Does it grate? Yes. Does it matter? No. (Or, it might, later on, but you were going to go back and refactor anyway, weren’t you?)

You might wonder why I put myself through the trouble. I know JS pretty damn well, I could have easily written a mobile game (from scratch, with Impact.js, using Phaser (or whatever)) in half the time; wrapped it in Cordova and plonked it on the App Store, job done. So why didn’t I?

I’ve written a few HTML5 mobile games for clients. Some pretty big clients. And, clients being clients, they’re not bothered how much of a feat of engineering it was to get the same game working perfectly cross-browser, cross-platform, on desktops, tablets and mobile phones. They only care that sound doesn’t play in the Android stock browser on a (manufacturer modified) Jiazu N3 running Android v1.36.34x alpha-something or other when they hold the phone in landscape orientation whilst standing on one foot and hopping in a circle.

If it was painful for one client, I reasoned, what would it be like when your ‘clients’ are the general public? I don’t want to deal with one-star reviews and¬†irate¬†emails¬†from players experiencing¬†bugs¬†on niche devices that I can’t possibly lay hands on. I’d rather spend more time up front and trust Unity to deliver a consistent cross-platform experience.

I appreciate that this rationale is contrary to my usual, lean-development minded take on things. There’s a lot to be said for getting an MVP released, and iterating on quickly based on customer feedback. But I’ve heard enough horror stories from fellow developers working on both desktop and mobile wrapped HTML5 games to confirm my views on this. Once my game is out, I need to be focused on player acquisition and implementing retention-focussed features – not firefighting edge case bugs and negative reviews.

So! That’s why – despite the fact that I love it to bits – I will not be using HTML5/JavaScript to build native(ish) mobile games.


In other news: PEAS! ūüėÄ

My lovely little veg garden is coming along – it’s already bigger than the herb garden I created last year. It’s amazing how a tiny¬†plant – something that I could fit into the palm of my hand – is now almost as tall as I am, and provides me with fresh, delicious peas whenever I care to venture outside.

It’s silly how much it enthrals¬†me. And slightly worrying. The whole process feels new and exciting to me, which is great – but doesn’t this demonstrate how far removed we are generally from the methods of food production? That ziplock¬†bag of peas that you buy from Sainsbury’s on your food shop every couple of weeks? It’s easily the entire crop of five times the plant you see here. Just something to think about.

What happened to PlayZap?

A couple of years ago, I developed a HTML5 mobile game portal. Built with Node.js at the backend, and Ratchet.js (something like a precursor to Bootstrap, but specifically for mobile devices) on the front end, it was one of the first of it’s kind.

It was reasonably successful – successful in so far as it achieved the initial goals I set for it, but unsuccessful in that I couldn’t (or didn’t know how to) pivot and turn that success into a self-sustaining business.

In 2012, HTML5 games were in their infancy, and, being a web developer with a keen interest in games, I wanted to do something to push the technology – to show what it was capable of. I’m not going to pretend that my motivations were entirely altruistic – as a freelancer, growth in adoption of HTML5 meant that I could write games for clients instead of websites, which at that point I had been doing for years. The new tech was exciting, it had promise, all the right noises were being made, I wanted to work with it. I was going to work with it (I did work with it!), but it’s even better if you can get paid for it.

I decided to create a games portal, solely for HTML5 mobile games. Developers would be able to show off their games and profit by the adverts shown in them, and I’d have something to point to to prove how awesome HTML5 really was. I shipped PlayZap in early 2013.

After a slow launch, adoption quickly picked up as I spent a bit of time on marketing. I¬†had thousands of users¬†each month – both players and developers seemed to really enjoy the site. The stumbling block arose when I tried to think of how to turn it into a business. Server costs were negligible (I was hosting a dedicated instance on EC2), but I didn’t want to self-fund it forever.

As the tech was new, there wasn’t a business model as such to follow; but it was supposed that it would work in a similar way to how the Flash ecosystem used to work. Developers would integrate advertising APIs into their games, which were then (in lieu of an exclusive licence bought by a portal) shared widely on portals. It was a¬†mutually beneficial relationship – portals provided the platform and the traffic, developers provided the games. The more eyeballs, the more gameplays, the more ad revenue for both the developer and the portal.

It didn’t quite pan out that way. In fact opposite happened – ad providers like Mochi, rather than pivoting, simply shut down.

There was – and, as far as I’m aware, this is still the case – a total lack of HTML5 ad providers that cater to mobile web games. Without providers like Mochi, early adopters turned to AdSense, on the assumption that, as HTML5 games are basically the same as web pages, they’d be able to use AdSense in their games. Some tried to use the square banners as interstitials, and many were banned for it. No one tried to fill the gap in the market, and the few that did have a suitable API placed such restrictions on their APIs’ use as to make implementation infeasible.

Without an interstitial ad provider on the horizon, all I had was banner ads that didn’t cover the bills.

In the end, PlayZap was very much a project without a business model. Others¬†have pivoted to providing games for messaging apps, or used the partnerships they forged with developers to move into the mobile space. HTML5 has landed, but it found it’s footing elsewhere.

For me,¬†the portal served it’s original purpose.¬†I shuttered PlayZap earlier this year.

Do I regret doing it? Not at all. PlayZap started as a learning experience, and ended as a learning experience. I learned more about responsive user experience. I learned Node.js. I learned about hosting MongoDB in a production environment (hint: Monit is your friend). I learned that, if you want to turn something into a business, you need to know what your business model is *before* you build it. All valuable lessons.

Web Audio API Tutorial

Welcome to this tutorial on the Web Audio API. I’ll show you how to load and play sounds, how to adjust the volume, how to loop sounds, and how to crossfade tracks in and out – everything you need to get started implementing audio into your HTML5 games.

Web Audio – a brief history

Before the release of the Web Audio API in 2011, the only cross-platform way of playing audio in the browser (without using Flash) was with the <audio> element. The <audio> element has a very basic feature set – there isn’t much to it beyond loading, playing, pausing and stopping a single track.

For the game developers taking advantage of the new and improved graphics APIs (WebGL, Canvas), audio support – or lack thereof – was a source of constant frustration. As graphics advanced, the paucity of the <audio> feature set became more pronounced. Worse still, <audio> was plagued by bugs across the different browser implementations, thwarting developers’ attempts to use the API for even the most basic of it’s intended purposes.

Ingenious hacks had to be devised – the ‚Äėaudio sprite‚Äô*¬†was invented simply to get audio to work correctly in iOS. Developers clamoured for a better audio API to complement the rich, engaging visual experiences they were creating with the far-superior graphics APIs.

Enter, the Web Audio API.

The Web Audio API

The Web Audio API enables developers to create vibrant, immersive audio experiences in the browser. It provides a high-level abstraction for manipulating and controlling audio.

The API has a node-based architecture: a sound can be routed through several different types of nodes before reaching it’s end-point. Each node has it’s own unique purpose; there are nodes for generating, modifying, analysing and outputting sounds.

Where is it supported?

web-audio-browser-support
The Web Audio API is currently supported in all good browsers.

Test for API Support

Before you can load a sound, you first need to check whether the API is supported in your target browser. This snippet of code attempts to create an AudioContext.

var context;

try {
  // still needed for Safari
  window.AudioContext = window.AudioContext || window.webkitAudioContext;

  // create an AudioContext
  context = new AudioContext();
} catch(e) {
  // API not supported
  throw new Error('Web Audio API not supported.');
}

View demo

If the test fails, you have one of three options: a) ignore it (user gets no audio); b) use an <audio> sprite; c) use a Flash fallback. Personally, I’m in favour of option a) – as mentioned in the footnotes, audio sprites are a real pain in the backside to create, and Flash is a no-go in HTML5 mobile games.

Load a sound

Next, we’ll load a sound. The binary audio data is loaded into an ArrayBuffer via Ajax. In the onload callback, it’s decoded using the AudioContext’s decodeAudioData method. The decoded audio is then assigned to our sound variable.

var sound;

/**
 * Example 1: Load a sound
 * @param {String} src Url of the sound to be loaded.
 */

function loadSound(url) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';

  request.onload = function() {
    // request.response is encoded... so decode it now
    context.decodeAudioData(request.response, function(buffer) {
      sound = buffer;
    }, function(err) {
      throw new Error(err);
    });
  }

  request.send();
}
// loadSound('audio/BaseUnderAttack.mp3');

View demo

Testing file format support

To ensure that our audio is playable wherever the Web Audio API is supported, we’ll need to provide the browser with two variants of our audio source, in MP3 and Ogg format. This code snippet checks whether the browser can play Ogg format audio, and helps us to fall back to the MP3 where it’s not supported.

var format = '.' + (new Audio().canPlayType('audio/ogg') !== '' ? 'ogg' : 'mp3');
// loadSound('audio/baseUnderAttack' + format);

View demo

Play a sound

To play a sound, we need to take the AudioBuffer containing our sound, and use it to create an AudioBufferSourceNode. We then connect the AudioBufferSourceNode to the AudioContext’s destination and call the start() method to play it.

/**
 * Example 2: Play a sound
 * @param {Object} buffer AudioBuffer object - a loaded sound.
 */

function playSound(buffer) {
  var source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start(0);
}
// playSound(sound);

View demo

Load multiple sounds

To load more than one sound, reference your sounds in a format that can be iterated over (like an array or an object).

var sounds = {
  laser : {
    src : 'audio/laser'
  },
  coin : {
    src : 'audio/coin'
  },
  explosion : {
    src : 'audio/explosion'
  }
};


/**
 * Example 3a: Modify loadSound fn to accept changed params
 * @param {Object} obj Object containing url of sound to be loaded.
 */

function loadSoundObj(obj) {
  var request = new XMLHttpRequest();
  request.open('GET', obj.src + format, true);
  request.responseType = 'arraybuffer';

  request.onload = function() {
    // request.response is encoded... so decode it now
    context.decodeAudioData(request.response, function(buffer) {
      obj.buffer = buffer;
    }, function(err) {
      throw new Error(err);
    });
  }

  request.send();
}
// loadSoundObj({ src : 'audio/baseUnderAttack' });


/**
 * Example 3b: Function to loop through and load all sounds
 * @param {Object} obj List of sounds to loop through.
 */

function loadSounds(obj) {
  var len = obj.length, i;

  // iterate over sounds obj
  for (i in obj) {
    if (obj.hasOwnProperty(i)) {
      // load sound
      loadSoundObj(obj[i]);
    }
  }
}
// loadSounds(sounds);

View demo

Adjusting the volume

In the ‘play’ example, we created an AudioBufferSourceNode, and then connected it to a destination. To change the volume of an audio source, we need to create an AudioBufferSourceNode as before, but then we create a GainNode, and connect the AudioBufferSourceNode to that, before connecting the GainNode to the destination. Then we can use the GainNode to alter the volume.

sounds = {
  laser : {
    src : 'audio/laser',
    volume : 2
  },
  coin : {
    src : 'audio/coin',
    volume : 1.5
  },
  explosion : {
    src : 'audio/explosion',
    volume : 0.5
  }
};


/**
 * Example 4: Modify the playSoundObj function to accept volume property
 * @param {Object} obj Object containing url of sound to be loaded.
 */

function playSoundObj(obj) {
  var source = context.createBufferSource();
  source.buffer = obj.buffer;

  // create a gain node
  obj.gainNode = context.createGain();

  // connect the source to the gain node
  source.connect(obj.gainNode);

  // set the gain (volume)
  obj.gainNode.gain.value = obj.volume;

  // connect gain node to destination
  obj.gainNode.connect(context.destination);

  // play sound
  source.start(0);
}
// loadSounds(sounds);

View demo

Muting a sound

To mute a sound, we simply need to set the value of the gain on the GainNode to zero.

var nyan = {
  src : 'audio/nyan',
  volume : 1
};
loadSoundObj(nyan);


/**
 * Example 5: Muting a sound
 * @param  {object} obj Object containing a loaded sound buffer.
 */

function muteSoundObj(obj) {
  obj.gainNode.gain.value = 0;
}
// muteSoundObj(nyan);

View demo

Looping sounds

Whenever you’re creating any game, you should always be mindful of optimising file sizes. There’s no point making your player download a 10Mb audio file, when the same effect is achievable with 0.5Mb of looped audio. This is especially the case if you’re creating games for HTML5 mobile game portals.

To create a looping sound, set the loop attribute of the AudioBufferSourceNode’s to true just before connecting it to the GainNode.

sounds = {
  laser : {
    src : 'audio/laser',
    volume : 1,
    loop: true
  },
  coin : {
    src : 'audio/coin',
    volume : 1,
    loop: true
  },
  explosion : {
    src : 'audio/explosion',
    volume : 1,
    loop: true
  }
};


/**
 * Example 6: Modify the playSoundObj function again to accept a loop property
 * @param {Object} obj Object containing url of sound to be loaded.
 */

function playSoundObj(obj) {
  var source = context.createBufferSource();
  source.buffer = obj.buffer;

  // loop the audio?
  source.loop = obj.loop;

  // create a gain node
  obj.gainNode = context.createGain();

  // connect the source to the gain node
  source.connect(obj.gainNode);

  // set the gain (volume)
  obj.gainNode.gain.value = obj.volume;

  // connect gain node to destination
  obj.gainNode.connect(context.destination);

  // play sound
  source.start(0);
}
// loadSounds(sounds);

View demo

Crossfading sounds

Making use of multiple audio tracks is a great way to aurally demarcate the different areas of your game. In Brickout, for example, I crossfade between the title music and the game music when the game starts, and back again when it ends.

To crossfade between two tracks, you’ll need to schedule a transition between the gain volume ‘now’ and a time fixed in the future (e.g. ‘now’ plus 3 seconds). ‘Now’ in Web Audio terms is the AudioContext’s currentTime property – the time that has elapsed since the AudioContext was created.

var crossfade = {
  battle : {
    src : 'audio/the-last-encounter',
    volume : 1,
    loop : true
  },
  eclipse : {
    src : 'audio/red-eclipse',
    volume : 0,
    loop : true
  }
};


/**
 * Example 7: Crossfading between two sounds
 * @param  {Object} a Sound object to fade out.
 * @param  {Object} b Sound object to fade in.
 */

function crossFadeSounds(a, b) {
  var currentTime = context.currentTime,
      fadeTime = 3; // 3 seconds fade time

  // fade out
  a.gainNode.gain.linearRampToValueAtTime(1, currentTime);
  a.gainNode.gain.linearRampToValueAtTime(0, currentTime + fadeTime);

  // fade in
  b.gainNode.gain.linearRampToValueAtTime(0, currentTime);
  b.gainNode.gain.linearRampToValueAtTime(1, currentTime + fadeTime);
}
// crossFadeSounds(crossfade.battle, crossfade.eclipse);

View demo

LinearRampToValueAtTime has a catch that isn’t immediately apparent – if you try to change the gain after using it, nothing will happen. You need to cancel any scheduled effects you’ve applied before you can set the gain, even if your schedule has long since expired. You can do this with the cancelScheduledValues() method.

Final Tip

If you’re struggling to get a sense of what each of the vast array of audio nodes does, head over to the spec. Under each node’s subheading you’ll find the following:

numberOfInputs: n
numberOfOutputs: n

From this you can get a rough idea of what the node does. If the node has no inputs and one output, it will load or synthesise audio. If it has one input and n outputs, an audio source can be connected to it and modified or analysed in some respect. If the node has inputs but no output, it will be an end-point – the final destination that connects your audio to your user’s headphones or speakers.

Further Reading

https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

 

*The process of creating an audio sprite was painstaking – all of your game’s audio had to be composited into one file, and once loaded the ‘playhead’ had to be jogged back and forth between each of the ‘sprites’. This workaround still had it’s downsides – sounds were often clipped if a new sound was triggered before the previous sound had finished playing.

iOS 7 Safari: Breaking Changes for HTML5 Games

If you have any experience of making HTML5 games for mobile web browsers, you’ll recognise this bit of code:

// set height of the document to be greater than screen size
document.body.style.height = screen.height * 2 + 'px';

// scroll down the page a bit to hide url bar
window.scrollTo(0, 1);

// set the view frame to the height of window
if (window.innerWidth < window.innerHeight) {
    $frame.style.height = window.innerHeight + 'px';
}

This code is used to hide the URL bar in mobile browsers. The game view is fixed (using CSS) within the browser window – giving players the maximum space available to play your game.

iOS 6 on an iPhone 4S, window.scrollTo hack working

In iOS 7, Safari introduces ‘fullscreen browsing’ – as soon as a user scrolls down the page, both the url bar at the top and the navigation bar at the bottom are hidden from view. The bars are revealed again by browsing to the top of the page (or tapping the clock ‘scroll to the top’ shortcut).¬† This gives the user much more screen ‘real estate’ when browsing the web.

Unfortunately this feature, whilst being a step in the right direction for general purpose web consumption, is a real pain in the backside for HTML5 game developers. From my tests, it appears that any scroll behaviour must be user initiated. The window.scrollTo hack doesn’t work anymore. The page scrolls to whatever you set the Y value to, but then jumps back to Y = 0.

IMG_0075
iOS 7 on an iPhone 5, window.scrollTo hack no longer works :(

This change is going to (and has, because I’ve checked) cause problems for a lot of games. Luckily, the beta implementation of landscape mode never made it through to the final version.¬† That would have been a real nightmare for landscape-only games, because there was practically no viewport visible to play the game in.

IMG_0033-1
iOS 7 beta version of landscape mode
IMG_0077-1
iOS 7 final version of fullscreen landscape mode

This is bad news for HTML5 games and web apps alike.¬† I’d love to hear if you come up with a solution for this, because thus far, I’m stumped!

canvas-performance

Optimising HTML5 Canvas games

In this blog post I’m going to share a few tips and tricks I’ve picked up from the process of refactoring HTML5 Breakout. Some of these will be common sense performance tips; others might leave you scratching your head a bit. ¬†I’ll start with a few general tips, and then move on to the canvas specific ones. (The best stuff is at the bottom).

Don’t prematurely optimise your code

This can cause all sorts of bugs, like breaking your collision detection. ¬†You’ll end up having to backtrack, and it can become a bit of a nightmare, so it’s best not to do it!

Profile your code

Once you’ve finished your game, you need to profile your code. ¬†It’s important to benchmark your code on all of the browsers you’re targeting. ¬†Modern browsers compile and optimise your code, but the different JS engines do it in slightly different ways, so the function that runs lightning fast in one browser may perform sluggishly on another (for example, see ‘clear methods’ below). ¬†David Mandelin at Mozilla did a great presentation on JS engine internals at VelocityConf, it’s well worth going through the slide deck if you’re interested in the nitty gritty. Go for the low hanging fruit here and investigate any code that is reported as running slow across all your profiles.

Minimise code in loops

This is a common sense best practice, but it becomes especially important when you’re doing something as CPU intensive as repeatedly drawing images to canvas. ¬†When I was profiling Breakout, I discovered one function that was taking a particularly long time to execute. ¬†It turned out that I¬†had a call to fillRect in one of the loops in the drawBricks function. Simply moving this one line out of the loop shaved 50% of the execution time of that function. ¬†Little fixes that result in huge performance increases are always the most satisfying ūüôā

Minimise draw calls

As mentioned above, constantly drawing and redrawing images is hard work, so don’t draw if you don’t have to! ¬†It can be tempting to draw a frame and then clear the whole canvas repeatedly in your game loop, as it simplifies development. However, it doesn’t do much good for your game’s performance. ¬†If you can figure out which parts of your canvas have changed since the last frame, and only redraw those parts (by using ‘dirty rectangles‘), your game should see a huge performance boost.

Use more than one canvas

If you draw too many pixels to the same canvas at the same time, your frame rate will fall through the floor. ¬†In these circumstances, it’s better to use multiple canvasses layered on top of one another. Take Breakout for example. Trying to draw the bricks, the ball, the paddle, any power-ups or weapons, and then each star in the background – this simply won’t work, it takes too long to execute each of these instructions in turn. ¬†By splitting the starfield and the rest of the game onto separate canvasses, I was able to ensure a decent framerate. ¬†This is a useful technique for animation-heavy games.

Pixel snapping

If you’re a front-end developer used to working with the DOM, the concept of sub-pixel rendering will be a little odd – we’re just not used to seeing anything with units of measurement less than a pixel. ¬†Canvas can render your images at positions less than a pixel, anti-aliasing them in the process. ¬†Anti-aliasing canvas is currently super slow on some platforms, so it’s best to avoid it if you can¬†by rounding off the positions of your game entities just before each frame is drawn. ¬†I’ve been using | 0 (bitwise OR) – it’s faster than Math.round() as it doesn’t have the function call overhead, but it seems like the best method changes with each incremental browser version. ¬†Check with JSPerf and pick the best method for your use case. And read Seb Lee Delisle’s HTML5 canvas sprite optimisation post, he explains this much better than I do ūüôā

Clear methods

There are three ways to clear your canvas: fillRect using your background colour, clearRect, and resetting the canvas’ width (or height) property. ¬†Resetting the width of the canvas is supposedly the fastest method, but if you look at the JSPerf tests – particularly the differences between Chrome 14 and Firefox 4 – the execution speeds documented are wildly at odds with each other. ¬†From this data, it appears that width = width performs at the same speed or faster (much faster in some instances) than the other two methods on mobile, but when it comes to desktop browsers, all bets are off.

Another issue worth thinking about – if you’re doing a lot of transforms, width = width resets the transform stack, whereas clearRect and fillRect don’t. ¬†So if you’re doing a lots of transforms and don’t want to reset state, clearRect is probably the best way to go. ¬†Simon Sarris did a good write up of this called How you clear your HTML5 Canvas matters.

From JSPerf:

UserAgent clearing non transformed canvas clearing transformed canvas setting width # Tests
Chrome 14.0.786 14,779 14,495 417,562 1
Chrome 14.0.789 15,842 15,258 407,201 3
Firefox 4.0.1 188,673 143,651 1,741 27

Use requestAnimationFrame instead of setInterval/setTimeout

SetInterval and setTimeout were never intended to be used as animation timers, they’re just generic methods for calling functions after a time delay. ¬†If you set an interval for 20ms in the future, but your queue of functions takes longer than that to execute, your timer won’t fire until after these functions have completed. ¬†That could be a while, which isn’t ideal where animation is concerned. ¬†RequestAnimationFrame is a method¬†invented by Robert O’Callahan at Mozilla – it specifically tells the browser that an animation is taking place, so it can optimise repaints accordingly. ¬†It also throttles the animation for inactive tabs, so it won’t sap your mobile device’s battery if you leave it open in the background.

Nicholas Zakas wrote a hugely detailed and informative article about requestAnimationFrame on his blog which is well worth reading. ¬†If you want some hard and fast implementation instructions, then Paul Irish has written a requestAnimationFrame shim – this is what I’m currently using in my games.

Canvas has a back-reference

And finally, a useful tip I picked up at Remy Sharp’s¬†HTML5 Workshop – your 2d context has a back reference to it’s associated DOM element:

var ctx = doc.getElementById('canvas').getContext('2d');
console.log(ctx.canvas);    // HTMLCanvasElement

This can come in pretty handy!

I hope you’ve enjoyed my whistle-stop tour of canvas performance optimisation techniques. If you spot any errors or glaring omissions, please leave a comment below ūüôā

Cheers!

tile-editor

Curved and sloped tiles in JavaScript platform games

Demo: http://nicolahibbert.com/demo/javascript-tile-map-editor/

If you’ve been reading along recently, you’ll know that HTML5 Breakout is at a stage where I’m not entirely embarrassed by it, and I’m making a start on my next JavaScript game.

I’ve had an idea for a 2d sidescrolling platformer for a while, but I specifically want the player character to be able to move up and down slopes and curves (ala Sonic the Hedgehog). JavaScript game engines are still generally quite young and aren’t particularly full featured – I don’t know of any that implement this functionality out of the box, so I’m going to show you how I’m doing it. It involves separating out the alpha channel using the HTML5 Canvas API, and using this data to build a pixel-level map of where the character’s position should be at any single point on the tile.

Please note that this is a discussion of static per-pixel collision detection only, i.e., character vs. background tile map. I’m assuming some knowledge of tile based collision maps and of the Canvas API, but I’ll do a quick overview just in case.

Static collision detection

Your standard collision map in JavaScript is just a multidimensional array:

var map = [
    [1,0,0,0],
    [1,1,0,0],
    [1,1,1,0],
    [1,1,1,1]
];

The zeros represent an empty space on the map, and the ones represent where a collision will take place. Each ‘one’ is a rectangle: the dimensions of the tile, multiplied by it’s position in the map, drawn to the stage at it’s respective x and y co-ordinate. This map will usually be stored in a JSON format with some other information – tile dimensions, image file locations and the like.

This is great if you want flat platforms, but not so great if you want your character to be able to traverse curved and sloped surfaces.

Using the Canvas API to extract alpha channel data

Creating a sloped or curved tile is easy – you just have to ensure that your tile image has an alpha transparent component representing the path that you want your character to follow. This tile is saved with a bunch of other tiles to a sprite sheet. You can then use Canvas to extract the alpha channel data, and include this data as a part of your collision map.

buildMap : function(e) {
    var alpha = [], // used to store alpha data
        numTiles = 10, // size of tilemap (10 tiles high, 10 tiles wide)
        tileSize = 32, // size of tile in pixels
        pixels, // store pixel data array
        len, // cache length of pixel data array
        x, y, z;

    for (x = 0; x < numTiles; x++) { // tiles across
        alpha[x] = [];

        for (y = 0; y < numTiles; y++) { // tiles down
            pixels = map.getImageData(y * tileSize, x * tileSize, tileSize, tileSize),
            len = pixels.data.length;

            alpha[x][y] = [];

            for (z = 0; z < len; z += 4) {
                alpha[x][y][z / 4] = pixels.data[z + 3]; // store alpha data only
            }

            if (alpha[x][y].indexOf(0) === -1) { // solid tile
                alpha[x][y] = 1;
            } else if (alpha[x][y].indexOf(255) === -1) { // transparent tile
                alpha[x][y] = 0;
            } else { // partial alpha, build pixel map
                alpha[x][y] = pixels;
            }
        }
    }
}

This bit of code retrieves image data from a canvas, breaks it down into tile sized chunks, and then stores the image data for each of these tiles into an array. It then checks the alpha data for each tile: if the tile is comprised entirely of alpha data, then the tile is transparent – it’s value is set to zero. If there isn’t any alpha on the tile, then the tile is completely solid – it’s value is set to one. If there is partial alpha data, then the pixel data for the alpha channel is stored so we can use it to resolve collisions at a per-pixel level later on.

The array containing the alpha channel for each tile can become pretty large. For these particular tiles, there are 1024 bits of alpha channel data (32 * 32). If you were to JSON.stringify() this, just one tile would take up a huge chunk of space in your map file. Thankfully, we can reduce this information down into a much more manageable state.

sortPartial : function(arr) {
    var len = arr.length,
        temp = [],
        i, j;

    for (i = 0; i < tileSize; i++) {
        temp[i] = [];
        for (j = 0; j < len; j++) {
            if (j % tileSize === j) {
                temp[i][j] = arr[j * tileSize + i];
            }
        }
        temp[i] = temp[i].indexOf(255);
    }
    return temp;
}

This function will filter the array of part-alpha tiles, and return an array containing the position of each first solid pixel on the y axis. Just call this function on the part-alpha array above, like so –

... else { // partial alpha, build pixel map
    alpha[x][y] = app.sortPartial(alpha[x][y]);
}

Now for each tile we’ll get back an array 32 items, rather than 1024 items long –

[28,28,26,26,24,24,22,22,20,20,18,18,16,16,14,14,12,12,10,10,8,8,6,6,4,4,2,2,0,0,0,0]

We can use this information to resolve the x/y position of a character in a 2d platform game when it’s interacting with curved or sloped terrain. Neat eh? ūüôā

A minor problem…

Half way through writing this tutorial, I realised it would be kind of lame not to have a working demonstration of this technique in action. Unfortunately to do that, I needed a tile map editor. I couldn’t find an open source JavaScript based tile map editor, so I started writing one. So much for not reinventing the wheel! It’s definitely a minimum viable product at this early stage – it only outputs collision maps, and it has very limited functionality.

What it does have though, is a lot of potential. If there are any JavaScript game engine developers out there reading this, and you’re in need of a tile map editor (and your project is open source), I’d be happy to contribute to your project and spend a bit more time on this; otherwise it’s just going to be developed as and when I get the time.

For a demonstration of per-pixel collision detection on sloped and curved tiles, check out the demo. Create and build a map, and hit the test button to see it in action. Works in Chrome, Safari and Firefox.

Check out the demo here: JavaScript tile map editor
Get the source on Github

breakout

HTML5 Canvas Breakout Game

Edit: 23/08/12
Check out the newest version: Brickout!

Some time last year, I started learning the HTML5 Canvas API. One of the most popular tutorials for this was (and still is) Bill Mill’s Canvas Tutorial, where you build up a basic version of Breakout step by step. After completing the tutorial, I carried on cobbling features onto it whenever I learned something new (the starfield was inspired by Mr Speaker for example).

On Friday I released my HTML5 Breakout game as an easter egg on the homepage of my freelance HTML5 game development site. The feedback I got though, was that it was such a well hidden easter egg that few people were ever going to see it! That would be a real shame, so I’ve given it it’s own demo page.

It uses the Canvas and Audio APIs, a bit of CSS3, and PHP/MySQL (with JSON output) for the back end. I’m still tacking on new features – I’m working on powerups and laser cannons at the moment, but I thought I’d put it up for people to have a play with.

Lessons Learned

The Audio API is still in a bad state. Remy Sharp’s Audio Sprites tutorial was a big help (only after I spent a couple of hours banging my head against the wall, mind…), but my implementation is far from perfect and still needs work. Which is ironic really, because I could say exactly the same thing about the entire Audio API. The implementation is currently quite fragmented, and some browsers support features that others don’t. For example, Firefox 4 doesn’t currently support the ‘loop’ property – you have to listen for the ‘ended’ event, set the currentTime to zero, and then set the audio to play.

Matters aren’t helped by the fact that browser vendors can’t agree on a universal audio format. Google, Mozilla and Opera have implemented open source codecs (OGG Vorbis/WebM), and Safari and Microsoft will be sticking with proprietary formats (MPEG, H.264), which means that we currently need at least two source files for each piece of audio we want to put on a web page. Better add Audacity to that ever-growing list of software to learn.

Premature optimisation is the root of all evil. I spent a lot of time just undoing changes that, at the time, made sense; but later on, became performance issues or bugs. For example, doing a left bitwise shift on x/y coordinates of the ball (for pixel snapping purposes – read Seb Lee-Delisle’s Sprite Optimisation post for more details) caused a few problems with collision detection later on. I’ve removed most of these optimisations for the time being, as the game is still in development. The best thing you can do during development is to just follow good coding practices – store references in a variable if you’re using them more than once, don’t set things like fillStyles in a loop if you can do it outside of a loop, etc. A really good book to read on this is High Performance JavaScript by Nicholas Zakas.

Game development is time consuming. This game was cobbled together over the course of a few months, and it’s not even finished! I think the reason that we haven’t seen anything particularly mind-blowing in the HTML5/WebGL game department yet is that it takes a long time to get a project into a playable state if you build it from the ground up – especially if you’re the only person working on it. (Did you see the production crew on ro.me? The list was a mile long! Wonder what the budget was…) There isn’t a great deal of choice in middleware/game engines yet, and projects that did look promising were snapped up by development studios (Aves by Zynga, Rocketpack by Disney…)

I’m really interested in the future of browser games, and of JavaScript games in particular. I plan to make a whole bunch more of them, but writing them from scratch would prove time consuming. It’s really tempting to fall victim to NIH (Not Invented Here) Syndrome when you’re a developer, but there’s enough work to do in building a game without having to write your own engine first. After all, if you needed a blogging engine, would you write your own, or use WordPress? Look forward to my next project, built with ImpactJS ūüôā

Update 31/05/11: HTML5 Breakout¬†now has lasers and marginally less crappy sfx. ¬†I also forgot to mention, the graphics and sfx were created by me (can’t you tell? :p) and the music is from AudioJungle. ¬†If there are any artists out there who would like to make a game and wouldn’t mind dealing with the artistically challenged, please tweet me!