تكوين مقابل الميراث ونمط الفريق وتطوير اللعبة بشكل عام


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

- باختصار - يمكن للسحرة مع مانا أن يلقيوا تعويذات ، ويمكن للمحاربين القتال في قتال عنيف وقضاء القدرة على التحمل. كل من السحرة والمحاربين يمكن أن تتحرك. نعم ، سيظل من الممكن سرقة الأبقار ، لكننا سنفعل ذلك بالفعل في الإصدار التالي ، باختصار. سوف تظهر النموذج الأولي بعد الوقوف ، حسنا؟

هرب بعيدا عن أعماله تصميم اللعبة ، وفتحت IDE.

في الواقع ، فإن موضوع "التكوين مقابل الميراث" ، "مشكلة قرد الموز" ، "مشكلة المعين (الميراث المتعدد)" هي أسئلة شائعة في المقابلات في أشكال مختلفة ولسبب وجيه. يمكن أن يؤدي الاستخدام غير الصحيح للميراث إلى تعقيد البنية ، ولا يعرف المبرمجون عديمي الخبرة كيفية التعامل مع هذا ، ونتيجة لذلك ، يبدأون في انتقاد OOP ككل والبدء في كتابة التعليمات البرمجية الإجرائية. لذلك ، يعتبر المبرمجون المتمرسون (أو أولئك الذين قرأوا أشياء ذكية على الإنترنت) من واجبهم أن يسألوا عن هذه الأشياء في مقابلة بأشكال مختلفة. الجواب العالمي هو "التكوين أفضل من الميراث ، ولا ينبغي تطبيق ظلال رمادية". أولئك الذين قرأوا للتو كل إجابة من هذا القبيل سيكونون راضين بنسبة 100 ٪.

ولكن ، كما قلت في بداية المقال ، فإن كل هندسة تناسب مشروعك وإذا كان الميراث كافياً لمشروعك وكل ما هو مطلوب لحل المشكلة هو إنشاء Monkey with Banana - قم بإنشائه. كان لدينا مبرمج في وضع مماثل. لا معنى لرفض الميراث لمجرد أن FPS سوف يضحك عليك.

class Character { x = 0; y = 0; moveTo (x, y) { this.x = x; this.y = y; } } class Mage extends Character { mana = 100; castSpell () { this.mana--; } } class Warrior extends Character { stamina = 100; meleeHit () { this.stamina--; } } 

الموقف ، كما هو الحال دائمًا ، استمر. هزت الكرسي وعلقته على الهاتف بينما حاول June Petya إقناع الفاحص بأن استحالة التحكم السريع من خلال زر الفأرة الأيمن ليس خطأ ، لأنه لا يوجد مثل هذا الاحتمال في أي مكان ، مما يعني أنك بحاجة إلى إسقاط المهمة إلى قسم ما قبل الإنتاج. جادل المختبر أنه نظرًا لأن التحكم عن طريق الزر الأيمن للمستخدمين يبدو إلزاميًا ، فهذا خطأ وليس ميزة. في الواقع ، باعتباره اللاعب الوحيد في فريقنا الذي يلعب لعبتنا على خوادم المعارك ، فقد أراد إضافة هذه الميزة في أقرب وقت ممكن ، لكنه كان يعلم أنه إذا قمت بإسقاطها في قسم ما قبل الإنتاج ، فسوف تسمح للآلة البيروقراطية بإصدارها في موعد لا يتجاوز 4 أشهر ، وبعد إصدارها كخلل - يمكنك الحصول عليها بالفعل في الإصدار التالي. كان مدير المشروع ، كما هو الحال دائمًا ، متأخراً ، وكان الرجال يشتكون بشدة لدرجة أنهم قد تحولوا بالفعل إلى الحصير ، وربما كانت الأمور ستندلع قريبًا إذا لم يخرج مدير الاستوديو إلى لعنة وأخذهم إلى مكتبه. ربما مرة أخرى لمدة 300 دولارات تغريمه.

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

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

 class Character { abilities = []; addAbility (...abilities) { for (const a of abilities) { this.abilities.push(a); } return this; } getAbility (AbilityClass) { for (const a of this.abilities) { if (a instanceof AbilityClass) { return a; } } return null; } } /////////////////////////////////////// // //    ,      //       // /////////////////////////////////////// class Ability {} class HealthAbility extends Ability { health = 100; maxHealth = 100; } class MovementAbility extends Ability { x = 0; y = 0; moveTo(x, y) { this.x = x; this.y = y; } } class SpellCastAbility extends Ability { mana = 100; maxMana = 100; cast () { this.mana--; } } class MeleeFightAbility extends Ability { stamina = 100; maxStamina = 100; constructor (power) { this.power = power; } hit () { this.stamina--; } } /////////////////////////////////////// // //        // /////////////////////////////////////// class CharactersFactory { createMage () { return new Character().addAbility( new MovementAbility(), new HealthAbility(), new SpellCastAbility() ); } createWarrior () { return new Character().addAbility( new MovementAbility(), new HealthAbility(), new MeleeFightAbility(3) ); } createPaladin () { return new Character().addAbility( new MovementAbility(), new HealthAbility(), new SpellCastAbility(), new MeleeFightAbility(2) ); } } 

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

 createMagicTree () { return new Character().addAbility( new SpellCastAbility() ); } 

