إدارة بسيطة للأصول في ألعاب 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";

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

الحل

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

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

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

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

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

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

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() ببساطة من خلال Downloadقائمة الانتظار لإنشاء عنصر "صورة" جديد. تتم إضافة أداة معالجة حدث لحدث التحميل وتعيين 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);
});

يوضح الرمز أعلاه ما يلي:

  1. إنشاء حساب مدير مواد عرض جديد
  2. إضافة مواد العرض إلى قائمة المحتوى التالي لتنزيلها
  3. بدء عمليات التنزيل باستخدام "downloadAll()"
  4. الإشارة عندما تكون مواد العرض جاهزة من خلال استدعاء وظيفة معاودة الاتصال
  5. استرداد مواد العرض باستخدام "getAsset()"

مجالات التحسين

لا شك أنك ستتغلّب على مدير الأصول البسيط هذا أثناء إنشاء لعبتك، على الرغم من أنني آمل أن يكون هذا الإصدار بمثابة بداية أساسية. يمكن أن تتضمن الميزات المستقبلية ما يلي:

  • الإشارة إلى مادة العرض التي بها خطأ
  • عمليات الاستدعاء للإشارة إلى التقدم
  • استرداد الأصول من File System API

يُرجى نشر التحسينات والروابط والروابط المؤدية إلى الرمز في التعليقات أدناه.

مصدر كامل

إنّ مصدر مدير مواد العرض هذا واللعبة التي تم استخلاصها منه هو برنامج مفتوح المصدر بموجب ترخيص Apache ويمكن العثور عليه في حساب Bad Aliens GitHub. يمكن تشغيل لعبة Bad Aliens في متصفّح متوافق مع HTML5. كانت هذه اللعبة موضوع حديثي في مؤتمر Google IO بعنوان Super Browser 2 Turbo HD Remix: مقدمة عن تطوير ألعاب HTML5 (عروض تقديمية وفيديو).

ملخّص

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