عبد الصمد العماري

الحلقات التكرارية هي جزء أساسي من كلّ لعبة. سنهيّئ في هذا المقال المسرح للعبتنا من خلال إنشاء مخطّط قويٍّ لسير العمل وبيئة متينة كذلك. سنطّلع على بعض المكتبات المفيدة، كما سننشئ أوّل شخصية لنا في اللعبة. إنه لأمرٌ ممتعٌ!

إليك ما سنعمل عليه في هذه المرحلة:

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

تهييئ مسرح اللعبة

إن هناك الكثير من الأشياء التي ينبغي التفكير فيها عند إنشاء لعبة قائمة على المتصفح. سنتعلم التقنيات التي تعمل مع العديد من المنصات واللغات المختلفة.

سنستخدم أيضًا مُحرّك عرضٍ ثنائي الأبعاد يسمى Pixi.js. إنّه ليس مُحرّك ألعاب أو مُحرّك أجسام مادّية. ربّما لا يزال يتعين علينا أن نعرف أكثر كيف تعمل الأجسام المادّية ومكوّنات اللّعبة، ولكن على الأقل لن نضيع الوقت في تناقضات المتصفح. يمكننا أن نأخذ هذا الخيار عندما لا يكون لدينا ما هو أفضل للقيام به. في الوقت الحالي، سنخلق بيئة للمحرّك Pixi كي يعمل فيها:

{
    "scripts": {
        "build": "browserify main.js -t babelify --outfile main.dist.js",
        "watch": "watchify main.js -t babelify --outfile main.dist.js"
    },
    "dependencies": {
        "pixi.js": "^3.0"
    },
    "devDependencies": {
        "babel": "^5.4",
        "babelify": "^6.1",
        "browserify": "^10.2",
        "watchify": "^3.2"
    }
}

أريد أن أكون قادرًا على استخدام ميزات ES6. لذلك، سنحوّل شيفرة ES6 إلى شيفرة ES5 احترازًا حتى يشمل الدعم المتصفحات القديمة. الأدوات التي سنستخدمها في ذلك هي Babel و Browserify و Watchify.

سوف يترجم Babel شيفرة ES6 إلى شيفرة ES5. أمّا Browserify فسيربط جميع وحدات ES6 الخاصة بنا أي ملفّاتنا جافاسكربت فيما بينها، وذلك باستخدام صيغة الاستيراد (import) في حين سيعمل Watchify على تفعيل كلّ من Babel وBrowserify عندما تتغير ملفاتنا.

سنستعمل أيضًا الإصدار 3.0 من Pixi كما سنستعمل سكريبتين NPM سيكونان مجرّد اختصار للأوامر التي قد نُدخلها في الطّرفيّة. وهكذا، بدلاً من كتابة browserify ... main.dist.js نكتب فقط npm run build.

