David Rousset - HTML5 & Gaming Technical Evangelist

HTML5 & Gaming Technical Evangelist

HTML5 Platformer: the complete port of the XNA game to <canvas> with EaselJS

HTML5 Platformer: the complete port of the XNA game to <canvas> with EaselJS

Rate This
  • Comments 32

After a couple of hours coding with JavaScript, I’ve finally finished porting the XNA 4.0 Platformer game sample to HTML5/Canvas with the help of the EaselJS gaming framework. This article will provide you the game and the story of some of the questions I’ve asked myself while coding it. If you’d like to know how the game works, simply read the JavaScript commented code available at the end of this article. Please note that my main goal was to better learn JavaScript by writing pure JS code (with no form of dependency to the DOM) and to write a cross-browsers working game and cross HTML5 compatible devices when possible also.   

You can play the game directly inside this iframe (left & right arrows keys to move & W to jump). Just press the “Start” button to launch the download sequence and the game itself.

Or you can play with it in a separate window through this link: HTML5 Platformer

Note 1: the game has been first released in September 2011 with EaselJS 0.3.2 and has been updated for EaselJS 0.5 on 8/31/2012. You can now use requestAnimationFrame on the supported browsers to check if it’s boosting your performance or not. You just have to check/unckeck the above case to see in real time the results.

Note 2: this game has been tested with success under IE9/10, Chrome 21, Firefox 15, Opera 12, IE9 on WP7 Mango and iPad 2. At last, if you really want to play to the game, an hardware accelerated browser is highly recommended. I’ve got 60 FPS in IE10 in these conditions with an nVidia GT330m (Vaio Z) or with an integrated Intel HD4000. 

At the end of this article, you’ll find the complete non-minified source code of this game inside a ZIP archive to download. I’ve tried to comment as much as possible the code to make it self-explicit. I hope you’ll learn some interesting things while reading it. If you have any feedbacks and/or suggestions on it, feel free to add it as a comment to this blog’s post.

I’ve also already written 2 articles covering the basics of the logic behind this game here:

- HTML5 Gaming: animating sprites in Canvas with EaselJS
- HTML5 Gaming: building the core objects & handling collisions with EaselJS

I’ve also written 3 others articles going further with this game:

- Tutorial: how to create HTML5 applications on Windows Phone thanks to PhoneGap where I’ll show you how to migrate this code to PhoneGap
- Modernizing your HTML5 Canvas games Part 1: hardware scaling & CSS3 where we’ll use CSS3 3D Transform, Transitions & Grid Layout
- Modernizing your HTML5 Canvas games Part 2: Offline API, Drag’n’drop & File API where we will enable playing to the game in offline mode

Building the content manager

In the previous article, the download content manager wasn’t notifying the user of the progression of the download process which was very bad. Hopefully, adding this feature was pretty straight forward as you just have to have a tick() method to your object and update a text element on the stage to show the progression. You can find that by reading the ContentManager.js file.

Moreover, as my objective was to be compatible with all browsers, I’ve then encoded the music & audio elements both in MP3 and OGG. If you’d like to know more about this HTML5 Audio tag codecs story, I recommend you having a look to my colleague’s article: 5 Things You Need to Know to Start Using Video and Audio Today

Then, I’m downloading either the MP3 or OGG version using the following code:

var canPlayMp3, canPlayOgg;
var audioExtension = ".none";

// Need to check the canPlayType first or an exception
// will be thrown for those browsers that don't support it      
var myAudio = document.createElement('audio');

if (myAudio.canPlayType) {
    // Currently canPlayType(type) returns: "", "maybe" or "probably" 
    canPlayMp3 = !!myAudio.canPlayType && "" != myAudio.canPlayType('audio/mpeg');
    canPlayOgg = !!myAudio.canPlayType && "" != myAudio.canPlayType('audio/ogg; codecs="vorbis"');
}

if (canPlayMp3)
    audioExtension = ".mp3";
else if (canPlayOgg) {
    audioExtension = ".ogg";
}

// If the browser supports either MP3 or OGG
if (audioExtension !== ".none") {
    SetAudioDownloadParameters(this.globalMusic, "sounds/Music" + audioExtension);
    SetAudioDownloadParameters(this.playerKilled, "sounds/PlayerKilled" + audioExtension);
    SetAudioDownloadParameters(this.playerJump, "sounds/PlayerJump" + audioExtension);
    SetAudioDownloadParameters(this.playerFall, "sounds/PlayerFall" + audioExtension);
    SetAudioDownloadParameters(this.exitReached, "sounds/ExitReached" + audioExtension);
}

