سؤال

بالنسبة إلى لعبة ثنائية الأبعاد أقوم بها (لنظام Android) ، أستخدم نظامًا قائمًا على المكونات حيث يحمل GameObject العديد من كائنات GameComponent. يمكن أن تكون GameComponents أشياء مثل مكونات الإدخال ، ومكونات تقديم ، ومكونات انبعاث الرصاص ، وما إلى ذلك. حاليًا ، لدى GameComponents إشارة إلى الكائن الذي يمتلكها ويمكنه تعديله ، لكن GameObject نفسها لديها قائمة بالمكونات ولا تهتم بالمكونات طالما يمكن تحديثها عند تحديث الكائن.

في بعض الأحيان يكون للمكون بعض المعلومات التي يحتاج GameObject إلى معرفتها. على سبيل المثال ، بالنسبة للكشف عن الاصطدام ، يسجل GameObject نفسه مع نظام الكشف عن التصادم عندما يصطدم به كائن آخر. يحتاج النظام الفرعي للكشف عن التصادم إلى معرفة المربع المحيط للكائن. أقوم بتخزين X و Y في الكائن مباشرة (لأنه يتم استخدامه بواسطة عدة مكونات) ، لكن العرض والارتفاع معروفان فقط لمكون التقديم الذي يحمل صورة نقطية للكائن. أرغب في الحصول على طريقة getBoundingBox أو getWidth في GameObject التي تحصل على تلك المعلومات. أو بشكل عام ، أريد إرسال بعض المعلومات من مكون إلى الكائن. ومع ذلك ، في تصميمي الحالي ، لا يعرف GameObject المكونات المحددة التي لديها في القائمة.

