现代化您的 HTML5 Canvas 游戏 第二部分:离线、拖放和文件 API





5.00/5 (5投票s)
现代化您的 HTML5 Canvas 游戏 第二部分:离线、拖放和文件 API
现代浏览器中的 HTML5 功能,例如 Internet Explorer 10,使得全新类型的 Web 应用程序和游戏场景成为可能。这篇分为两部分的系列文章展示了我如何利用这些新功能来现代化我之前的 HTML5 游戏 HTML5 Platformer。
在本系列文章的第一部分,我介绍了如何为 HTML5 Platformer 游戏使用 CSS3 3D Transform、Transitions 和 Grid Layout。在第二部分,我将向您展示如何使用离线、拖放和文件 API 来实现一些有趣的新想法。
离线模式下玩游戏
我的游戏的原始版本仅在您的设备当前连接到 Internet 时才能运行。因此,如果您想在火车上、出租车上或任何没有互联网连接的地方玩我的精彩游戏,您将无能为力——无法访问其中的精彩内容。这太可惜了,因为一旦所有资源都已下载,我的游戏中实际上没有任何内容需要“实时”连接到 Web 服务器。幸运的是,HTML5 中的离线 API 为此提供了解决方案。
步骤 1:选择您要缓存的资源
告诉浏览器您希望为离线使用缓存哪些资源实际上非常简单。但在继续之前,我建议您阅读这两篇文章:
对于我的游戏,我创建了一个名为 *platformer.cache* 的文件,内容如下:
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: *
我插入了所有包含我的精灵、背景层和叠加层的 PNG 文件;EaselJS 框架必需的 JS 文件;以及我自己的游戏逻辑和主 HTML 及 CSS 文件。然后,我只需在我的主 HTML 页面中指示我想使用此清单文件。在这种情况下,它是“index.html”。
<!DOCTYPE html> <html manifest="platformer.cache"> <head> <title>HTML5 Platformer Game</title> // ... </head> </html>
请注意,您的清单文件应以“text/cache-manifest”的形式提供。对于我的游戏,我添加了一个新的“ .cache”内容类型,它映射到“text/cache-manifest”,因为它存储在 Windows Azure 的 blob 存储中。
请注意,此规范不允许增量更改。即使只有一个文件已更改,您也需要强制完全重新下载,浏览器才会更新到新版本。但是,对您的清单文件的任何更改都会被浏览器检测到,然后浏览器将重新下载其中指定的所有资源。更改可以是版本号、日期、GUID——任何适合您的方法——在文件开头通过注释(如上例中的“Version 1.5”)进行。
步骤 2:修改加载关卡的逻辑
我的代码的原始版本通过XHR 调用 Web 服务器来下载每个关卡。我需要对其进行更改才能使我的游戏在离线模式下运行。此外,我想通过在游戏画布中添加“官方”的 HTML5 相关徽标来向用户指示他们当前正在离线模式下玩游戏。
让我们逐步分析实现这一目标所做的更改。当用户第一次启动我的游戏时,所有关卡都将(在 {x}.txt 文件中描述)下载到本地存储。这得到了广泛支持(自 IE8 起),并且非常易于使用。最重要的是,它在离线模式下可用。
这是我添加到“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();
}
}
}
PlatformerGame
构造函数中的所有关卡都将异步下载。一旦所有关卡都下载完毕(numberOfLevelDownloaded === numberOfLevels
),第一个关卡就会加载。这是新函数的代码:
// 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);
};
代码的开头处理 CSS3 过渡,如上一篇文章中所述。游戏将简单地通过相应的键访问本地存储以检索先前下载的内容。
步骤 3:检查在线/离线状态并在离线模式下启动时显示徽标
要确认游戏是否在离线模式下运行,需要进行两次检查。第一次是浏览器是否实现了 offline/online 事件,大多数现代浏览器都支持。如果浏览器显示用户离线,则确认完毕,游戏应立即切换到离线逻辑。但是,这通常不足以进行简单的检查。浏览器可能显示它在线,但它不知道 Web 服务器是否仍然在线。因此,您需要通过简单的 XHR ping 服务器来执行第二次检查。
这是我为这两项检查编写的代码:
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;
};
我的测试是尝试下载第一个关卡。如果失败,它将切换到我的代码的离线部分。现在,这是在 *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();
}
这是在 Level.js 的 CreateAndAddRandomBackground
中显示离线徽标的代码:
if (!PlatformerGame.IsOnline) {
offlineLogo.x = 710;
offlineLogo.y = -1;
offlineLogo.scaleX = 0.5;
offlineLogo.scaleY = 0.5;
this.levelStage.addChild(offlineLogo);
}
通过实现这些更改,我的游戏在没有网络连接的情况下启动时的样子是这样的:
离线徽标显示在帧率之前,指示用户游戏目前完全离线运行。
步骤 4:有条件地下载 MP3 或 OGG 文件并将它们存储为 IndexedDB 中的 blob
这是我尚未实现但想作为奖励与您分享的概念,以便您可以自己实现!
您可能已经注意到,我在步骤 1 的清单文件中没有包含我游戏的音效和音乐。当我编写这个 HTML5 游戏时,我的第一个目标是与尽可能多的浏览器兼容。为了实现这一点,我有两个版本的声音:MP3 用于 IE 和 Safari,OGG 用于 Chrome、Firefox 和 Opera。内容下载管理器只下载启动我的游戏之当前浏览器支持的编解码器类型。这是因为如果我在 IE 中播放,就没有必要下载文件的 OGG 版本,如果在 Firefox 中播放,也没有必要下载 MP3 版本。
清单文件的问题在于,您无法根据当前浏览器支持情况有条件地指示要加载哪个资源。我想出了三种解决方案来绕过这个限制:
- 下载两个版本,将所有文件版本放在清单文件中。这非常简单易实现,并且运行良好,但用户将下载一些文件,这些文件将永远不会被某些浏览器使用。
- 构建一个服务器端动态清单文件,通过嗅探浏览器代理来猜测支持的编解码器。这绝对是一个非常糟糕的做法!
- 使用客户端功能在内容管理器对象中检测编解码器支持,然后在 IndexedDB 或本地存储中下载适当的文件格式以供离线使用。
我认为第三种解决方案是最好的,但您需要注意几点才能使其正常工作:
- 如果您使用本地存储,您需要将文件编码为 base64,如果您有太大/太多的文件,您可能会遇到配额限制。
- 如果您使用 IndexedDB,您可以存储文件的 base64 编码版本,也可以将它们存储为 blob。
blob 方法绝对是更智能、更高效的解决方案,但它需要非常新的浏览器,例如最新版本的IE10 (PP5) 或 Firefox (11)。如果您对此想法感兴趣,请在此处查看我们 IE Test Drive 网站上的 Facebook Companion 演示:
您可以在这篇文章中找到有关此演示的更多详细信息:《IE10 和 Metro 风格应用程序的 IndexedDB 更新》
在本文提供的游戏版本中,我决定缓存所有格式(方案 1)。我可能会在未来的文章中通过实现 IndexedDB 缓存来更新它。敬请期待!
拖放和文件 API
这是一个有趣的新功能,它利用了新的拖放和文件 API。用户可以使用他们喜欢的文本编辑器创建/编辑关卡,然后只需将其从文件资源管理器中直接拖放到 HTML5 游戏中即可玩!
关于拖放,我不会详细介绍,因为它在这篇文章中已经得到了很好的涵盖:《IE10 中的 HTML5 拖放》,其中解释了 Magnetic Poetry 演示是如何构建的。我建议您先阅读这篇文章,以便充分理解下面的代码。
对于我的游戏,我创建了 *dragDropLogic.js* 文件,其中包含以下代码:
(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;
})();
这段代码在 main.js 的 startGame
函数中被调用:
// 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();
}
就是这样!例如,将此文本块复制/粘贴到一个新的“ .txt”文件中:
.................... .................... .................... .1.................. ######.........##### .................... .........###........ .................... .G.G.GGG.G.G.G...... .GGG..G..GGG.G...... .G.G..G..G.G.GGG.... .................... .................... .X................C. ####################
然后将其拖放到我的游戏中。新关卡将神奇般地加载!
演示和源代码
如果您想在 IE10 中观看本文所有功能的演示,请观看此短视频:
下载视频: MP4, WebM, VideoJS 的 HTML5 视频播放器
您也可以在 IE10 或您喜欢的浏览器中在此处玩此演示:Modern HTML5 Platformer
由于您如此慷慨地阅读了整篇文章,请在此处欣赏完整的源代码:HTML5 现代平台游戏源代码