简介
HTML5 提供了许多实用的 API,用于在浏览器中构建现代、响应迅速且功能强大的 Web 应用。太棒了,但您确实想构建和玩游戏!幸运的是,HTML5 也开创了游戏开发的新时代,它使用画布等 API 和强大的 JavaScript 引擎,无需插件即可将游戏直接传送到浏览器中。
本文介绍了如何为 HTML5 游戏构建简单的资源管理组件。如果没有素材资源管理器,您的游戏将很难针对未知的下载时间和异步图片加载进行补偿。下面通过一个示例来了解适用于 HTML5 游戏的简单素材资源管理器。
问题
HTML5 游戏不能假设图片或音频等资源位于玩家的本地计算机上,因为 HTML5 游戏暗示在网络浏览器中玩游戏,且资源通过 HTTP 下载。由于涉及到网络,因此浏览器不确定何时可以下载和提供游戏资源。
在网络浏览器中以编程方式加载图片的基本方法是以下代码:
var image = new Image();
image.addEventListener("success", function(e) {
// do stuff with the image
});
image.src = "/some/image.png";
现在,假设有一百张图片需要在游戏启动时加载并显示。如何知道 100 张图片全部准备好了?它们是否全部成功加载?比赛应该何时真正开始?
解决方法
让资产管理器处理资产队列,并在一切就绪后向游戏报告。素材资源管理器概括了通过网络加载素材资源的逻辑,并提供了一种查看状态的简单方法。
我们的简易资产管理器具有以下要求:
- 将下载内容加入队列
- 开始下载
- 跟踪成功和失败
- 在一切完成后发出信号
- 资源轻松检索
正在排入队列
第一项要求是将下载内容加入队列。此设计可让您声明所需资源,而无需实际下载。例如,如果您想在配置文件中声明某个游戏关卡的所有资源,此方法会很有用。
构造函数和队列的代码如下所示:
function AssetManager() {
this.downloadQueue = [];
}
AssetManager.prototype.queueDownload = function(path) {
this.downloadQueue.push(path);
}
开始下载
将所有要下载的资源排入队列后,您可以要求资源管理器开始下载所有内容。
幸运的是,网络浏览器可以并行进行下载,每个主机通常最多可以有 4 个连接。加快资源下载速度的一种方法是在托管资源时使用一系列域名。例如,您可以尝试使用 assets1.example.com、 assets2.example.com 和 assets3.example.com 等文件,而不要投放 assets.example.com 中的所有文件。即使其中每一个域名只是同一网络服务器的 CNAME,网络浏览器也会将其视为单独的服务器,并会增加用于下载资源的连接数。如需详细了解此技巧,请参阅跨网域拆分组件中的“加快网站速度的最佳做法”。
我们的下载初始化方法称为 downloadAll()
。我们会逐渐积累更多数据。目前,这是第一个直接开始下载的逻辑。
AssetManager.prototype.downloadAll = function() {
for (var i = 0; i < this.downloadQueue.length; i++) {
var path = this.downloadQueue[i];
var img = new Image();
var that = this;
img.addEventListener("load", function() {
// coming soon
}, false);
img.src = path;
}
}
如以上代码所示,downloadAll()
只是遍历 downloadQueue 并创建新的 Image 对象。系统将为加载事件添加事件监听器,并设置图片的 src,以触发实际下载。
使用此方法,您就可以开始下载了。
跟踪成功和失败
另一个要求是同时跟踪成功和失败,因为遗憾的是,有些事情并非总是完美无缺。到目前为止,该代码仅跟踪已成功下载的素材资源。通过为错误事件添加事件监听器,您可以捕获成功和失败场景。
AssetManager.prototype.downloadAll = function(downloadCallback) {
for (var i = 0; i < this.downloadQueue.length; i++) {
var path = this.downloadQueue[i];
var img = new Image();
var that = this;
img.addEventListener("load", function() {
// coming soon
}, false);
img.addEventListener("error", function() {
// coming soon
}, false);
img.src = path;
}
}
我们的资产管理器需要了解我们经历了多少成功和失败,否则就永远不知道游戏何时可以开始。
首先,我们将计数器添加到构造函数中的对象中,如下所示:
function AssetManager() {
<span class="highlight"> this.successCount = 0;
this.errorCount = 0;</span>
this.downloadQueue = [];
}
接下来,在事件监听器中递增计数器,代码现在如下所示:
img.addEventListener("load", function() {
<span class="highlight">that.successCount += 1;</span>
}, false);
img.addEventListener("error", function() {
<span class="highlight">that.errorCount += 1;</span>
}, false);
资产管理器现在可同时跟踪成功加载和失败的资产。
完成时发出信号
在游戏将资源排入下载队列并要求资源管理器下载所有资源后,需要通知游戏何时下载所有资源。资源管理器可以向游戏发回信号,而无需游戏反复询问资源是否已下载。
素材资源管理器首先需要知道每项素材资源的完成时间。我们现在添加一个 isDone 方法:
AssetManager.prototype.isDone = function() {
return (this.downloadQueue.length == this.successCount + this.errorCount);
}
通过将 successCount + errorCount 与 downloadQueue 的大小进行比较,素材资源管理器可以了解每项资源是成功完成,还是出现了某种错误。
当然,知道是否已完成这一过程只是成功的一半;资产管理者也需要对此方法进行检查。我们将在两个事件处理脚本中添加此检查,如以下代码所示:
img.addEventListener("load", function() {
console.log(this.src + ' is loaded');
that.successCount += 1;
if (that.isDone()) {
// ???
}
}, false);
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
// ???
}
}, false);
在计数器递增后,我们将查看这是否是队列中的最后一个资产。如果素材资源管理器确实已经下载完毕,我们该怎么做?
如果资源管理器下载完了所有资源,当然,我们会调用回调方法!让我们更改 downloadAll()
并为回调添加一个形参:
AssetManager.prototype.downloadAll = function(downloadCallback) {
...
我们将在事件监听器内调用 downloadCallback 方法:
img.addEventListener("load", function() {
that.successCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
素材资源管理器终于可以满足最后一个要求了。
轻松检索资产
一旦收到游戏可以开始的信号,游戏便会开始呈现图片。资产管理器不仅负责下载和跟踪资产,还负责将其提供给游戏。
我们的最终要求暗示某种 getAsset 方法,因此我们现在添加它:
AssetManager.prototype.getAsset = function(path) {
return this.cache[path];
}
此缓存对象在构造函数中进行初始化,如下所示:
function AssetManager() {
this.successCount = 0;
this.errorCount = 0;
this.cache = {};
this.downloadQueue = [];
}
缓存在 downloadAll()
末尾填充,如下所示:
AssetManager.prototype.downloadAll = function(downloadCallback) {
...
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
img.src = path;
<span class="highlight">this.cache[path] = img;</span>
}
}
额外知识点:问题修复
您发现错误了吗?如上文所述,只有在触发加载或错误事件时,系统才会调用 isDone 方法。但是,如果资产管理器没有任何排队等待下载的资产,该怎么办?isDone 方法永远不会被触发,游戏也永远不会开始。
您可以通过向 downloadAll()
添加以下代码来适应此情况:
AssetManager.prototype.downloadAll = function(downloadCallback) {
if (this.downloadQueue.length === 0) {
downloadCallback();
}
...
如果没有资源加入队列,则立即调用回调函数。错误已修复!
用法示例
在 HTML5 游戏中使用此素材资源管理器非常简单。以下是使用该库的最基本方法:
var ASSET_MANAGER = new AssetManager();
ASSET_MANAGER.queueDownload('img/earth.png');
ASSET_MANAGER.downloadAll(function() {
var sprite = ASSET_MANAGER.getAsset('img/earth.png');
ctx.drawImage(sprite, x - sprite.width/2, y - sprite.height/2);
});
上面的代码说明了:
- 创建新的素材资源管理器
- 将要下载的资产加入队列
- 使用
downloadAll()
开始下载 - 通过调用回调函数在资源准备就绪时发出信号
- 使用
getAsset()
检索资产
有待改进的方面
毫无疑问,随着您开发游戏的这一简单资产管理器,您一定会发现,它能帮助您打下坚实的基础。未来的功能可能包括:
- 指示哪个素材资源出错了
- 用于指示进度的回调
- 从 File System API 中检索资源
请在下面的评论中发表改进内容、分支和代码链接。
完整源代码
此资产管理器的源代码以及从其衍生的游戏作品,根据 Apache 许可作为开源内容,可以在 Bad Aliens GitHub 帐号中找到。您可在兼容 HTML5 的浏览器中玩 Bad Aliens 游戏。该游戏是我的 Google IO 大会演讲的主题,主题为“Super Browser 2 Turbo HD Remix: Introduction to HTML5 Game Development”(幻灯片、视频)。
摘要
大多数游戏都有某种资产管理器,但 HTML5 游戏则需要使用资产管理器来通过网络加载资产并处理故障。本文概要介绍了一个简单的素材资源管理器,它应该易于使用,以便您在开发下一款 HTML5 游戏中进行相应调整。祝你玩得开心,并在下面的评论区告诉我们你的想法。谢谢!