I’ve spent more time trying to figure out what was the best way to preload or download the audio files before starting the game. In the case of the image elements, we can’t start the game at all if we don’t have downloaded all the images locally. Otherwise, trying to draw on the canvas with non-yet downloaded images will fail.

For the audio elements, my wish was to be sure to play it exactly when I will need it. If the player dies, I don’t want the sound to be played 2 sec after the fatal event. Unfortunately, there is no real way to do that properly in all browsers. The HTML5 Audio element has been made to stream the audio file and plays it as soon as enough data is available. We don’t have an event saying: “the audio file has been completely downloaded”. My idea was then to download a base64 version of the file using an XMLHTTPRequest and to pass it to the audio tag using <source src="data:audio/mp3;base64,{base64_string}" /> for instance. But I didn’t have the time to test if this is working or not. If you have done some similar tests, I’m interested in the results.

So finally, the only thing I’m doing is calling the load() method to start loading as much data as possible before using it. But on slow network, this won’t prevent the case where the audio won’t be ready while the game asks it. This won’t generate an error, but the sound won’t be synchronized with the action.

Note: a better version has been written by Thomas Wieczorek on GitHub using PreloadJS from the CreateJS suite here: https://gist.github.com/3836891

Working around HTML5 Audio tag limitations

While coding and testing the game, I’ve found another issue using the HTML5 <audio> tag (I didn’t expect spending so much time on the audio part!). If the player takes several gems too quickly, the audio element associated to this event can’t handle it. For instance, at the beginning, when the player was taking 8 gems on the same line, I was able to hear only 1 to 3 times the sound “GemCollected.mp3/ogg”. This means that no sound was emitted for the 5 remaining gems.

I’ve then started several experiments and I’ve finally discover that I’ve done everything already related in this article: Multiple Channels for HTML5 Audio.

On my side, I’ve finished by using another workaround. In the content manager object, I’m downloading 8 times the same GemCollected sound into an array of Audio() objects:

// Used to simulate multi-channels audio 
// As HTML5 Audio in browsers is today too limited
// Yes, I know, we're forced to download N times to same file...
for (var a = 0; a < 8; a++) {
    this.gemCollected[a] = new Audio();
    SetAudioDownloadParameters(this.gemCollected[a], "sounds/GemCollected" + audioExtension);
}

And during the game, I’m cycling inside this array to be able to “simulate” multichannels sound. You can find this trick in the UpdateGems() method of Level.js

/// <summary>
/// Animates each gem and checks to allows the player to collect them.
/// </summary>
Level.prototype.UpdateGems = function () {
    for (var i = 0; i < this.Gems.length; i++) {
        if (this.Gems[i].BoundingRectangle().Intersects(this.Hero.BoundingRectangle())) {
            // We remove it from the drawing surface
            this.levelStage.removeChild(this.Gems[i]);
            this.Score += this.Gems[i].PointValue;
            // We then remove it from the in memory array
            this.Gems.splice(i, 1);
            // And we finally play the gem collected sound using a multichannels trick
            this.levelContentManager.gemCollected[audioGemIndex % 8].play();
            audioGemIndex++;
        }
    }
};

I know this is not efficient at all. Maybe some Audio APIs will help us in the future to better handle the audio in our games. I know Mozilla and Google are working on some good suggestions but this is far from being mainstream for the moment.

The mono-threaded JavaScript nature

I’ve already covered this topic inside one of my previous articles: Introduction to the HTML5 Web Workers: the JavaScript multithreading approach . Without WebWorkers, JavaScript is by nature mono-threaded. Even if setInterval() and setTimetout() try to make the illusions that multiple things occur at the same time, this is not the case. Everything is serialized.

In my gaming scenario, this leads to problems while handling the animations. We don’t have the guarantee of being call-backed every xxx milliseconds in our update logic. To avoid that, XNA provides 2 different separate loops: the drawing loop and the update loop. In my case, they are more or less merged. And this is where the problem is. For instance, if the elapsed time between 2 ticks is too important (due to the single threading processing), I could miss an update call that would prevent one of my enemies to hit testing properly its environment. This then leads to very weird results. This is why in some parts of the code, I’ve hard-coded the expected elapsed time to 17 milliseconds. In the XNA version, the elapsed time is compute and often equal to approximately 16 ms (60 FPS). The solution to better handle the update loop (the tick() method in EaselJS) could be to use some WebWorkers.

On the other side, the solution for HTML5 gaming to properly handle animations could come in the future with requestAnimationFrame(). This is now supported by Internet Explorer 10 and can be used since EaselJS 0.4. But there are some interesting debates around it on the web. If you’re interested in this topic, I suggest you reading these 3 articles:

