บทนำ
HTML5 มี API ที่มีประโยชน์มากมายสําหรับการสร้างเว็บแอปพลิเคชันที่ทันสมัย ตอบสนองได้รวดเร็ว และมีประสิทธิภาพในเบราว์เซอร์ นี่เป็นวิธีที่ดี แต่คุณอยากสร้างและเล่นเกมจริงๆ แต่โชคดีที่ HTML5 ได้เปิดศักราชใหม่ในการพัฒนาเกมที่ใช้ API เช่น Canvas และเครื่องมือ 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 รายการต่อโฮสต์ วิธีหนึ่งในการเร่งความเร็วการดาวน์โหลดชิ้นงานคือการใช้ชื่อโดเมนที่หลากหลายสำหรับการโฮสต์ชิ้นงาน เช่น แทนที่จะแสดงทุกอย่างจาก assets.example.com ให้ลองใช้ assets1.example.com, assets2.example.com, assets3.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 ใหม่ ระบบจะเพิ่ม Listener เหตุการณ์สําหรับเหตุการณ์การโหลดและตั้งค่า src ของรูปภาพ ซึ่งจะทริกเกอร์การดาวน์โหลดจริง
วิธีนี้จะช่วยให้คุณเริ่มการดาวน์โหลดได้
การติดตามความสําเร็จและการไม่สําเร็จ
ข้อกําหนดอีกข้อคือการติดตามทั้งความสําเร็จและความล้มเหลว เนื่องจากทุกอย่างอาจไม่ได้เป็นไปตามที่คาดหวังเสมอไป รหัสดังกล่าวจะติดตามเฉพาะชิ้นงานที่ดาวน์โหลดสําเร็จเท่านั้น การเพิ่ม Listener เหตุการณ์สําหรับเหตุการณ์ข้อผิดพลาดจะช่วยให้คุณบันทึกทั้งสถานการณ์ที่ประสบความสําเร็จและไม่สําเร็จได้
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 = [];
}
ถัดไป ให้เพิ่มตัวนับใน Listener เหตุการณ์ ซึ่งตอนนี้มีลักษณะดังนี้
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
แน่นอนว่าการทราบว่าดำเนินการเสร็จแล้วเป็นเพียงครึ่งหนึ่งของปัญหา ผู้จัดการเนื้อหายังต้องตรวจสอบวิธีการนี้ด้วย เราจะเพิ่มการตรวจสอบนี้ไว้ในตัวแฮนเดิลเหตุการณ์ทั้ง 2 รายการ ดังที่แสดงในโค้ดด้านล่าง
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 ภายใน Listener เหตุการณ์ ดังนี้
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 และดูได้ในบัญชี GitHub ของ Bad Aliens เกม Bad Aliens เล่นได้ในเบราว์เซอร์ที่รองรับ HTML5 เกมนี้เป็นหัวข้อในการบรรยายของฉันใน Google IO ชื่อ Super Browser 2 Turbo HD Remix: ข้อมูลเบื้องต้นเกี่ยวกับการพัฒนาเกม HTML5 (สไลด์, วิดีโอ)
สรุป
เกมส่วนใหญ่มีเครื่องมือจัดการชิ้นงาน แต่เกม HTML5 ต้องใช้เครื่องมือจัดการชิ้นงานที่โหลดชิ้นงานผ่านเครือข่ายและจัดการเมื่อเกิดข้อผิดพลาด บทความนี้กล่าวถึงเครื่องมือจัดการชิ้นงานที่ใช้งานง่ายและปรับให้เข้ากับเกม HTML5 เกมถัดไปได้ ขอให้สนุกและโปรดบอกให้เราทราบความคิดเห็นของคุณในช่องแสดงความคิดเห็นด้านล่าง ขอขอบคุณ