إدارة بسيطة للأصول في ألعاب HTML5

مقدمة

لقد وفر HTML5 العديد من واجهات برمجة التطبيقات المفيدة لإنشاء تطبيقات ويب حديثة وسريعة الاستجابة وقوية في المتصفح. هذا رائع، ولكنك تريد حقًا بناء الألعاب ولعبها! لحسن الحظ، ساهم HTML5 أيضًا في دخول عصر جديد من تطوير الألعاب يستخدم واجهات برمجة تطبيقات مثل 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";

تخيَّل توفُّر مئات الصور التي تحتاج إلى تحميلها وعرضها عند بدء تشغيل اللعبة. كيف تعرف عندما تكون جميع الصور المائة جاهزة؟ هل تم تحميلها جميعًا بنجاح؟ متى يجب أن تبدأ اللعبة؟

الحل

يمكنك السماح لمدير مواد العرض بالتعامل مع إضافة مواد العرض إلى "قائمة المحتوى التالي" وإرسال تقارير إلى اللعبة عندما يصبح كل شيء جاهزًا. ويعمل مدير مواد العرض على تعميم منطق تحميل مواد العرض عبر الشبكة، ويوفّر طريقة سهلة للتحقّق من حالة مواد العرض.

يتطلب مدير مواد العرض البسيط المتطلبات التالية:

  • إضافة الفيديوهات إلى قائمة المحتوى التالي
  • بدء عمليات التنزيل
  • تتبع النجاح والفشل
  • الإشارة عند الانتهاء من كل شيء
  • سهولة استرداد الأصول

الإضافة إلى "قائمة المحتوى التالي"

الشرط الأول هو إضافة التنزيلات إلى قائمة المحتوى التالي. يتيح لك هذا التصميم الإعلان عن الأصول التي تحتاجها دون تنزيلها فعليًا. ويمكن أن يكون ذلك مفيدًا إذا كنت تريد مثلاً توضيح جميع مواد العرض الخاصة بأحد مستويات اللعبة في ملف إعداد.

تبدو التعليمة البرمجية للدالة الإنشائية والإضافة في قائمة الانتظار على النحو التالي:

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

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

بدء عمليات التنزيل

بعد إضافة جميع مواد العرض إلى قائمة الانتظار ليتم تنزيلها، يمكنك أن تطلب من مدير مواد العرض البدء بتنزيل كلّها.

ولحسن الحظ، يمكن لمتصفح الويب إجراء عمليات التنزيل بشكل موازٍ، مع ما يصل عادةً إلى 4 اتصالات لكل مضيف. تتمثل إحدى طرق تسريع تنزيل مواد العرض في استخدام مجموعة من أسماء النطاقات لاستضافة مواد العرض. على سبيل المثال، بدلاً من عرض كل المحتوى من Asset.example.com، جرِّب استخدام 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() ببساطة عبر قائمة انتظار التنزيل وينشئ كائن صورة جديدًا. تتم إضافة أداة معالجة الحدث لحدث التحميل ويتم ضبط 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);
}

من خلال مقارنة عدد النجاحات + 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) {
    ...

سنستدعي طريقة downloadbackcallback داخل أدوات معالجة الأحداث لدينا:

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 فقط عند تشغيل أحداث التحميل أو الخطأ. ولكن ماذا لو لم يكن لدى مدير مواد العرض أي مواد عرض في قائمة الانتظار للتنزيل؟ لا يتم تشغيل طريقة "تم" أبدًا، ولا تبدأ اللعبة أبدًا.

يمكنك استيعاب هذا السيناريو عن طريق إضافة الرمز التالي إلى 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. كانت هذه اللعبة هي موضوع محادثتي في IO عن Google بعنوان Super Browser 2 Turbo HD Remix: مقدّمة حول تطوير ألعاب HTML5 (شرائح، فيديو).

ملخّص

تتضمن معظم الألعاب نوعًا ما من أدوات إدارة الأصول، لكن ألعاب HTML5 تتطلب مدير أصول يحمّل الأصول عبر الشبكة ويعالج الإخفاقات. توضّح هذه المقالة أداة بسيطة لإدارة مواد العرض يجب أن يكون من السهل عليك استخدامها والتكيف مع لعبة HTML5 التالية. استمتع بوقتك وأخبرنا برأيك في التعليقات أدناه. شكرًا.