WebAppers

/ best free open source web resources /

Graphic Resources

Modernize Your HTML5 Canvas Game Part II

Posted · Category: Information

HTML5 features in modern browsers like Internet Explorer 10 make possible a whole new class of Web applications and gaming scenarios. This two-part article demonstrates how I’ve used some of these new features to modernize my last HTML5 game, HTML5 Platformer. In Part 1 of this article, I covered how to use CSS3 3D Transform, Transitions, and Grid Layout. In this article, I’ll show you how to use the offline, drag-and-drop and file APIs to implement some interesting new ideas.

Playing a Game in Offline Mode

The original version of my game worked only if your device was currently connected to the Internet. If you wanted to play to my fabulous game while you were on the train, in a taxi, or somewhere else without an Internet connection, you were out of luck—stuck without access to the awesomeness. And that’s too bad, because there really isn’t anything in my game that needs a live connection to the Web server once all the resources have been downloaded. Fortunately, offline APIs provide a solution for this in HTML5.

Step 1: Choosing the Resources You Want to Cache

It’s actually pretty easy to tell the browser which resources you want to cache for offline use. But before you go any further, I recommend reading these two articles:

For my game, I built a file named platformer.cache. It’s shown in Figure 1.

CACHE MANIFEST

# Version 1.5

CACHE:

index.html

modernplatformer.css

img/MonsterA.png

.. up to ..

img/MonsterD.png

img/Player.png

img/offlinelogoblack.png

img/Backgrounds/Layer0_0.png

.. up to ..

img/Backgrounds/Layer2_2.png

img/Tiles/BlockA0.png

.. up to ..

img/Tiles/BlockA6.png

img/Tiles/BlockB0.png

img/Tiles/BlockB1.png

img/Tiles/Gem.png

img/Tiles/Exit.png

img/Tiles/Platform.png

overlays/you_died.png

overlays/you_lose.png

overlays/you_win.png

src/dragDropLogic.js

src/main.js

src/ModernizrCSS3.js

src/easeljs/utils/UID.js

src/easeljs/geom/Matrix2D.js

src/easeljs/events/MouseEvent.js

src/easeljs/utils/SpriteSheetUtils.js

src/easeljs/display/SpriteSheet.js

src/easeljs/display/Shadow.js

src/easeljs/display/DisplayObject.js

src/easeljs/display/Container.js

src/easeljs/display/Stage.js

src/easeljs/display/Bitmap.js

src/easeljs/display/BitmapAnimation.js

src/easeljs/display/Text.js

src/easeljs/utils/Ticker.js

src/easeljs/geom/Rectangle.js

src/easeljs/geom/Point.js

src/easeljs/XNARectangle.js

src/easeljs/PlatformerHelper.js

src/easeljs/ContentManager.js

src/easeljs/Tile.js

src/easeljs/Gem.js

src/easeljs/Enemy.js

src/easeljs/Player.js

src/easeljs/Level.js

src/easeljs/PlatformerGame.js

NETWORK:

*

I inserted all the PNG files containing my sprites, background layers, and overlays; the necessary JS files from the EaselJS framework; and my own gaming logic and the main HTML and CSS files. Then I simply indicate that I want to use this manifest file in my main HTML page. In this case, that’s index.html:

<!DOCTYPE html>

<html manifest="platformer.cache">

<head>

<title>HTML5 Platformer Game</title>

// ...

</head>

</html>

Please note that your manifest file should be served as “text/cache-manifest”. For my game, I added a new .cache content type mapped to text/cache-manifest because it’s stored in the blob storage of Windows Azure.

Be aware that this specification doesn’t allow delta changes. Even if only one of your files changes, you need to force a complete re-download for the browser to update to the new version. However, any change to your manifest file will be detected by the browser, which then downloads all the resources specified inside the file.

The change could be a version number, a date, a GUID—whatever works for you—at the beginning of the file via a comment.

Step 2: Modifying the Logic for Loading the Levels

The original version of my code downloaded each level via an XHR call to the Web server. I need to change that to make my game run in offline mode. Also, I’d like to indicate to the user that he’s currently playing in offline mode by adding the “official” HTML5 associated logo inside the gaming canvas.

Let’s go through the changes to make this happen. The first time a user launches my game, all the levels will be downloaded (described into {x}.txt files) into the local storage. This operation is widely supported (since Internet Explorer 8 ) and very easy to use—and most important, it’s available in offline mode.

