การจัดการชิ้นงานง่ายๆ สําหรับเกม HTML5

เกริ่นนำ

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 รูปพร้อมแล้ว ทุกคนโหลดสําเร็จใช่ไหม เกมนี้ควรเริ่มตอนไหน

โซลูชัน

ให้ผู้จัดการเนื้อหาจัดการคิวของเนื้อหาและรายงานกลับไปยังเกมเมื่อทุกอย่างพร้อมแล้ว ผู้จัดการเนื้อหาจะสรุปตรรกะสำหรับการโหลดเนื้อหาผ่านเครือข่าย และให้วิธีการง่ายในการตรวจสอบสถานะ

ผู้จัดการเนื้อหาแบบง่ายของเรามีข้อกำหนดต่อไปนี้

  • จัดคิวการดาวน์โหลด
  • เริ่มดาวน์โหลด
  • ติดตามความสำเร็จและความล้มเหลว
  • เป็นสัญญาณเมื่อทุกอย่างเสร็จเรียบร้อยแล้ว
  • ดึงข้อมูลเนื้อหาได้ง่าย

การจัดคิว

ข้อกำหนดแรกคือการจัดลำดับการดาวน์โหลด การออกแบบนี้ช่วยให้คุณประกาศเนื้อหาที่ต้องการได้โดยไม่ต้องดาวน์โหลดจริง วิธีนี้มีประโยชน์ในกรณีที่ต้องการประกาศเนื้อหาทั้งหมดสําหรับระดับเกมในไฟล์การกำหนดค่า

โค้ดสำหรับตัวสร้างและการจัดคิวจะมีลักษณะดังนี้

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 และสร้างออบเจ็กต์รูปภาพใหม่ มีการเพิ่ม 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);
}

การเปรียบเทียบความสำเร็จจากจำนวนความสำเร็จและข้อผิดพลาดกับขนาดของคิวดาวน์โหลดจะทำให้เครื่องมือจัดการเนื้อหารู้ว่าเนื้อหาทุกรายการเสร็จสมบูรณ์หรือมีข้อผิดพลาดบางอย่างหรือไม่

แน่นอนว่าการทราบว่าการกระทำเสร็จสิ้นเป็นเพียงแค่ครึ่งทางเท่านั้น ผู้จัดการเนื้อหาก็ต้องตรวจสอบวิธีการนี้ด้วยเช่นกัน เราจะเพิ่มการตรวจสอบนี้ภายในตัวแฮนเดิลเหตุการณ์ทั้ง 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);
});

โค้ดด้านบนแสดงข้อมูลต่อไปนี้

  1. สร้างตัวจัดการชิ้นงานใหม่
  2. จัดคิวเนื้อหาที่จะดาวน์โหลด
  3. เริ่มดาวน์โหลดด้วย downloadAll()
  4. ส่งสัญญาณเมื่อเนื้อหาพร้อมแล้วโดยเรียกใช้ฟังก์ชันเรียกกลับ
  5. ดึงข้อมูลชิ้นงานด้วย getAsset()

สิ่งที่ยังพัฒนาได้อีก

เครื่องมือจัดการชิ้นงานที่เรียบง่ายนี้พัฒนาขึ้นมาได้อย่างแน่นอน ไม่ว่าคุณจะสร้างเกมขึ้นมา แต่ฉันหวังว่านี่จะเป็นการเริ่มเกมพื้นฐาน ฟีเจอร์ในอนาคตอาจประกอบด้วย

  • ส่งสัญญาณว่าชิ้นงานใดมีข้อผิดพลาด
  • โค้ดเรียกกลับเพื่อแสดงความคืบหน้า
  • ดึงข้อมูลเนื้อหาจาก File System API

โปรดโพสต์การปรับปรุง ส้อม และลิงก์ไปยังโค้ดในความคิดเห็นด้านล่าง

แหล่งที่มาแบบเต็ม

แหล่งที่มาของผู้จัดการเนื้อหานี้และเกมที่เรียกที่มานั้นเป็นโอเพนซอร์สภายใต้การอนุญาตให้ใช้สิทธิ Apache และดูได้ในบัญชี Bad Aliens GitHub เกม Bad Aliens เล่นได้ในเบราว์เซอร์ที่เข้ากันได้กับ HTML5 เกมนี้เป็นหัวข้อการพูดคุยของ Google IO ที่ชื่อ Super Browsing 2 Turbo HD Remix: ข้อมูลเบื้องต้นเกี่ยวกับการพัฒนาเกม HTML5 (สไลด์ วิดีโอ)

สรุป

เกมส่วนใหญ่จะมีเครื่องมือจัดการเนื้อหาบางประเภท แต่เกม HTML5 ต้องใช้ตัวจัดการเนื้อหาที่โหลดเนื้อหาผ่านเครือข่ายและจัดการกับความล้มเหลว บทความนี้กล่าวถึงเครื่องมือจัดการเนื้อหาแบบง่ายที่ควรใช้งานและปรับให้เข้ากับเกม HTML5 ถัดไปได้ง่ายๆ ขอให้สนุก และโปรดบอกให้เราทราบว่าคุณคิดอย่างไรในความคิดเห็นด้านล่าง ขอขอบคุณ