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

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

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

تحتاج ألعاب المنصّات platform games (مثل اللعبة التي نُنشئها) إلى إدخالٍ من كلتا اليدين. غالبًا ما يكون الجانب الأيمن من لوحة المفاتيح مخصّصًا للحركة. أما الجانب الأيسر من لوحة المفاتيح فيكون غالبًا مخصّصًا لإجراءات اللاعب، مثل إطلاق النار أو فتح الأبواب.

ربما ترغب في تصميم لعبة منصة مثل Terraria، والتي تستخدم الفأرة لإجراءات اللاعب والتصويب على الهدف. ربما تريد استخدام WASD لتحريك اللاعب على الشاشة. هيا بنا إلى العمل!

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

  • نقل صنف اللاعب إلى وحدة نمطية
  • الكشف عن الأحداث الخاصّة بلوحة المفاتيح والفأرة
  • التسارع والتباطؤ

إنشاء الوحدات

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

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

export default Player;

يمكن أن تلاحظ أنّ فئة اللاعب مماثلة لتلك التي أنشأناها من قبل. الجزء المهم هنا هو export default Player. هذا يجعل فئة اللاعب قابلة للاستيراد بالطريقة التالية:

import Player from "./player";

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

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

لا تنس أن تتحقق من أن الشّكل ما زال يتحرك عبر الشاشة. ومثلما ذكرنا من قبل، يمكنك بدء اختبارٍ للخادم باستعمال php -S 127.0.0.1:8000.

الكشف عن المدخلات

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

تكمن الحيلة في كشف الأحداث في المستوى العلوي، وحفظ تفاصيلها في كائن خاصّ بحالة اللعبة (game state object). لقد أنشأنا من قبل هذا النوع من الكائنات الخاصّ بحالة اللعبة، وقمنا بتمريره إلى الدالة الوظيفية animate()‎ الخاصّة بتحريك اللاعب. دعنا نتوسع في هذه الحالة:

var state = {
    "renderer": renderer,
    "stage": stage,
    "keys": {},
    "clicks": {},
    "mouse": {}
};

window.addEventListener("keydown", function(event) {
    state.keys[event.keyCode] = true;
});

window.addEventListener("keyup", function(event) {
    state.keys[event.keyCode] = false;
});

window.addEventListener("mousedown", function(event) {
    state.clicks[event.which] = {
        "clientX": event.clientX,
        "clientY": event.clientY
    };
});

window.addEventListener("mouseup", function(event) {
    state.clicks[event.which] = false;
});

window.addEventListener("mousemove", function(event) {
    state.mouse.clientX = event.clientX;
    state.mouse.clientY = event.clientY;
});

animate();

function animate() {
    requestAnimationFrame(animate);

    player.animate(state);

    renderer.render(stage);
}

بدأنا بإضافة المصيّر والحالة إلى كائن حالة اللعبة. لقد أضفنا الآن المفاتيح والنقرات وخصائص الفأرة والتي تتعقب الأزرار والحركات.

اقتباس

بالنسبة لهذا الجزء التالي، كان عليّ أن أضبط شيفرة CSS الخاصة بالملفّ index.html لتأخذ اللوحة القماشية موضعًا ثابتا. هذا يتيح تتبع حركة الفأرة بدقّة أكثر. تستطيع فحص الشيفرة لرؤية التغيير الحاصل.

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

animate(state) {
    if (state.keys[37]) { // left يسار
        this.x = Math.max(
            0, this.x  5
        );
    }

    if (state.keys[39]) { // right يمين
        this.x = Math.min(
            window.innerWidth - 64, this.x + 5
        );
    }

    if (state.clicks[1]) { // left click نقرة يسار
        this.x = state.clicks[1].clientX;
    }

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

هناك تغيير فعلي بسيط في الدالّة ()animate. نترقب المفتاحين السهميين، ونتحرك إذا ضغط اللاعب على أحدهما. نتحقق أيضًا مما إذا نقر اللاعب على الفأرة، ونحرّك اللاعب على الفور إذا كان الأمر كذلك.

اقتباس

لا تولي اهتماما للدّوال الرياضية وللرقم السحري 64. فهذه الأشياء موجودة فقط لمنع اللاعب من الخروج من الشاشة، وسنزيلها عندما نبدأ في بناء الجدران …

حركة اللاعب الطبيعية

يستطيع اللاعب التحرك الآن قليلاً. لكنه لا يحسّ أن الأمور جيدة رغم ذلك. في اللحظة التي نترك فيها مفتاح السهم، فإنه يتوقف وحسب. كما أنّ سرعته تبقى على نسق واحد هو النسق البطيئٍ.

ما نحتاج إليه هو بعض التسارع للسماح للاعب بتسريع حركته في اتجاهٍ ما. يمكننا أيضًا استخدام بعض الاحتكاك لإبطاء اللاعب عندما لا نرغب في التسارع. دعنا نضيف هذه الأمور:

class Player {
    constructor(sprite, x, y) {
        this.sprite = sprite;
        this.x = x;
        this.y = y;
        this.velocityX = 0;
        this.maximumVelocityX = 12;
        this.accelerationX = 2;
        this.frictionX = 0.9;


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


   animate(state) {
        if (state.keys[37]) { // left
            this.velocityX = Math.max(
                this.velocityX - this.accelerationX,
                this.maximumVelocityX * -1
            );
        }

        if (state.keys[39]) { // right
            this.velocityX = Math.min(
                this.velocityX + this.accelerationX,
                this.maximumVelocityX
            );
        }

        this.velocityX *= this.frictionX;
        this.x += this.velocityX;
        this.sprite.x = this.x;
        this.sprite.y = this.y;
    }
}

هيّا بنا! دعنا نراجع هذا في بضع خطوات:

  1. لقد أنشأنا مجموعة من الخصائص: السرعة والسرعة القصوى والتسارع والاحتكاك. لقد وضعناها على قيمٍ افتراضية معقولة. لا تتردد في تجربة قيمها حتى تتأكد فعلًا أنها كذلك.
  2. يمكننا أن نبدأ في تتبع تسارع لاعبنا في أي من الاتجاهين. إذا ضغطنا على المفتاح الأيسر، فسنبدأ بتسريع اللاعب في هذا الاتجاه. هناك تسارع أعظمي يمكن أن يصل إليه اللاعب، لذلك لا يتجاوزه.
  3. في غياب نوع من القوة المضادة، سيستمر اللاعب في التحرك في نفس الاتجاه. هذا يشبه ما يحدث في الفضاء حيث لا توجد مقاومة الهواء أو الجاذبية لمواجهة القوّة الدافعة.

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

ترجمة -وبتصرف- للمقال Player Input لصاحبه Christopher Pitt

اقرأ أيضًا





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


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



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

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

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


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

تسجيل الدخول

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


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