PlatformerGame.prototype.DownloadAllLevels = function () {

// Searching where we are currently hosted

var levelsUrl = window.location.href.replace('index.html', '') + "levels/";

var that = this;

for (var i = 0; i < numberOfLevels; i++) {

try {

var request = new XMLHttpRequest();

request.open('GET', levelsUrl + i + ".txt", true);

request.onreadystatechange = makeStoreCallback(i, request, that);

request.send(null);

}

catch (e) {

// Loading the hard coded error level to have at least something // to play with console.log("Error in XHR. Are you offline?");

if (!window.localStorage["platformer_level_0"]) {

window.localStorage["platformer_level_0"] = hardcodedErrorTextLevel;

}

}

}

};

// Closure of the index

function makeStoreCallback(index, request, that) {

return function () {

storeLevel(index, request, that);

}

}

function storeLevel(index, request, that) {

if (request.readyState == 4) {

// If everything was OK

if (request.status == 200) {

// storing the level in the local storage

// with the key "platformer_level_{index}

window.localStorage["platformer_level_" + index] = request.responseText.replace(/[\n\r\t]/g, '');

numberOfLevelDownloaded++;

}

else {

// Loading a hard coded level in case of error

window.localStorage["platformer_level_" + index] = hardcodedErrorTextLevel;

}

if (numberOfLevelDownloaded === numberOfLevels) {

that.LoadNextLevel();

}

}

}

All the levels in the PlatformerGame constructor will be downloaded asynchronously. Once all the levels have been downloaded (numberOfLevelDownloaded = = = numberOfLevels), the first level loads. Here is the code for the new function:

// Loading the next level contained into the localStorage in // platformer_level_{index}

PlatformerGame.prototype.LoadNextLevel = function () {

this.loadNextLevel = false;

// Setting back the initialRotation class will trigger the transition

this.platformerGameStage.canvas.className = "initialRotation";

this.levelIndex = (this.levelIndex + 1) % numberOfLevels;

var newTextLevel = window.localStorage["platformer_level_" + this.levelIndex];

this.LoadThisTextLevel(newTextLevel);

};

The beginning of the code handles the CSS3 transitions described in Part 1. The game will simply access the local storage via the appropriate key to retrieve the previously downloaded content.

Step 3: Checking Online/Offline and Displaying a Logo when Launched in Offline Mode

Two tests are required to confirm that a game is running in offline mode. The first is whether the browser has implemented offline/online events, as most modern browsers do. If the browser says the user is offline, it’s confirmed, and the game should immediately switch to the offline logic. Often this simple check is not enough, though. The browser may say it’s online, but it doesn’t know if the Web server is still online or not. So you need to do a second check by pinging the server with a simple XHR. Figure 3 shows my code for both checks.

PlatformerGame.prototype.CheckIfOnline = function () {

if (!navigator.onLine) return false;

var levelsUrl = window.location.href.replace('index.html', '') +

"levels/";

try {

var request = new XMLHttpRequest();

request.open('GET', levelsUrl + "0.txt", false);

request.send(null);

}

catch (e) {

return false;

}

if (request.status !== 200)

return false;

else

return true;

};

My test is to try to download the first level. If that fails, it switches to the offline part of my code. Now here’s the code launched in the constructor part of PlatformerGame.js:

PlatformerGame.IsOnline = this.CheckIfOnline();

// If we're online, we're downloading/updating all the levels

// from the webserver

if (PlatformerGame.IsOnline) {

this.DownloadAllLevels();

}

// If we're offline, we're loading the first level

// from the cache handled by the local storage

else {

this.LoadNextLevel();

}

And here is the code displaying the offline logo in Level.js in the CreateAndAddRandomBackground function:

if (!PlatformerGame.IsOnline) {

offlineLogo.x = 710;

offlineLogo.y = -1;

offlineLogo.scaleX = 0.5;

offlineLogo.scaleY = 0.5;

this.levelStage.addChild(offlineLogo);

}

With these changes implemented, Figure 4 shows what my game looks like when it’s launched without a network connection.

The offline logo is displayed just before the frame rate, indicating to the user that the game is currently running purely offline.

Step 4: Conditionally Downloading the MP3 or OGG Files and Storing Them as Blob in IndexedDB

This is something I’ve not implemented, but I’d like to share the concept with you as a bonus so you can implement it yourself.

You may have noticed that I didn’t include my game’s sound effects and music in the manifest file in step 1. When I wrote this HTML5 game, my first goal was to be compatible with the largest number of browsers possible. To accomplish that, I have two versions of the sounds: MP3 for Internet Explorer and Safari, and OGG for Chrome, Firefox and Opera. The content download manager downloads only the type of codec supported by the current browser launching my game. That’s because there’s no need to download the OGG version of the files if I’m playing the game inside Internet Explorer, and no need to download the MP3 version for Firefox.

