引言
HTML5 提供許多實用的 API,可讓您在瀏覽器中建立先進、回應且功能強大的網頁應用程式。這麼做固然很好,但你真的很想建構並玩遊戲!幸好,HTML5 也已進入新遊戲開發時代,透過 Canvas 和強大的 JavaScript 引擎等 API,直接在瀏覽器中提供遊戲,不需另外安裝外掛程式。
本文將逐步說明如何為 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 個連線。如要加快資產下載速度,其中一種方法是使用一系列的網域名稱代管資產。例如,請嘗試使用 asset1.example.com、asset2.example.com、asset3.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()
只會疊代下載佇列,並建立新的 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 與下載佇列的大小,藉此知道每項資產是否順利完成或發生錯誤。
當然,想要知道是否確實完成工作只是其中一半,資產管理員也必須檢查這種方法。我們會在事件處理常式中同時新增這項檢查,如下列程式碼所示:
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 帳戶中找到。Bad Aliens 遊戲可以透過與 HTML5 相容的瀏覽器播放。這個遊戲是我名為「Super Browser 2 Turbo HD Remix: Introduction to HTML5 Game Development」(HTML5 遊戲開發簡介) 的 Google IO 講座的主題 (投影片、影片)。
摘要
大多數遊戲都有一種資產管理工具,但 HTML5 遊戲需要使用資產管理工具,才能透過網路載入素材資源並處理錯誤。本文將概述簡單的素材資源管理工具,可供您輕鬆使用,並配合您接下來的 HTML5 遊戲進行調整。希望您可以玩,歡迎在下方留言分享心得。Thanks!