Unity3D: بنية اللعبة ، ScriptableObjects ، Singletones

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

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

الروابط بين النصوص والكائنات


السؤال الأول الذي يواجهه المطور المبتدئ هو كيفية ربط جميع الفئات المكتوبة معًا وتكوين التفاعلات بينهما.

أسهل طريقة هي تحديد رابط للفصل مباشرة:

public class MyScript : MonoBehaviour { public OtherScript otherScript; } 

ثم - ربط النص يدويًا من خلال المفتش.

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

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

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

كائن قابل للبرمجة


هناك شيئان أساسيان يجب معرفتهما عن ScriptableObject:

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

لقد وقعت على الفور في حب حبهم الساخن. لقد أصبحوا ، بطريقة ما ، الدواء الشافي لكل مشكلة:

  • هل تحتاج إلى تخزين إعدادات اللعبة؟ ScriptableObject!
  • إنشاء جرد؟ ScriptableObject!
  • اكتب الذكاء الاصطناعي؟ ScriptableObject!
  • سجل معلومات عن شخصية ، عدو ، عنصر؟ لن يخيبك ScriptableObject!

دون التفكير مرتين ، قمت بإنشاء عدة فئات من النوع ScriptableObject ، ثم مستودع لهم:

 public class Database: ScriptableObject { public PlayerData playerData; public GameSettings gameSettings; public SpellController spellController; } 

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

الآن لست بحاجة لتحديد عدد لا نهائي من الروابط بين النصوص! بالنسبة لكل نص برمجي ، يمكنني تحديد رابط إلى المستودع الخاص بي - وسيتلقى جميع المعلومات من هناك.

وبالتالي ، فإن حساب سرعة الشخصية يأخذ نظرة أنيقة للغاية:

 //   float speed = database.playerData.speed; //    if (database.spellController.haste.active) speed = speed * database.spellController.haste.speedModifier; // ,     if (database.playerData.health<database.playerData.healthThreshold) speed = speed * database.playerData.woundedModifier; 

وإذا ، على سبيل المثال ، يجب أن يطلق المصيدة على شخصية جارية فقط:

 if (database.playerData.isSprinting) Activate(); 

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

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

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

لا شيء جيد.

لفترة طويلة ، عمل عكاز بالنسبة لي: تم إنشاء رابط عام في المستودع ، ثم قام كل كائن ، الرابط الذي تريد تذكره ، بملء هذا الرابط:

 public class PlayerController : MonoBehaviour { void Awake() { database.playerData.player = this.gameObject; } } 

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

 public Database database; Vector3 destination; void Update () { destination = database.playerData.player.transform.position; } 

يبدو أن النظام مثالي! لكن لا. لا تزال لدينا مشكلتان.

  1. لا يزال يتعين علي تحديد ارتباط إلى المستودع يدويًا لكل برنامج نصي.
  2. من المحرج تعيين مراجع لكائنات المشهد داخل ScriptableObject.

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

 database.spellController.light.CastSpell(); 

وهذا يؤدي إلى سلسلة من ردود الفعل:

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

كيف تفعل كل هذا؟

من الممكن لكل كائن مهتم بالضوء ، مباشرة في Update () والكتابة ، كما يقولون ، بهذه الطريقة ، وأن كل إطار يتبع الضوء (إذا (database.spellController.light.isActive)) ، وعندما يضيء ، رد! ولا تهتم أن 90٪ من الوقت سيعمل هذا الشيك خاملاً. على عدة مئات من الأشياء.

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

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

سينجلتون


هنا يأتي دور المفرد. في الواقع ، هذا كائن موجود (ويمكن أن يوجد) فقط في نسخة واحدة.

 public class GameController : MonoBehaviour { public static GameController Instance; //   ,      public Database database; public GameObject player; public GameObject GUI; public List<Enemy> enemies; public List<Spell> spells; void Awake () { if (Instance == null) { DontDestroyOnLoad (gameObject); Instance = this; } else if (Instance != this) { Destroy (gameObject); } } } 

التقطه على كائن مشهد فارغ. دعونا نسميها GameController.

وبالتالي ، لدي كائن في المشهد يخزن جميع المعلومات حول اللعبة. علاوة على ذلك ، يمكن أن تتحرك بين المشاهد ، وتدمير الزوجي (إذا كان هناك بالفعل GameController آخر على المشهد الجديد) ، ونقل البيانات بين المشاهد ، وإذا رغبت ، تنفيذ حفظ / تحميل اللعبة.

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

على ماذا نحصل؟

يمكن لأي كائن الحصول على أي معلومات حول اللعبة التي يحتاجها:

 if (GameController.Instance.database.playerData.isSprinting) ActivateTrap(); 

في هذه الحالة ، ليست هناك حاجة على الإطلاق لتكوين الروابط بين الكائنات ، يتم تخزين كل شيء في GameController.

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

الأخطار


إذا قرأت حتى هذه النقطة ، يجب أن أحذرك من مخاطر هذا النهج.

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

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

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

أيضا ، لا تنس أن الأغاني الفردية هي مخلوقات لطيفة. لا تسيء لهم.

هذا كل شيء لهذا اليوم.

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

وبالتالي - المناقشة موضع ترحيب!

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


All Articles