简介
HTML5 提供了许多实用的 API,可用于在浏览器中构建现代、响应迅速且功能强大的 Web 应用。这太棒了,但您真的想构建和玩游戏!幸运的是,HTML5 还开创了游戏开发的新纪元,它使用 Canvas 等 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 张图片。如何知道所有 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 提供所有内容。即使这些域名只是指向同一 Web 服务器的 CNAME,Web 浏览器也会将它们视为单独的服务器,并增加用于下载资源的连接数量。如需详细了解此技术,请参阅“网站加速最佳实践”中的跨网域拆分组件。
我们的下载初始化方法称为 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 对象。添加了 load 事件的事件监听器,并设置了图片的 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>
}
}
附加内容:bug 修复
您发现错误了吗?如上所述,只有在触发加载或错误事件时,系统才会调用 isDone 方法。但是,如果资产管理器没有任何待下载的资产,该怎么办?isDone 方法从未触发,游戏也从未启动。
您可以通过将以下代码添加到 downloadAll()
来适应这种情况:
AssetManager.prototype.downloadAll = function(downloadCallback) {
if (this.downloadQueue.length === 0) {
downloadCallback();
}
...
如果没有资源加入队列,系统会立即调用回调。已修复 bug!
用法示例
在 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:HTML5 游戏开发入门”(幻灯片、视频)就介绍了这款游戏。
摘要
大多数游戏都具有某种资源管理器,但 HTML5 游戏需要通过网络加载资源并处理失败情况的资源管理器。本文概述了一个简单的资源管理器,您应该能够轻松使用它并将其用于下一个 HTML5 游戏。祝您玩得开心!欢迎在下方评论区告诉我们您的想法。谢谢!