Freigeben über


HTML5 Platformer: the complete port of the XNA game to

with EaselJS

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

Comments

  • Anonymous
    September 09, 2011
    The comment has been removed

  • Anonymous
    September 11, 2011
    Works splendidly for me in Chrome! Ace!

  • Anonymous
    September 12, 2011
    No problems in IE9 here..

  • Anonymous
    September 12, 2011
    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

  • Anonymous
    September 13, 2011
    I´ve got it running at 18fps at Chrome.

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

  • Anonymous
    September 14, 2011
    Only 7fps on ipad2 ios 4.3

  • Anonymous
    September 14, 2011
    The comment has been removed

  • Anonymous
    September 15, 2011
    Solid 60fps in IE9. Chrome gave 7fps :(

  • Anonymous
    September 19, 2011
    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.

  • Anonymous
    October 03, 2011
    Love the artwork :)

  • Anonymous
    October 03, 2011
    Bit slow on i8700

  • Anonymous
    February 14, 2012
    Works great on Safari on a Mac too.

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

  • Anonymous
    April 08, 2012
    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.

  • Anonymous
    April 12, 2012
    truly outstanding work.  nice job.  

  • Anonymous
    May 21, 2012
    Nice port on browser, love the desing too.. speed is fine on Chrome & no bugs ;)

  • Anonymous
    October 04, 2012
    Thank you for the great tutorial. I've learned a lot. For fun, I have rewritten the ContentManager to use PreloadJS. You can find it here: gist.github.com/3836891

  • Anonymous
    October 07, 2012
    Hi Thomas, This is awesome. I'm adding your reference to the article as an update. Thanks for sharing! Bye, David

  • Anonymous
    October 08, 2012
    Awesome tutorial. One thing I've noticed is that the entire screen moves within the browser container when you hold it with your finger and move up and down. Is there a way to get rid of this? (I'm using Phonegap + your code on WP7)

  • Anonymous
    November 14, 2012
    Great Work

  • Anonymous
    January 09, 2013
    Excellent !!

  • Anonymous
    February 03, 2013
    cannot play on chrome v 24.x on mac osx. :( only blank screen...

  • Anonymous
    April 10, 2013
    This is an excellent tutorial, but in the downloadable source files, the game doesn't go to the other levels, does anyone know why?

  • Anonymous
    May 16, 2013
    The comment has been removed

  • Anonymous
    May 16, 2013
    You're probably running into a security issue by executing from file://, try to host the code on http:// to solve this problem.

  • Anonymous
    May 26, 2013
    I'm also having issues getting anything other than the hard coded level. I'm working on a Windows store app. Been at it for a few days with no solution found yet.

  • Anonymous
    July 21, 2013
    Awesome. Just gave this a 5 star rating

  • Anonymous
    December 10, 2013
    Great tutorail.But I have a small doubt how can one make a background larger than the canvas and allow the camera move based on the movement of the player.

  • Anonymous
    December 15, 2013
    Thanks for this, it's really great! I noticed the levels backgrounds and order are randomised, you set an amount of levels you want, configure the platforms in the .txt files and the layered backgrounds are randomly created. What if I want to have a finite amount of levels, say 10, with specific backgrounds for each one, level1background.png, level2background.png, level3background.png and so on up to level 10. They should always run in that sequence from level 1-10, not in random order? Is that possible?

  • Anonymous
    April 16, 2014
    is the download version only cover for one level?when I play it doesn't proceed to next level when complete the first level, it just repeat back the first level

  • Anonymous
    April 16, 2014
    You need to host it on a webserver to be able to go to the next level. Otherwise, the xhr calls will fail on file:// . That's why, you're looping on the same level sent in case of error.