- Are We Fast Yet? by Dominic Szablewski, the author of HTML5 Benchmark (using ImpactJS) : “At the moment requestAnimationFrame() is truly worthless for games.” 
- requestAnimationFrame for smart animating from Paul Irish.
- requestAnimationFrame API from our IE Test Drive site to show our current IE10 implementation.

I let you experiment its usage on my game to check if it’s interesting or not in your case.

Special Kudos for IE9 & IE10

I’ve added 2 Kudos for IE users browsing my little game:

- pinned mode with a high resolution favicon and a jumplist to resources linked to the game
- thumbnail buttons to play the game in an original way! Clignement d'œil

You can then drag’n’drop the tab into your Windows 7 taskbar. IE9 will then reflect the theme of the game:

image

You will also have a jumplist available:

image

The game will then be available on the user’s taskbar as if it was a native application.

At last, you can discretely continue playing the game via the preview mode of Windows7!

image

There are 3 thumbnails buttons to move left/right and jump even in preview mode. Rire

Note: there is another more or less hidden kudos to IE in one of the levels. Will you find it?

Adding new levels to the game

The levels are stored inside the “/levels” directory inside .txt files. You’ll find 4 levels by default: 0.txt, 1.txt, 2.txt & 3.txt. They are simply downloaded via an asynchronous XMLHTTPRequest call and they parsed to build the appropriate rendering & collisions system.

For instance, here is the 2nd level:

....................
....................
..........X.........
.......######.......
..G..............G..
####..G.G.G.G....###
.......G.G.GCG......
......--------......
...--...........--..
......GGGG.GGGG.....
.G.G...GG..G....G.G.
####...GG..GGGG.####
.......GG..G........
.1....GGGG.GGGG.....
####################

And here is the output in the game:

image

1 is where the player will start the game, X is the exit, G is a Gem, # is a platform block, - is a passable block, C one of the monsters/enemies, etc.

So, if you’d like to add levels to the game, simply add a new text file and edit the level yourself with… Notepad! You’ll need also to modify the value of the numberOfLevel variable inside PlatformerGame.js.

Non-minified source code

As promised, you can download the source code and all the assets of the game here: HTML5 Platformer Non-minified.

I hope you’ve enjoyed this series of 3 articles around HTML5 Gaming. Maybe this will help you transforming your gaming ideas into an HTML5 reality!

If you’re ready to go even further, I’ve made a 4th tutorial on how to port the very same HTML5 game on Windows Phone using PhoneGap or check these 2 advanced articles:

- Modernizing your HTML5 Canvas games Part 1: hardware scaling & CSS3 where we’ll use CSS3 3D Transform, Transitions & Grid Layout
- Modernizing your HTML5 Canvas games Part 2: Offline API, Drag’n’drop & File API where we will enable playing to the game in offline mode

David

  • Both Chrome and IE9 fail to load this game up. IE9 says:

    Line: 660

    Error: Object doesn't support property or method 'autoResize'

  • Works splendidly for me in Chrome! Ace!

  • No problems in IE9 here..

  • Hi Jon,

    Have you tried running the game directly from this URL: david.blob.core.windows.net/.../index.html ? This shoudn't generate any errors neither in IE9 & Chrome.

    But I know that our blog platform generates this error sometimes.

    Bye,

    David

  • I´ve got it running at 18fps at Chrome.

  • @marcelozepgames: if you've got a PC, you should try IE9 to check if the hardware acceleration part help. What's you CPU/GPU?

  • Only 7fps on ipad2 ios 4.3

  • Nice experiment. Really inconsistent performance for me. Solid 60fps in IE10, but 40% CPU. 5% CPU in Chrome, but it feels very stuttery, despite reporting 50+fps. 2GHZ Core 2 Duo + Nvidia Quadro 1600m. Also the sound craps out in Chrome after a moment.

  • Solid 60fps in IE9.

    Chrome gave 7fps :(

  • To speed up things on IE9 Mango and other mobile devices (iPads), disable the shadows using the checkbox. I'm running between 15 & 20 fps on mon HD7 Mango device this way.

  • Love the artwork :)

  • Bit slow on i8700

  • Works great on Safari on a Mac too.

  • The player should turn around when it changes direction in mid air. Just sayin', a very nice demo by the way.

  • Thank you for an excellent game and explanations of HTML5.

    It runs great on my Linux hosted website and on

    my Apache web server on my PC running windows xp.

    It refuses to run on my Apache web server running Debian 6.

    Just a black screen. I don't know why.

    The online version works great everywhere.

    Even designed an extra level.

Page 1 of 3 (32 items) 123
Leave a Comment
  • Please add 8 and 4 and type the answer here:
  • Post