يمكنني التفكير في عدة طرق لحل هذه المشكلة:

  1. بدلاً من الحصول على قائمة عامة تمامًا بالمكونات ، يمكنني السماح لـ GameObject بحقل محدد لبعض المكونات المهمة. على سبيل المثال ، يمكن أن يكون لديه متغير عضو يسمى RenderingComponent ؛ كلما احتجت إلى الحصول على عرض الكائن الذي أستخدمه للتو renderingComponent.getWidth(). لا يزال هذا الحل يسمح بقائمة عامة بالمكونات ، لكنه يعامل بعضها بشكل مختلف ، وأخشى أن ينتهي الأمر بوجود العديد من الحقول الاستثنائية حيث تحتاج المزيد من المكونات إلى الاستعلام عنها. بعض الكائنات ليس لديها حتى مكونات تقديم.

  2. لديك المعلومات المطلوبة كأعضاء في GameObject ولكن السماح للمكونات بتحديثها. لذا فإن الكائن له عرض وارتفاع 0 أو -1 بشكل افتراضي ، ولكن يمكن لمكون التقديم تعيينها على القيم الصحيحة في حلقة التحديث الخاصة به. هذا يبدو وكأنه اختراق وقد ينتهي بي الأمر إلى دفع العديد من الأشياء إلى فئة GameObject للراحة حتى لو لم تكن كل الأشياء تحتاجها.

  3. اطلب من مكونات تطبيق واجهة تشير إلى نوع المعلومات التي يمكن الاستعلام عنها. على سبيل المثال ، سيقوم مكون التقديم بتطبيق واجهة المتسايق التي تتضمن طرقًا مثل GetWidth و Getheight. عندما يحتاج GameObject إلى العرض ، فإنه يحلق على مكوناته للتحقق مما إذا كانت تنفذ واجهة Hassize (باستخدام instanceof الكلمة الرئيسية في جافا ، أو is في C#). هذا يبدو وكأنه حل أكثر عامة ، عيب واحد هو أن البحث عن المكون قد يستغرق بعض الوقت (ولكن بعد ذلك ، تحتوي معظم الكائنات على 3 أو 4 مكونات فقط).

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

النسخة القصيرة

في نظام قائم على المكونات للعبة ، ما هي أفضل طريقة لتمرير المعلومات من المكونات إلى الكائن مع الحفاظ على التصميم العام؟

هل كانت مفيدة؟

المحلول

نحصل على اختلافات في هذا السؤال ثلاث أو أربع مرات في الأسبوع على gamedev.net (حيث يُطلق على GameObject عادةً "الكيان") وحتى الآن لا يوجد إجماع على أفضل طريقة. لقد تبين أن العديد من الأساليب المختلفة قابلة للتطبيق ولكن لن تقلق بشأن ذلك كثيرًا.

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

المكونات العامة تمامًا غير مجدية إلى حد كبير: فهي بحاجة إلى توفير نوع من الواجهة المعروفة وإلا فهناك القليل من النقطة الموجودة عليها. وإلا ، فقد يكون لديك أيضًا مجموعة نقاطية كبيرة من القيم غير المعطلة ويتم ذلك. في Java و Python و C#واللغات الأخرى ذات المستوى الأعلى قليلاً عن C ++ ، يمكنك استخدام الانعكاس لمنحك طريقة عامة أكثر لاستخدام فئات فرعية محددة دون الحاجة إلى تشفير معلومات النوع والواجهة في المكونات نفسها.

أما بالنسبة للاتصال:

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

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

أو يمكنك وضع جميع البيانات المشتركة في الكيان نفسه واستخدامها كمنطقة اتصال مشتركة (مرتبطة بشكل ضعيف بـ نظام السبورة في منظمة العفو الدولية) يمكن لكل من المكونات القراءة والكتابة. يتطلب هذا عادة بعض المتانة في مواجهة بعض الخصائص غير الموجودة عندما تتوقع منهم ذلك. كما أنه لا يعرض على التوازي ، على الرغم من أنني أشك في أن هذا مصدر قلق كبير على نظام صغير مضمن ...؟

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

نصائح أخرى

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

لسيناريو المحدد الخاص بك حيث تحتاج إلى تخزين المربع المحيط في مكان ما وفقط مكون الاصطدام يهتم به ، أود:

  1. تخزينه في مكون الاصطدام نفسه.
  2. اجعل رمز الكشف عن التصادم يعمل مع المكونات مباشرة.

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

هذا يمنحك بعض الفوائد:

  1. يترك حالة خاصة بالتصادم من GameObject.
  2. تنطلقك من تكرار GameObjects التي لا تحتوي على مكونات تصادم. (إذا كان لديك الكثير من الكائنات غير التفاعلية مثل المؤثرات المرئية والديكور ، فإن هذا يمكن أن يوفر عددًا لائقًا من الدورات.)
  3. ينفجرك من حرق دورات المشي بين الكائن ومكونه. إذا كنت تكرر من خلال الكائنات ثم افعل getCollisionComponent() على كل واحد ، يمكن أن يتبع ذلك المؤشر إلى تفويت ذاكرة التخزين المؤقت. القيام بذلك لكل إطار لكل كائن يمكن أن يحرق الكثير من وحدة المعالجة المركزية.

إذا كنت مهتمًا لدي المزيد حول هذا النمط هنا, ، على الرغم من أنه يبدو أنك تفهم بالفعل معظم ما هو موجود في هذا الفصل.

استخدم "حافلة الحدث". (لاحظ أنه ربما لا يمكنك استخدام الرمز كما هو ولكن يجب أن يعطيك الفكرة الأساسية).

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

تحرير] للاطلاع على مناقشة أكثر تفصيلاً ، انظر تمرير الرسالة (بفضل SNK_KID لتشير هذا إلى الخارج).

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

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

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

هذا قريب منك الحل 3) ، توقع توصيل الاتصالات بين المكونات مرة واحدة فقط ولا يتم فحصها في كل تكرار لحلقة اللعبة.

ال إطار عمل مُدار لـ .NET هو حل جميل لهذه المشكلة. أدرك أنك تنوي تطويره على Android ، لكن لا يزال بإمكانك الحصول على بعض الإلهام من هذا الإطار.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top