Curved and sloped tiles in JavaScript platform games

Demo: https://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

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!