<!doctype html>
<html>
    <head>
        <style>
            html, body {
                margin: 0;
                padding: 0;
                cursor: default;
                background: #000;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <script src="node_modules/pixi.js/bin/pixi.js"></script>
        <script src="main.dist.js"></script>
    </body>
</html>

قد تتساءل هل سنحتاج إلى تضمين كلّ ملفّ جافاسكربت أنشأناه. كلّا، لأن Browserify يربط جميع ملفات جافاسكربت التي تُستورَد إلى main.js فيما بينها ثم يُحوّلها بواسطة Babel، مما يؤدي إلى ملفٍّ واحدٍ. سنحتاج فقط لاستيراد هذا الملف.

ورّبما تتساءل أيضًا عن العناصر التي سنعرضها في لعبتنا. ستندمج الشيفرة التي نكتبها لعرض لعبتنا تلقائيًا في نص الملفّ.

يمكنك عرض هذه الملفات بالانتقال إلى مجلّدها في الطّرفية، وتنفيذ الأمر التالي: php -S 127.0.0.1:8000. يشغّل هذا الأمر خادمَ اختبارٍ PHP في عنوان URL يمكنك وضعه في متصفحك. ستحتاج إلى ذلك قبل أن تتمكن من تحميل صور الأشكال، لذا جرِّب ذلك الآن.

إنشاء الأشكال

الأشكال اسمٌ شائعٌ للكائنات المرئية في اللعبة. نستطيع تحريكها على شاشة اللعبة رغم أنها غالباً ما تكون مسؤولة عن تحريك نفسها، كما نستطيع التفاعل معها كذلك.

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

إذن كيف نُنشِئها؟ لنبدأ بإنشاء صورة PNG صغيرة. نستخدم صيغة PNG لأنها تسمح لنا بجعل أجزاء من نسيج الشّكل شفافةً. يمكنك استخدام صور JPEG لأشكالك إذا كنت ترغب بذلك.

بعد ذلك، نحتاج إلى إنشاء أداةِ عرضٍ ومسرحٍ وشكلٍ من الأشكال:

var renderer = new PIXI.autoDetectRenderer(
    window.innerWidth,
    window.innerHeight,
    {
        "antialias": true,
        "autoResize": true,
        "transparent": true,
        "resolution": 2
    }
);

document.body.appendChild(renderer.view);

var sprite = new PIXI.Sprite.fromImage("player.png");
sprite.x = window.innerWidth / 2;
sprite.y = window.innerHeight / 2;

var stage = new PIXI.Container();
stage.addChild(sprite);

animate();

function animate() {
    requestAnimationFrame(animate);

    renderer.render(stage);
}

حسنًا، تحدث أمور كثيرة هنا، دعنا نلقي نظرة على هذه الشيفرة على مراحل:

1- نُنشِئ المصيّر، وهو أداة تُحوّل العناصر المجرّدة في Pixi (أي الأشكال والأنسجة وغيرها) إلى رسومات قماشية أو رسومات WebGL وتعرضها. ليس علينا أن نتفاعل معها، ولكن يجب أن يكون لدينا دائمًا عارضٌ لعرض عناصر Pixi الخاصة بنا.

نطلب من العارض أن يغطّي نافذةَ المتصفح كاملة بالطول والعرض. إننا نطلب ذلك من أجل تنقية وضوح الرسومات باستعمال مضادات التعرج (Anti Aliasing) ورفعها إلى دقة شبكية العين. نطلب ذلك أيضا للحصول على خلفية شفافة وتغيير حجم جميع الرسومات لتلائم الشاشة.

2- بعدها، نُنشِئ نسخة جديدة للفئة PIXI.Sprite، باستخدام الصورة PNG التي أنشأناها سابقًا. بشكل افتراضي، تكون النسخة في الموضع x = 0 و y = 0. ويمكننا وضعها في وسط الشاشة بدلًا من ذلك.

يتعين علينا إنشاء حاوية جذرية للأشكال والتي تسمى غالبًا مسرحًا، ثم إلحاق الشّكل بالمسرح. إنّ الأمر أقل تعقيدًا مما قد يبدو عليه. فكِّر في الأمر مثل صفحات HTML. يكون لدى الصفحة دائمًا عنصرٌ جذريٌّ HTML يحتوي جميع العناصر الأخرى التي نضيف إليه. إنّه نفس الشيء هنا.

3- أخيرًا، ننشئ دالّةً للتحريك ونحرص على أن يعرض العارض المسرح والشّكل معًا.

نستخدم الدالّة requestAnimationFrame()‎ كطريقة للعرض دون إعاقة سلسلة جافاسكربت. إنّ هناك نقاشًا طويلًا يمكن أن نخوضه حول هذا الموضوع. في الوقت الحالي، من المهم فقط أن تعرف أن الدالّة requestAnimationFrame()‎ تُنفّذ عدة مرات في الثانية.

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

حلقات اللعبة

سوف تتحكم حلقة اللعبة في تدفق لعبتنا. إنها عملية متكررة لقراءة المُدخلات، وحساب التغييرات في الحال وتقديم المُخرجات إلى الشاشة.

كلّ ما قُمنا به حتى الآن هو عرض صورة ثابتة أي إعداد السقالة فحسب. دعنا نتحرك قليلا! بادئ ذي بدء، سنُنشِئ فئة لاعبٍ، حتى نتمكن من تتبع موضع اللاعب:

class Player {
    constructor(sprite, x, y) {
        this.sprite = sprite;
        this.x = x;
        this.y = y;

        this.sprite.x = this.x;
        this.sprite.y = this.y;
    }

    animate(state) {
        this.x += 5;

        if (this.x > window.innerWidth) {
            this.x = 0;
        }

        this.sprite.x = this.x;
        this.sprite.y = this.y;
    }
}

هذا ما تبدو عليه أصناف جافاسكريبت في ES6. تمتلك فئة المشغل مُنشئًا (constructor) نستخدمه لتخزين مرجعٍ يحيل على الشَّكل ويخزّن كذلك إحداثيتي x و y البدئيتين، كما أنّها تمتلك كذلك دالّةً وظيفية animate()‎ سنستدعيها عدة مرات في الثانية. يمكننا استخدام الدّالة لمعرفة ما إذا كان اللاعب يحتاج إلى تغيير موضعه أو القيام بشيء آخر. في هذه الحالة، نُحرّك موضع x للشّكل قليلاً. إذا تحرك اللاعب/الشّكل من الجانب الأيمن من الشاشة، فإننا نعيده إلى الجانب الأيسر منها.

ينبغي علينا أيضًا أن نغيّر كيفية إنشاء الشّكل:

var sprite = new PIXI.Sprite.fromImage("player.png");

var player = new Player(
    sprite, 
    window.innerWidth / 2, 
    window.innerHeight / 2
);

stage.addChild(sprite);

var state = {
    "renderer": renderer,
    "stage": stage
};

animate();

function animate() {
    requestAnimationFrame(animate);

    player.animate(state);

    renderer.render(stage);
}

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

الشيء الوحيد الذي ينقصنا لحدّ الآن هو الإدخال، لكننا سنصل إلى هذه المرحلة قريبًا!

ترجمة -وبتصرف- للمقال The Game Loop لصاحبه Christopher Pitt

اقرأ أيضًا





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن