Modernize-your-HTML5-Canvas-game

Modernize your HTML5 Canvas game

Part 2: Offline, Drag-and-Drop, and File APIs

HTML5 features in modern browsers like Internet Explorer 10 are making 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 for an HTML5 Platformer game. In part 2, 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 only worked if your device was currently connected to the Internet. So, 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'd like to cache

It's actually pretty easy to tell the browser which resources you'd like 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 that looks like this:

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; as well as my own gaming logic and the main HTML and CSS files. Then, I simply indicate that I'd like to use this manifest file in my main HTML page. In this case, it'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 has changed, 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 will then re-download all the resources specified inside it. The change could be a version number, a date, a GUID-whatever works for you-at the beginning of the file via a comment ("Version 1.5" in the above example).

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 will 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 "officialHTML5 associated logo inside the gaming canvas.

Let's go through the changes to make it 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 is widely supported (since IE8) and very easy to use. And most important, it's available in offline mode.

Here is the code I added inside the "PlatformerGame.js":

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 as described in the previous article . 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 a game is running in offline mode. The first is whether the browser has implementedoffline/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.

Here is my code for both these 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 the 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 theCreateAndAddRandomBackground 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, here is what my game looks like when launched without 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 of 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 IE and Safari, and OGG for Chrome, Firefox, and Opera. The content download manager only downloads 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 it inside IE, 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 be never 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 thendownload the appropriate file format in IndexedDB or in the local storage for offline use.

I believe the 3rd 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'll need to encode the files in base64, and you may run into quota limits if you have too big/many files.
  • If you're using IndexedDB, you can either 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 IE10 (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/edit a level using his favorite text editor, then simply drag and drop it from his file explorer directly into the HTML5 game and play it!

I won't go into too much detail about drag and drop as it has been very well covered in this article: 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 below.

For my game, I created the dragDropLogic.js file containing this code:

(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;
})();

This 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/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.
####################

And drag and drop it into my game. The new level will be loaded like magic!

Demo and source code

If you'd like to see a demonstration in IE10 of all the features implemented in this article, check out this short video:

Download Video:

MP4  ,  WebM HTML5 Video Player  by VideoJS

You can also play with this demo in IE10 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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值