فقدنا الميراث وظهرت تركيبة بدلا من ذلك. الآن نقوم بإنشاء شخصية وسرد قدراته المحتملة. لكن هذا لا يعني أن الميراث سيء دائمًا ، ولكن في هذه الحالة لا يصلح. أفضل طريقة لفهم ما إذا كان الميراث صحيحًا هي الإجابة على السؤال لنفسك عن العلاقة التي يمثلها. إذا كان هذا الاتصال "is-a" ، فهذا يعني أنك تشير إلى أن MeleeFightAbility هي قدرة ، فهي مثالية. إذا تم إنشاء الاتصال فقط لأنك تريد إضافة إجراء ويعرض "has-a" ، فعليك التفكير في التكوين.
لقد استمتعت بالنظر إلى نتيجة رائعة. وهو يعمل بذكاء ودون أخطاء ، هندسة الأحلام! أنا متأكد من أنها ستجتاز أكثر من اختبار زمني ولوقت طويل لن نضطر إلى إعادة كتابته. كنت متحمسًا للغاية لرمز بلدي لدرجة أنني لم ألاحظ كيف اقترب مني يونيو بتيا.

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

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

لقد لعنت لنفسي ، لأنني نسيت تمامًا أنه لا يزال يتعين علينا حل هذا الرأي. لعنة ، يبدو أن لديها لإعادة كتابة العمارة.
عادةً ما تصف مثل هذه المقالات العمل فقط مع النموذج ، لأنه مجرد وبالغ ، ويمكنك إعطاء "صور لإظهارها" إلى يونيو أيضًا ، بغض النظر عن البنية المعمول بها. ومع ذلك ، يجب أن يوفر نموذجنا أقصى قدر من المعلومات للعرض حتى يتمكن من أداء وظيفته. في GameDev ، عادة ما يتم استخدام نمط "الفريق" لهذا الغرض. باختصار - لدينا حالة بدون منطق ، ويجب أن يحدث أي تغيير في الفرق المقابلة. قد يبدو هذا مثل المضاعفات ، لكنه يقدم العديد من المزايا:
- يندمجون جيدًا عندما يتصل فريق واحد بآخر
- كل أمر ، عند تنفيذه ، هو ، في الواقع ، حدث يمكنك الاشتراك فيه
- يمكننا تسلسلها بسهولة.

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

 class DealDamageCommand extends Command { constructor (target, damage) { this.target = target; this.damage = damage; } execute () { const healthAbility = this.target.getAbility(HealthAbility); if (healthAbility == null) { throw new Error('NoHealthAbility'); } const resultHealth = healthAbility.health - this.damage; healthAbility.health = Math.max( 0, resultHealth ); } } 

أرغب في إنشاء أوامر هرمية - عندما يتم تنفيذ أحد هذه الأوامر ، فإنها تلد العديد من الأطفال ، والتي ينفذها المحرك بعد ذلك. والآن بعد أن أصبح لدينا القدرة على التعامل مع الأضرار ، يمكننا محاولة تنفيذ إضراب

 class MeleeHitCommand extends Command { constructor (source, target, damage) { this.source = source; this.target = target; this.damage = damage; } execute () { const fightAbility = this.source.getAbility(MeleeFightAbility); if (fightAbility == null) { throw new Error('NoFightAbility'); } this.addChildren([ new DealDamageCommand(this.target, fightAbility.power); ]); } } 

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

 async onMeleeHit (meleeHitCommand) { await view.drawMeleeHit( meleeHitCommand.source, meleeHitCommand.target ); } async onDealDamage (dealDamageCommand) { await view.showDamageNumbers( dealDamageCommand.target, dealDamageCommand.damage ); } 

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

- هنا - وضع مصمم اللعبة أمامي تلمود من 200 صفحة مطبوعة على أوراق A4. على الرغم من أن وثيقة التصميم قد نُفذت بالتقاء ، فقد أحببنا أن نطبعها في مراحل مهمة حتى نشعر بهذا العمل في تجسيد مادي. لقد فتحته على صفحة عشوائية وحصلت على قائمة ضخمة بمجموعة متنوعة من التعويذات التي يمكن أن يقوم بها السحراء و بالادين ، ووصف لآثارها ، ومتطلبات الذكاء ، وسعر مانا ووصف تقريبي للفنانين حول كيفية عرضها. العمل لعدة أشهر ، لأنني سأظل اليوم في العمل.
الهندسة المعمارية لدينا يجعل من السهل إنشاء مجموعات معقدة من نوبات. إنه فقط أن كل تعويذة يمكنها إرجاع قائمة بالأوامر التي يجب إتمامها خلال فريق التمثيل

 class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility); if (spellAbility == null) { throw new Error('NoSpellCastAbility'); } this.addChildren(new PayManaCommand(this.source, this.spell.manaCost)); this.addChildren(this.spell.getCommands(this.source, this.target)); } } class Spell { manaCost = 0; getCommands (source, target) { return []; } } class DamageSpell extends Spell { manaCost = 3; constructor (damageValue) { this.damageValue = damageValue; } getCommands (source, target) { return [ new DealDamageCommand(target, this.damageValue) ]; } } class HealSpell extends Spell { manaCost = 2; constructor (healValue) { this.healValue = healValue; } getCommands (source, target) { return [ new HealDamageCommand(target, this.healValue) ]; } } class VampireSpell extends Spell { manaCost = 5; constructor (value) { this.value = value; } getCommands (source, target) { return [ new DealDamageCommand(target, this.value), new HealDamageCommand(source, this.value) ]; } } 

بعد سنة ونصف

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

- اسمع - كتب مصمم اللعبة - لدي فكرة رائعة ...

Source: https://habr.com/ru/post/ar438510/


All Articles