Pengelolaan aset yang sederhana untuk game HTML5

Pengantar

HTML5 telah menyediakan banyak API yang berguna untuk membangun aplikasi web yang modern, responsif, dan canggih di browser. Ini bagus, tapi Anda benar-benar ingin membangun dan memainkan game! Untungnya, HTML5 juga telah mengantarkan era baru pengembangan game yang menggunakan API seperti Canvas dan mesin JavaScript yang canggih untuk menghadirkan game langsung ke browser Anda tanpa membutuhkan plugin.

Artikel ini akan memandu Anda dalam membangun komponen Pengelolaan Aset sederhana untuk game HTML5. Tanpa pengelola aset, game Anda akan mengalami kesulitan untuk mengimbangi waktu download yang tidak diketahui dan pemuatan gambar asinkron. Ikuti panduan ini untuk melihat contoh pengelola aset sederhana untuk game HTML5 Anda.

Masalah

Game HTML5 tidak dapat mengasumsikan aset seperti gambar atau audio akan berada di komputer lokal pemain, karena game HTML5 dimainkan di browser web dengan aset yang didownload melalui HTTP. Karena jaringan terlibat, browser tidak yakin kapan aset untuk game akan didownload dan tersedia.

Cara dasar untuk memuat gambar secara terprogram di browser web adalah kode berikut:

var image = new Image();
image.addEventListener("success", function(e) {
  // do stuff with the image
});
image.src = "/some/image.png";

Sekarang bayangkan memiliki seratus gambar yang perlu dimuat dan ditampilkan saat game dimulai. Bagaimana cara mengetahui bahwa 100 gambar sudah siap? Apakah semuanya berhasil dimuat? Kapan seharusnya game dimulai?

Solusi

Biarkan pengelola aset menangani antrean aset dan melapor kembali ke game jika semuanya sudah siap. Pengelola aset menggeneralisasi logika untuk memuat aset melalui jaringan, dan ini memberikan cara mudah untuk memeriksa statusnya.

Pengelola aset sederhana kita memiliki persyaratan berikut:

  • mengantrekan download
  • mulai download
  • melacak keberhasilan dan kegagalan
  • memberikan sinyal ketika semuanya sudah selesai
  • agar aset mudah diambil

Mengantre

Persyaratan pertama adalah mengantrekan download. Desain ini memungkinkan Anda mendeklarasikan aset yang diperlukan tanpa benar-benar mendownloadnya. Hal ini dapat berguna jika, misalnya, Anda ingin mendeklarasikan semua aset untuk level game dalam file konfigurasi.

Kode untuk konstruktor dan antrean terlihat seperti ini:

function AssetManager() {
  this.downloadQueue = [];
}

AssetManager.prototype.queueDownload = function(path) {
    this.downloadQueue.push(path);
}

Mulai Download

Setelah mengantrekan semua aset untuk didownload, Anda dapat meminta pengelola aset untuk mulai mendownload semuanya.

Untungnya, browser web dapat memparalelkan download—biasanya hingga 4 koneksi per host. Salah satu cara untuk mempercepat download aset adalah dengan menggunakan rentang nama domain untuk hosting aset. Misalnya, daripada menyajikan semuanya dari assets.example.com, coba gunakan assets1.example.com, assets2.example.com, assets3.example.com, dan sebagainya. Bahkan apabila setiap nama domain tersebut hanya berupa CNAME untuk server web yang sama, browser web akan menganggapnya sebagai server terpisah dan meningkatkan jumlah koneksi yang digunakan untuk mendownload aset. Pelajari lebih lanjut teknik ini dari Memisahkan Komponen di Seluruh Domain di Praktik Terbaik untuk Mempercepat Situs Web Anda.

Metode kita untuk inisialisasi download disebut downloadAll(). Kami akan mengembangkannya seiring waktu. Untuk saat ini, berikut adalah logika pertama untuk memulai download.

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;
    }
}

Seperti yang dapat Anda lihat dalam kode di atas, downloadAll() hanya melakukan iterasi melalui downloadQueue dan membuat objek Image baru. Pemroses peristiwa untuk peristiwa pemuatan ditambahkan dan src gambar ditetapkan, yang memicu download sebenarnya.

Dengan metode ini, Anda dapat memulai download.

Melacak Keberhasilan dan Kegagalan

Persyaratan lainnya adalah melacak keberhasilan dan kegagalan, karena sayangnya tidak semuanya selalu berjalan dengan sempurna. Sejauh ini, kode ini hanya melacak aset yang berhasil didownload. Dengan menambahkan pemroses peristiwa untuk peristiwa error, Anda akan dapat merekam skenario berhasil dan gagal.

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;
  }
}

Pengelola aset kami perlu mengetahui berapa banyak keberhasilan dan kegagalan yang kami temui, atau tidak akan pernah tahu kapan game dapat dimulai.

Pertama, kita akan menambahkan penghitung ke objek di konstruktor, yang kini terlihat seperti ini:

function AssetManager() {
<span class="highlight">    this.successCount = 0;
    this.errorCount = 0;</span>
    this.downloadQueue = [];
}

Selanjutnya, tambahkan penghitung di pemroses peristiwa, yang sekarang terlihat seperti ini:

img.addEventListener("load", function() {
    <span class="highlight">that.successCount += 1;</span>
}, false);
img.addEventListener("error", function() {
    <span class="highlight">that.errorCount += 1;</span>
}, false);

