نمذجة لعبة محمولة ، ومن أين تبدأ ، وكيفية القيام بذلك. الجزء 1

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

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

فكرة تبدو مثل هذا




الجزء 2
الجزء 3

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

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

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

الوظائف العامة التي نطبقها:

  1. يمكنك ضبط المسار الأولي للصاروخ ، مع تحديد درجة الانحراف عن العمودي (لا يمكن تحويل الصاروخ إلى الجانب بأكثر من درجة)
  2. يجب أن يكون هناك زر البدء ، الذي نرسل الصواريخ على الطريق
  3. التمرير على الشاشة عند وضع المعدل (لبدء)
  4. حركة الكاميرا خلف المشغل (بعد البدء)
  5. لوحة الواجهة التي سيتم بها تطبيق معدّلات السحب والإفلات في الحقل
  6. يجب أن يحتوي النموذج الأولي على معدلتين - التنافر والتسارع
  7. يجب أن يكون هناك كواكب عندما تلمس والتي تموت
  8. يجب أن يكون هناك كوكب عندما تلمس الذي تفوز به

هندسة معمارية


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

قبل أي بداية نكرر دائما - الصلبة ، قبلة ، جافة ، YAGNI. حتى المبرمجين المتمرسين ينسون قبلة وياني.

ما هي العمارة الأساسية التي أوافق عليها

هناك Gameobject Gameobject فارغ في المشهد به علامة مقابلة ، والمكونات / الدراجات الأحادية معلقة عليه ، من الأفضل أن تجعله الجاهزة ، ثم أضف المكونات حسب الضرورة إلى الجاهزة:

  1. GameController - (مسؤول عن حالة اللعبة ، مباشرة إلى المنطق (فاز ، ضاع ، مقدار الحياة ، وما إلى ذلك)
  2. InputController - كل ما يتعلق بإدارة المشغل وتتبع tachi والنقرات والذين نقروا والتحكم في الحالة ، إلخ.
  3. TransformManager - في الألعاب ، غالبًا ما تحتاج إلى معرفة من هو المكان والبيانات المختلفة المتعلقة بمركز اللاعب / الأعداء. على سبيل المثال ، إذا تجاوزنا كوكبًا ، فحينئذٍ يهزم اللاعب ، تكون وحدة التحكم في اللعبة مسؤولة عن ذلك ، ولكن يجب عليه معرفة موقف اللاعب من أين. مدير التحويل هو بالضبط الجوهر الذي يعرف الأشياء
  4. AudioController - من الواضح هنا أنه يتعلق بالأصوات
  5. InterfacesController - وهنا يتضح ، هذا حول واجهة المستخدم

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

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

وهكذا بدأ الرمز


الطبقة الأساسية للصواريخ
using GlobalEventAggregator; using UnityEngine; using UnityEngine.Assertions; namespace PlayerRocket { public enum RocketState { WAITFORSTART = 0, MOVE = 1, STOP = 2, COMPLETESTOP = 3, } [RequireComponent(typeof(Rigidbody))] public abstract class PlayerRocketBase : MonoBehaviour, IUseForces, IPlayerHelper { [SerializeField] protected RocketConfig config; protected Rigidbody rigidbody; protected InputController inputController; protected RocketHolder rocketHolder; protected RocketState rocketState; public Transform Transform => transform; public Rigidbody RigidbodyForForce => rigidbody; RocketState IPlayerHelper.RocketState => rocketState; protected ForceModel<IUseForces> forceModel; protected virtual void Awake() { Injections(); EventAggregator.AddListener<ButtonStartPressed>(this, StartEventReact); EventAggregator.AddListener<EndGameEvent>(this, EndGameReact); EventAggregator.AddListener<CollideWithPlanetEvent>(this, DestroyRocket); rigidbody = GetComponent<Rigidbody>(); Assert.IsNotNull(rigidbody, "    " + gameObject.name); forceModel = new ForceModel<IUseForces>(this); } protected virtual void Start() { Injections(); } private void DestroyRocket(CollideWithPlanetEvent obj) { Destroy(gameObject); } private void EndGameReact(EndGameEvent obj) { Debug.Log("     "); rocketState = RocketState.STOP; } private void Injections() { EventAggregator.Invoke(new InjectEvent<InputController> { inject = (InputController obj) => inputController = obj}); EventAggregator.Invoke(new InjectEvent<RocketHolder> { inject = (RocketHolder holder) => rocketHolder = holder }); } protected abstract void StartEventReact(ButtonStartPressed buttonStartPressed); } public interface IPlayerHelper { Transform Transform { get; } RocketState RocketState { get; } } } 


دعنا نذهب فوق الصف:

  [RequireComponent(typeof(Rigidbody))] public abstract class PlayerRocketBase : MonoBehaviour, IUseForces, IPlayerHelper 

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

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

 [SerializeField] protected RocketConfig config; 

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

RocketConfig
 using UnityEngine; namespace PlayerRocket { [CreateAssetMenu(fileName = "RocketConfig", menuName = "Configs/RocketConfigs", order = 1)] public class RocketConfig : ScriptableObject { [SerializeField] private float speed; [SerializeField] private float fuel; public float Speed => speed; public float Fuel => fuel; } } 


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

 protected ForceModel<IUseForces> forceModel; 

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

ForceModel
 using System.Collections.Generic; using System.Linq; using UnityEngine; public enum TypeOfForce { Push = 0, AddSpeed = 1, } public class ForceModel<T> where T : IUseForces { readonly private T forceUser; private List<SpaceForces> forces = new List<SpaceForces>(); protected bool IsHaveAdditionalForces; public ForceModel(T user) { GlobalEventAggregator.EventAggregator.AddListener<SpaceForces>(this, ChangeModificatorsList); forceUser = user; } private void ChangeModificatorsList(SpaceForces obj) { if (obj.IsAdded) forces.Add(obj); else forces.Remove(forces.FirstOrDefault(x => x.CenterOfObject == obj.CenterOfObject)); if (forces.Count > 0) IsHaveAdditionalForces = true; else IsHaveAdditionalForces = false; } public void AddModificator() { if (!IsHaveAdditionalForces) return; foreach (var f in forces) { switch (f.TypeOfForce) { case TypeOfForce.Push: AddDirectionForce(f); break; case TypeOfForce.AddSpeed: forceUser.RigidbodyForForce.AddRelativeForce(Vector3.up*f.Force); break; } } } private void AddDirectionForce(SpaceForces spaceForces) { //Debug.Log(""); //var t = AngleDir(forceUser.TransformForForce.position, spaceForces.CenterOfObject); forceUser.RigidbodyForForce.AddForce(Push(spaceForces)); } private Vector3 Push(SpaceForces spaceForces) { var dist = Vector2.Distance(forceUser.Transform.position, spaceForces.CenterOfObject); var coeff = 1 - (spaceForces.ColliderBound / dist); if (forceUser.Transform.position.x > spaceForces.CenterOfObject.x) return (Vector3.right * spaceForces.Force) * coeff; else return (-Vector3.right * spaceForces.Force) * coeff; } public static float AngleDir(Vector2 A, Vector2 B) { return -Ax * By + Ay * Bx; } } public interface IUseForces { Transform Transform { get; } Rigidbody RigidbodyForForce { get; } } public struct SpaceForces { public TypeOfForce TypeOfForce; public Vector3 CenterOfObject; public Vector3 Direction; public float Force; public float ColliderBound; public bool IsAdded; } 


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

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

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


All Articles