The problem with the manifest file is that you can’t conditionally indicate which resource to load based on the current browser’s support. I’ve come up with three solutions to work around this limitation:

  1. Download both versions by putting all file versions inside the manifest file. This is very simple to implement and works fine, but users will be downloading some files that will never be used by some browsers.
  2. Build a server-side dynamic manifest file by sniffing the browser agent to guess the codec supported. This is definitely a very bad practice!
  3. Use a client-side feature to detect the codec support in the content manager object and then download the appropriate file format in IndexedDB or in the local storage for offline use.

I believe the third solution is the best, but you need to be mindful of a couple things to make it work:

  • If you’re using local storage, you need to encode the files in base64, and you may run into quota limits if you have too many files or your files are too big.
  • If you’re using IndexedDB, you can store the base64 encoded version of the files or store them as a blob.

The blob approach is definitely the smarter and more efficient solution, but it requires a very up-to-date browser like the last version of Internet Explorer 10 (PP5) or Firefox (11). If you’re curious about this idea, check out our Facebook Companion demo from our IE Test Drive site here:

You’ll find more details about this demo in this article: IndexedDB Updates for IE10 and Metro style apps.

In the game version supplied with this article, I decided to cache all formats (solution 1). I may update that in a future article by implementing an IndexedDB caching. Stay tuned!

Drag-and-Drop and File APIs

Here’s a fun new feature that takes advantage of the new drag-and-drop and file APIs. The user can create or edit a level using his favorite text editor and then simply drag and drop it from his file explorer into the HTML5 game and play it!

I won’t go into too much detail about drag and drop because it’s been very well covered in HTML5 Drag and Drop in IE10, which explains how the Magnetic Poetry demo was built. I recommend reading the article first to fully understand the code that follows.

For my game, I created the dragDropLogic.js file containing the code shown in Figure 5.

(function () {

"use strict";

var DragDropLogic = DragDropLogic || {};

var _elementToMonitor;

var _platformerGameInstance;

// We need the canvas to monitor its drag&drop events

// and the platformer game instance to trigger the loadnextlevel // function

DragDropLogic.monitorElement = function (elementToMonitor, platformerGameInstance) {

_elementToMonitor = elementToMonitor;

_platformerGameInstance = platformerGameInstance;

_elementToMonitor.addEventListener("dragenter", DragDropLogic.drag,false);

_elementToMonitor.addEventListener("dragover", DragDropLogic.drag, false);

_elementToMonitor.addEventListener("drop", DragDropLogic.drop, false);

};

// We don't need to do specific actions

// enter & over, we're only interested in drop

DragDropLogic.drag = function (e) {

e.stopPropagation();

e.preventDefault();

};

DragDropLogic.drop = function (e) {

e.stopPropagation();

e.preventDefault();

var dt = e.dataTransfer;

var files = dt.files;

// Taking only the first dropped file

var firstFileDropped = files[0];

// Basic check of the type of file dropped

if (firstFileDropped.type.indexOf("text") == 0) {

var reader = new FileReader();

// Callback function

reader.onload = function (e) {

// get file content

var text = e.target.result;

var textLevel = text.replace(/[\s\n\r\t]/g, '');

// Warning, there is no real check on the consistency

// of the file.

_platformerGameInstance.LoadThisTextLevel(textLevel);

}

// Asynchronous read

reader.readAsText(firstFileDropped);

}

};

window.DragDropLogic = DragDropLogic;

})();

The drag-and-drop code is called inside main.js in the startGame function:

// Callback function once everything has been downloaded

function startGame() {

platformerGame = new PlatformerGame(stage, contentManager, 800, 480, window.innerWidth, window.innerHeight);

window.addEventListener("resize", OnResizeCalled, false);

OnResizeCalled();

DragDropLogic.monitorElement(canvas, platformerGame);

platformerGame.StartGame();

}

That’s all there is to it! For instance, copy and paste this text block into a new .txt file:

....................
....................
....................
.1..................
######.........#####
....................
.........###........
....................
.G.G.GGG.G.G.G......
.GGG..G..GGG.G......
.G.G..G..G.G.GGG....
....................
....................
.X................C.
####################

Now drag and drop it into my game. The new level will be loaded like magic, as you can see in Figure 6.

Demo and Source Code

If you’d like to see a demonstration in Internet Explorer 10 of all the features implemented in this article, check out this short video:

You can also play with this demo in Internet Explorer 10 or your favorite browser here: Modern HTML5 Platformer.

Since you’ve been kind enough to read this entire article, please enjoy the complete source code here: HTML5 Modern Platformer Source Code.

About the Author

David Rousset is a developer evangelist at Microsoft, specializing in HTML5 and Web development. Read his blog on MSDN or follow him @davrous on Twitter.

1 Comment
Supported By

Deals

Web Browsers Icon Set
Food Icon Set
Flat Icon Set

Flat Icon Set

100 icons