Pengelola aset kini melacak aset yang berhasil dimuat dan yang gagal.

Memberi Sinyal Setelah Selesai

Setelah game mengantrekan asetnya untuk didownload, dan meminta pengelola aset untuk mendownload semua aset, game tersebut perlu diberi tahu saat semua aset telah didownload. Pengelola aset dapat memberi sinyal kembali ke game, bukan meminta aset didownload berulang-ulang.

Pengelola aset harus terlebih dahulu mengetahui kapan setiap aset selesai dibuat. Kita akan menambahkan metode isDone sekarang:

AssetManager.prototype.isDone = function() {
    return (this.downloadQueue.length == this.successCount + this.errorCount);
}

Dengan membandingkan successCount + errorCount dengan ukuran downloadQueue, pengelola aset akan mengetahui apakah setiap aset berhasil diselesaikan atau memiliki semacam error.

Tentu saja mengetahui bahwa penyelesaian masalah hanyalah setengah dari upaya yang harus dilakukan; pengelola aset juga perlu memeriksa metode ini. Kita akan menambahkan pemeriksaan ini di dalam kedua pengendali peristiwa, seperti yang ditunjukkan kode di bawah ini:

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);

Setelah penghitung bertambah, kita akan melihat apakah itu adalah aset terakhir dalam antrean. Jika pengelola aset telah selesai mendownload, apa yang harus kita lakukan?

Jika pengelola aset selesai mendownload semua aset, kita akan memanggil metode callback. Mari kita ubah downloadAll() dan tambahkan parameter untuk callback:

AssetManager.prototype.downloadAll = function(downloadCallback) {
    ...

Kita akan memanggil metode downloadCallback di dalam pemroses peristiwa:

img.addEventListener("load", function() {
    that.successCount += 1;
    if (that.isDone()) {
        downloadCallback();
    }
}, false);
img.addEventListener("error", function() {
    that.errorCount += 1;
    if (that.isDone()) {
        downloadCallback();
    }
}, false);

Pengelola aset akhirnya siap untuk memenuhi persyaratan terakhir.

Pengambilan Aset dengan Mudah

Setelah game diberi sinyal bahwa game bisa dimulai, game akan mulai merender gambar. Pengelola aset tidak hanya bertanggung jawab untuk mendownload dan melacak aset, tetapi juga menyediakannya ke dalam game.

Persyaratan akhir kita menyiratkan semacam metode getAsset, jadi kita akan menambahkannya sekarang:

AssetManager.prototype.getAsset = function(path) {
    return this.cache[path];
}

Objek cache ini diinisialisasi di konstruktor, yang sekarang terlihat seperti ini:

function AssetManager() {
    this.successCount = 0;
    this.errorCount = 0;
    this.cache = {};
    this.downloadQueue = [];
}

Cache akan diisi pada akhir downloadAll(), seperti yang ditunjukkan di bawah ini:

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>
  }
}

Bonus: Perbaikan Bug

Apakah Anda menemukan bug? Seperti yang ditulis di atas, metode isDone hanya dipanggil bila peristiwa pemuatan atau error dipicu. Namun, bagaimana jika pengelola aset tidak memiliki aset dalam antrean untuk didownload? Metode isDone tidak pernah dipicu, dan game tidak pernah dimulai.

Anda dapat mengakomodasi skenario ini dengan menambahkan kode berikut ke downloadAll():

AssetManager.prototype.downloadAll = function(downloadCallback) {
    if (this.downloadQueue.length === 0) {
      downloadCallback();
  }
 ...

Jika tidak ada aset dalam antrean, callback akan segera dipanggil. Bug telah diperbaiki!

Contoh Penggunaan

Menggunakan pengelola aset ini di game HTML5 cukup mudah. Berikut adalah cara paling dasar untuk menggunakan library:

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);
});

Kode di atas menggambarkan:

  1. Membuat pengelola aset baru
  2. Mengantrekan aset untuk didownload
  3. Mulai download dengan downloadAll()
  4. Memberi sinyal saat aset sudah siap dengan memanggil fungsi callback
  5. Ambil aset dengan getAsset()

Area yang Perlu Diperbaiki

Anda pasti akan mengalahkan pengelola aset sederhana ini saat membangun game, meskipun saya harap ini bisa memberikan awal yang mendasar. Fitur mendatang dapat mencakup:

  • menandai aset mana yang memiliki error
  • callback untuk menunjukkan progres
  • mengambil aset dari File System API

Posting peningkatan, fork, dan link ke kode dalam komentar di bawah.

Sumber Lengkap

Sumber untuk pengelola aset ini, dan game yang memisahkannya, adalah open source berdasarkan Lisensi Apache dan dapat ditemukan di akun GitHub Bad Aliens. Game Alien yang buruk dapat dimainkan di browser yang kompatibel dengan HTML5. Game ini menjadi subjek untuk diskusi Google IO saya yang berjudul Super Browser 2 Turbo HD Remix: Introduction to HTML5 Game Development (slide, video).

Ringkasan

Sebagian besar game memiliki pengelola aset, tetapi game HTML5 memerlukan pengelola aset yang memuat aset melalui jaringan dan menangani kegagalan. Artikel ini menguraikan pengelola aset sederhana yang mudah digunakan dan disesuaikan untuk game HTML5 Anda berikutnya. Bersenang-senanglah, dan sampaikan pendapat Anda melalui komentar di bawah. Terima kasih.