الوحدة مع MVC: كيفية رفع مستوى تطوير لعبتك
نشرت: 2022-03-11عادةً ما يبدأ المبرمجون لأول مرة في تعلم التجارة باستخدام برنامج Hello World
الكلاسيكي. من هناك ، لا بد أن تتبع المهام الأكبر والأكبر. كل تحد جديد يقودنا إلى درس مهم:
كلما زاد حجم المشروع ، زاد حجم السباغيتي.
قريبًا ، من السهل أن نرى أنه في الفرق الكبيرة أو الصغيرة ، لا يمكن للمرء أن يفعل بتهور كما يحلو له. يجب الحفاظ على الكود وقد يستمر لفترة طويلة. لا يمكن للشركات التي عملت لديها فقط البحث عن معلومات الاتصال الخاصة بك وسؤالك في كل مرة يريدون فيها إصلاح قاعدة التعليمات البرمجية أو تحسينها (وأنت لا تريدهم أيضًا).
هذا هو سبب وجود أنماط تصميم البرامج ؛ إنهم يفرضون قواعد بسيطة لإملاء الهيكل العام لمشروع البرمجيات. إنها تساعد مبرمجًا واحدًا أو أكثر على فصل الأجزاء الأساسية لمشروع كبير وتنظيمها بطريقة موحدة ، مما يزيل الالتباس عند مواجهة جزء غير مألوف من قاعدة التعليمات البرمجية.
تسمح هذه القواعد ، عند اتباعها من قبل الجميع ، بالحفاظ على التعليمات البرمجية القديمة والتنقل فيها بشكل أفضل ، وإضافة رمز جديد بسرعة أكبر. يتم قضاء وقت أقل في تخطيط منهجية التطوير. نظرًا لأن المشاكل لا تأتي في نكهة واحدة ، فلا يوجد نمط تصميم رصاصة فضية. يجب على المرء أن يفكر مليًا في نقاط القوة والضعف لكل نمط ، والعثور على أفضل ما يناسب التحدي الذي يواجهه.
في هذا البرنامج التعليمي ، سأربط تجربتي بمنصة تطوير ألعاب Unity الشهيرة ونمط Model-View-Controller (MVC) لتطوير اللعبة. خلال سبع سنوات من التطوير ، بعد أن تصارعت مع نصيبي العادل من لعبة dev spaghetti ، كنت أحقق بنية شفرة رائعة وسرعة تطوير باستخدام نمط التصميم هذا.
سأبدأ بشرح القليل من البنية الأساسية للوحدة ، نمط مكون الكيان. ثم سأنتقل لشرح كيف يتناسب MVC مع ذلك ، واستخدم مشروعًا وهميًا صغيرًا كمثال.
تحفيز
في أدبيات البرمجيات ، سنجد عددًا كبيرًا من أنماط التصميم. على الرغم من أن لديهم مجموعة من القواعد ، فعادة ما يقوم المطورون بقليل من الانحناء للقواعد من أجل تكييف النمط بشكل أفضل مع مشكلتهم المحددة.
"حرية البرمجة" هذه دليل على أننا لم نعثر بعد على طريقة واحدة ونهائية لتصميم البرامج. وبالتالي ، لا يُقصد بهذه المقالة أن تكون الحل النهائي لمشكلتك ، ولكن بدلاً من ذلك ، لإظهار فوائد وإمكانيات نمطين معروفين: Entity-Component و Model-View-Controller.
نمط مكون الكيان
عنصر الكيان (EC) هو نمط تصميم حيث نحدد أولاً التسلسل الهرمي للعناصر التي يتكون منها التطبيق (الكيانات) ، وبعد ذلك ، نحدد الميزات والبيانات التي سيحتوي كل منها (المكونات). في مصطلحات "مبرمج" أكثر ، يمكن أن يكون الكيان كائنًا به مصفوفة من 0 أو أكثر من المكونات. دعنا نصور كيانًا مثل هذا:
some-entity [component0, component1, ...]
إليك مثال بسيط لشجرة EC.
- app [Application] - game [Game] - player [KeyboardInput, Renderer] - enemies - spider [SpiderAI, Renderer] - ogre [OgreAI, Renderer] - ui [UI] - hud [HUD, MouseInput, Renderer] - pause-menu [PauseMenu, MouseInput, Renderer] - victory-modal [VictoryModal, MouseInput, Renderer] - defeat-modal [DefeatModal, MouseInput, Renderer]
EC هو نمط جيد للتخفيف من مشاكل الوراثة المتعددة ، حيث يمكن أن تقدم بنية فئة معقدة مشاكل مثل مشكلة الماس حيث يمكن للفئة D ، التي ترث فئتين ، B و C ، مع نفس الفئة الأساسية A ، تقديم تعارضات لأن كيف يعدل B و C ميزات A بشكل مختلف.
يمكن أن تكون هذه الأنواع من المشاكل شائعة في تطوير اللعبة حيث غالبًا ما تستخدم الوراثة على نطاق واسع.
من خلال تقسيم الميزات ومعالجات البيانات إلى مكونات أصغر ، يمكن إرفاقها وإعادة استخدامها في كيانات مختلفة دون الاعتماد على الموروثات المتعددة (والتي ، بالمناسبة ، ليست ميزة لـ C # أو Javascript ، اللغات الرئيسية المستخدمة بواسطة Unity ).
حيث ينقص عنصر الكيان
نظرًا لكونه أعلى من مستوى OOP ، فإن EC يساعد على إلغاء التجزئة وتنظيم بنية الكود بشكل أفضل. ومع ذلك ، في المشاريع الكبيرة ، ما زلنا "أحرارًا جدًا" ويمكننا أن نجد أنفسنا في "محيط مميز" ، ونواجه صعوبة في العثور على الكيانات والمكونات المناسبة ، أو معرفة كيفية تفاعلها. هناك طرق لا حصر لها لتجميع الكيانات والمكونات لمهمة معينة.
تتمثل إحدى طرق تجنب الفوضى في فرض بعض الإرشادات الإضافية أعلى مكون الكيان. على سبيل المثال ، إحدى الطرق التي أحب التفكير بها بشأن البرنامج هي تقسيمها إلى ثلاث فئات مختلفة:
- يتعامل البعض مع البيانات الأولية ، مما يسمح بإنشائها أو قراءتها أو تحديثها أو حذفها أو البحث عنها (أي مفهوم CRUD).
- يقوم الآخرون بتنفيذ الواجهة لعناصر أخرى للتفاعل معها ، ويكتشفون الأحداث المتعلقة بنطاقهم ويطلقون الإشعارات عند حدوثها.
- أخيرًا ، تكون بعض العناصر مسؤولة عن تلقي هذه الإخطارات ، واتخاذ قرارات منطق الأعمال ، وتحديد كيفية معالجة البيانات.
لحسن الحظ ، لدينا بالفعل نمط يتصرف بهذه الطريقة بالضبط.
نموذج وحدة التحكم في العرض (MVC)
يقسم نمط وحدة التحكم في عرض النموذج (MVC) البرنامج إلى ثلاثة مكونات رئيسية: النماذج (البيانات CRUD) ، وجهات النظر (الواجهة / الكشف) وأجهزة التحكم (القرار / الإجراء). MVC مرن بدرجة كافية ليتم تنفيذه حتى في أعلى ECS أو OOP.
يتمتع تطوير اللعبة وواجهة المستخدم بسير العمل المعتاد في انتظار مدخلات المستخدم ، أو أي حالة تشغيل أخرى ، وإرسال إشعار بهذه الأحداث في مكان مناسب ، وتحديد ما يجب فعله استجابةً ، وتحديث البيانات وفقًا لذلك. تظهر هذه الإجراءات بوضوح توافق هذه التطبيقات مع MVC.
تقدم هذه المنهجية طبقة تجريد أخرى تساعد في تخطيط البرامج ، وتسمح أيضًا للمبرمجين الجدد بالتنقل حتى في قاعدة بيانات أكبر. من خلال تقسيم عملية التفكير إلى بيانات وواجهة وقرارات ، يمكن للمطورين تقليل عدد ملفات المصدر التي يجب البحث عنها من أجل إضافة أو إصلاح الوظائف.
الوحدة والمفوضية الأوروبية
دعونا أولاً نلقي نظرة فاحصة على ما تقدمه لنا الوحدة مقدمًا.
Unity عبارة عن منصة تطوير قائمة على EC ، حيث تكون جميع الكيانات عبارة عن أمثلة لـ GameObject
ويتم توفير الميزات التي تجعلها "مرئية" و "متحركة" و "قابلة للتفاعل" وما إلى ذلك ، من خلال الفئات الممتدة Component
.
توفر لوحة التسلسل الهرمي ولوحة المفتش لمحرر Unity طريقة قوية لتجميع التطبيق الخاص بك ، وإرفاق المكونات ، وتكوين حالتها الأولية وإقلاع لعبتك باستخدام كود مصدر أقل بكثير مما هو معتاد.
ومع ذلك ، كما ناقشنا ، يمكننا مواجهة مشكلة "العديد من الميزات" ونجد أنفسنا في تسلسل هرمي ضخم ، مع ميزات منتشرة في كل مكان ، مما يجعل حياة المطور أكثر صعوبة.
بالتفكير بطريقة MVC ، يمكننا ، بدلاً من ذلك ، البدء بتقسيم الأشياء وفقًا لوظيفتها ، وتنظيم تطبيقنا مثل المثال أدناه:
تكييف MVC مع بيئة تطوير الألعاب
الآن ، أود أن أقدم تعديلين صغيرين على نمط MVC العام ، مما يساعد على تكييفه مع المواقف الفريدة التي صادفتها في بناء مشاريع Unity باستخدام MVC:
- تنتشر مراجع فئة MVC في جميع أنحاء الكود بسهولة. - داخل الوحدة ، يجب على المطورين عادةً سحب المثيلات وإفلاتها لتسهيل الوصول إليها ، أو الوصول إليها من خلال عبارات البحث المرهقة مثل
GetComponent( ... )
. - سيحدث جحيم المرجع المفقود في حالة تعطل الوحدة أو جعل بعض الأخطاء جميع المراجع المسحوبة تختفي. - هذا يجعل من الضروري وجود كائن مرجعي جذر واحد ، يمكن من خلاله الوصول إلى جميع الطبعات في التطبيق واستعادتها. - تتضمن بعض العناصر وظائف عامة يجب أن تكون قابلة لإعادة الاستخدام بشكل كبير ، والتي لا تندرج بشكل طبيعي في إحدى الفئات الرئيسية الثلاث للنموذج أو العرض أو وحدة التحكم. هذه التي أحب أن أسميها ببساطة المكونات . وهي أيضًا "مكونات" بمعنى مكون الكيان ولكنها تعمل فقط كمساعدين في إطار عمل MVC. - على سبيل المثال ، أحد مكونات
Rotator
، الذي يقوم فقط بتدوير الأشياء بسرعة زاوية معينة ولا يُعلم أو يخزن أو يقرر أي شيء.
للمساعدة في التخفيف من هاتين المشكلتين ، توصلت إلى نمط معدل أسميه AMVCC ، أو Application-Model-View-Controller-Component.
- التطبيق - نقطة دخول واحدة إلى التطبيق الخاص بك وحاوية لجميع المثيلات الهامة والبيانات المتعلقة بالتطبيق.
- MVC - يجب أن تعرف هذا الآن. :)
- المكون - برنامج نصي صغير ومضمون جيدًا يمكن إعادة استخدامه.
لقد استوفى هذان التعديلان احتياجاتي لجميع المشاريع التي استخدمتها فيها.
مثال: 10 ارتداد
كمثال بسيط ، دعنا نلقي نظرة على لعبة صغيرة تسمى 10 Bounce ، حيث سأستفيد من العناصر الأساسية لنمط AMVCC.
إعداد اللعبة بسيط: Ball
مع SphereCollider
و Rigidbody
(والتي ستبدأ في السقوط بعد "Play") ، Cube
كأرضي و 5 نصوص لتكوين AMVCC.
التسلسل الهرمي
قبل البرمجة النصية ، عادةً ما أبدأ في التسلسل الهرمي وأقوم بإنشاء مخطط تفصيلي لفصلي وأصول. اتبع دائمًا أسلوب AMVCC الجديد هذا.
كما نرى ، فإن view
GameObject تحتوي على جميع العناصر المرئية وأيضًا العناصر التي تحتوي على نصوص View
أخرى. عادةً ما تحتوي كائنات GameObjects model
ووحدة controller
، للمشاريع الصغيرة ، على البرامج النصية الخاصة بها فقط. بالنسبة للمشاريع الأكبر ، سوف تحتوي على GameObjects بنصوص أكثر تحديدًا.
عندما يريد شخص ما يتنقل في مشروعك الوصول إلى:
- البيانات: انتقل إلى
application > model > ...
- المنطق / سير العمل: انتقل إلى
application > controller > ...
- التقديم / الواجهة / الكشف: انتقل إلى
application > view > ...
إذا اتبعت جميع الفرق هذه القواعد البسيطة ، فلا ينبغي أن تصبح المشاريع القديمة مشكلة.
لاحظ أنه لا توجد حاوية Component
لأنها ، كما ناقشنا ، أكثر مرونة ويمكن ربطها بعناصر مختلفة في أوقات فراغ المطور.
البرمجة
ملاحظة: البرامج النصية الموضحة أدناه هي إصدارات مجردة من تطبيقات العالم الحقيقي. التنفيذ التفصيلي لن يفيد القارئ كثيرًا. ومع ذلك ، إذا كنت ترغب في استكشاف المزيد ، فإليك الرابط إلى إطار عمل MVC الشخصي الخاص بي من أجل Unity ، Unity MVC. ستجد الفئات الأساسية التي تنفذ إطار عمل AMVCC الهيكلي اللازم لمعظم التطبيقات.
دعنا نلقي نظرة على بنية البرامج النصية لـ 10 Bounce .
قبل البدء ، بالنسبة لأولئك الذين ليسوا على دراية بسير عمل Unity ، دعنا نوضح بإيجاز كيف تعمل البرامج النصية وكائنات GameObjects معًا. في الوحدة ، يتم تمثيل "المكونات" بمعنى مكون الكيان بواسطة فئة MonoBehaviour
. لكي يتواجد أحدها أثناء وقت التشغيل ، يجب على المطور إما سحب ملفه المصدر وإفلاته في كائن GameObject (وهو "كيان" نمط مكون الكيان) أو استخدام الأمر AddComponent<YourMonobehaviour>()
. بعد ذلك ، سيتم إنشاء مثيل للبرنامج النصي ويكون جاهزًا للاستخدام أثناء التنفيذ.
للبدء ، نحدد فئة التطبيق ("A" في AMVCC) ، والتي ستكون الفئة الرئيسية التي تحتوي على مراجع لجميع عناصر اللعبة التي تم إنشاء مثيل لها. سننشئ أيضًا فئة أساسية مساعدة تسمى Element
، والتي تتيح لنا الوصول إلى مثيل التطبيق ومثيلات MVC التابعة له.
مع وضع ذلك في الاعتبار ، دعنا نحدد فئة Application
("A" في AMVCC) ، والتي سيكون لها مثيل فريد. داخله ، ستمنحنا ثلاثة متغيرات ، model
، view
، ووحدة controller
، نقاط وصول لجميع مثيلات MVC أثناء وقت التشغيل. يجب أن تكون هذه المتغيرات MonoBehaviour
مع مراجع public
للنصوص المطلوبة.
بعد ذلك ، سننشئ أيضًا فئة أساسية مساعدة تسمى Element
، والتي تتيح لنا الوصول إلى مثيل التطبيق. سيسمح هذا الوصول لكل فئة MVC بالوصول إلى بعضها البعض.
لاحظ أن كلا الفئتين MonoBehaviour
. إنها "مكونات" سيتم إرفاقها بـ "كيانات" GameObject.
// BounceApplication.cs // Base class for all elements in this application. public class BounceElement : MonoBehaviour { // Gives access to the application and all instances. public BounceApplication app { get { return GameObject.FindObjectOfType<BounceApplication>(); }} } // 10 Bounces Entry Point. public class BounceApplication : MonoBehaviour { // Reference to the root instances of the MVC. public BounceModel model; public BounceView view; public BounceController controller; // Init things here void Start() { } }
من BounceElement
يمكننا إنشاء فئات MVC الأساسية. عادةً ما تعمل البرامج النصية BounceModel
و BounceView
و BounceController
أكثر تخصصًا ، ولكن نظرًا لأن هذا مثال بسيط ، فإن العرض فقط سيكون له بنية متداخلة. يمكن عمل النموذج ووحدة التحكم في برنامج نصي واحد لكل منهما:

// BounceModel.cs // Contains all data related to the app. public class BounceModel : BounceElement { // Data public int bounces; public int winCondition; }
// BounceView .cs // Contains all views related to the app. public class BounceView : BounceElement { // Reference to the ball public BallView ball; }
// BallView.cs // Describes the Ball view and its features. public class BallView : BounceElement { // Only this is necessary. Physics is doing the rest of work. // Callback called upon collision. void OnCollisionEnter() { app.controller.OnBallGroundHit(); } }
// BounceController.cs // Controls the app workflow. public class BounceController : BounceElement { // Handles the ball hit event public void OnBallGroundHit() { app.model.bounces++; Debug.Log(“Bounce ”+app.model.bounce); if(app.model.bounces >= app.model.winCondition) { app.view.ball.enabled = false; app.view.ball.GetComponent<RigidBody>().isKinematic=true; // stops the ball OnGameComplete(); } } // Handles the win condition public void OnGameComplete() { Debug.Log(“Victory!!”); } }
مع إنشاء جميع البرامج النصية ، يمكننا المضي قدمًا في إرفاقها وتكوينها.
يجب أن يكون تخطيط التسلسل الهرمي كما يلي:
- application [BounceApplication] - model [BounceModel] - controller [BounceController] - view [BounceView] - ... - ball [BallView] - ...
باستخدام BounceModel
كمثال ، يمكننا أن نرى كيف يبدو في محرر Unity:
BounceModel
مع الحقول winCondition
bounces
مع تعيين جميع البرامج النصية وتشغيل اللعبة ، يجب أن نحصل على هذا الإخراج في لوحة وحدة التحكم .
إشعارات
كما هو موضح في المثال أعلاه ، عندما تصطدم الكرة بالأرض ، يؤدي عرضها إلى app.controller.OnBallGroundHit()
وهي طريقة. ليس من الخطأ ، بأي حال من الأحوال ، القيام بذلك مع جميع الإشعارات في التطبيق. ومع ذلك ، من خلال تجربتي ، فقد حققت نتائج أفضل باستخدام نظام إشعار بسيط مطبق في فئة تطبيق AMVCC.
لتنفيذ ذلك ، دعنا نقوم بتحديث تخطيط BounceApplication
ليكون:
// BounceApplication.cs class BounceApplication { // Iterates all Controllers and delegates the notification data // This method can easily be found because every class is “BounceElement” and has an “app” // instance. public void Notify(string p_event_path, Object p_target, params object[] p_data) { BounceController[] controller_list = GetAllControllers(); foreach(BounceController c in controller_list) { c.OnNotification(p_event_path,p_target,p_data); } } // Fetches all scene Controllers. public BounceController[] GetAllControllers() { /* ... */ } }
بعد ذلك ، نحتاج إلى برنامج نصي جديد حيث سيضيف جميع المطورين أسماء حدث الإشعار ، والتي يمكن إرسالها أثناء التنفيذ.
// BounceNotifications.cs // This class will give static access to the events strings. class BounceNotification { static public string BallHitGround = “ball.hit.ground”; static public string GameComplete = “game.complete”; /* ... */ static public string GameStart = “game.start”; static public string SceneLoad = “scene.load”; /* ... */ }
من السهل رؤية أنه ، بهذه الطريقة ، تم تحسين وضوح الكود لأن المطورين لا يحتاجون إلى البحث في جميع أجزاء الكود المصدري عن controller.OnSomethingComplexName
. OnSomethingComplexName من أجل فهم نوع الإجراءات التي يمكن أن تحدث أثناء التنفيذ. من خلال فحص ملف واحد فقط ، يمكن فهم السلوك العام للتطبيق.
الآن ، نحتاج فقط إلى تكييف BallView
و BounceController
للتعامل مع هذا النظام الجديد.
// BallView.cs // Describes the Ball view and its features. public class BallView : BounceElement { // Only this is necessary. Physics is doing the rest of work. // Callback called upon collision. void OnCollisionEnter() { app.Notify(BounceNotification.BallHitGround,this); } }
// BounceController.cs // Controls the app workflow. public class BounceController : BounceElement { // Handles the ball hit event public void OnNotification(string p_event_path,Object p_target,params object[] p_data) { switch(p_event_path) { case BounceNotification.BallHitGround: app.model.bounces++; Debug.Log(“Bounce ”+app.model.bounce); if(app.model.bounces >= app.model.winCondition) { app.view.ball.enabled = false; app.view.ball.GetComponent<RigidBody>().isKinematic=true; // stops the ball // Notify itself and other controllers possibly interested in the event app.Notify(BounceNotification.GameComplete,this); } break; case BounceNotification.GameComplete: Debug.Log(“Victory!!”); break; } } }
سيكون للمشاريع الأكبر الكثير من الإخطارات. لذلك ، لتجنب الحصول على هيكل كبير لحالة التبديل ، يُنصح بإنشاء وحدات تحكم مختلفة وجعلها تتعامل مع نطاقات إشعار مختلفة.
AMVCC في العالم الحقيقي
أظهر هذا المثال حالة استخدام بسيطة لنمط AMVCC. إن تعديل طريقة تفكيرك من حيث العناصر الثلاثة لـ MVC ، وتعلم تصور الكيانات كتسلسل هرمي منظم ، هي المهارات التي يجب صقلها.
في المشاريع الأكبر حجمًا ، سيواجه المطورون سيناريوهات وشكوكًا أكثر تعقيدًا حول ما إذا كان يجب أن يكون الشيء طريقة عرض أو وحدة تحكم ، أو إذا كان يجب فصل فئة معينة بشكل أكثر شمولاً في فصول أصغر.
قواعد الإبهام (بواسطة إدواردو)
لا يوجد أي "دليل عالمي لفرز MVC" في أي مكان. ولكن هناك بعض القواعد البسيطة التي أتبعها عادةً لمساعدتي في تحديد ما إذا كان يجب تحديد شيء ما كنموذج أو عرض أو وحدة تحكم وأيضًا عند تقسيم فصل دراسي معين إلى أجزاء أصغر.
عادة ، يحدث هذا بشكل طبيعي عندما أفكر في بنية البرنامج أو أثناء البرمجة النصية.
الفرز الطبقي
عارضات ازياء
- احتفظ ببيانات وحالة التطبيق الأساسية ، مثل
health
اللاعب أوammo
البندقية. - التسلسل و / أو إلغاء التسلسل و / أو التحويل بين الأنواع.
- تحميل / حفظ البيانات (محليًا أو على الويب).
- إخطار المراقبين بتقدم العمليات.
- قم بتخزين حالة اللعبة لجهاز الحالة المحدودة الخاصة باللعبة.
- لا تصل إلى المشاهدات مطلقًا.
الآراء
- يمكن الحصول على بيانات من النماذج لتقديم حالة اللعبة المُحدثة للمستخدم. على سبيل المثال ، يمكن لمشغل طريقة العرض
player.Run()
استخدامmodel.speed
داخليًا لإظهار قدرات اللاعب. - يجب ألا يغير النماذج أبدًا.
- تنفذ بدقة وظائف فئتها. علي سبيل المثال:
- يجب ألا يقوم برنامج
PlayerView
باكتشاف الإدخال أو تعديل حالة اللعبة. - يجب أن يكون العرض بمثابة صندوق أسود له واجهة ويبلغ عن الأحداث المهمة.
- لا يخزن البيانات الأساسية (مثل السرعة والصحة والحياة ...).
- يجب ألا يقوم برنامج
تحكم
- لا تقم بتخزين البيانات الأساسية.
- يمكن أحيانًا تصفية الإشعارات من طرق العرض غير المرغوب فيها.
- تحديث واستخدام بيانات النموذج.
- يدير سير عمل مشهد الوحدة.
التسلسل الهرمي للفئة
في هذه الحالة ، ليس هناك الكثير من الخطوات التي أتبعها. عادة ، أدرك أن بعض الفئات تحتاج إلى تقسيم عندما تبدأ المتغيرات في إظهار عدد كبير جدًا من "البادئات" ، أو أن العديد من المتغيرات من نفس العنصر تبدأ في الظهور (مثل فئات Player
في MMO أو أنواع Gun
في FPS).
على سبيل المثال ، Model
واحد يحتوي على بيانات المشغل سيكون به الكثير من playerDataA, playerDataB,...
أو Controller
تتعامل مع إشعارات المشغل سيكون لها OnPlayerDidA,OnPlayerDidB,...
نريد تقليل حجم البرنامج النصي والتخلص من بادئات player
و OnPlayer
.
اسمحوا لي أن أوضح استخدام فئة Model
لأنه من الأسهل فهم استخدام البيانات فقط.
أثناء البرمجة ، أبدأ عادةً بفئة Model
واحدة تحتوي على جميع بيانات اللعبة.
// Model.cs class Model { public float playerHealth; public int playerLives; public GameObject playerGunPrefabA; public int playerGunAmmoA; public GameObject playerGunPrefabB; public int playerGunAmmoB; // Ops Gun[CDE ...] will appear... /* ... */ public float gameSpeed; public int gameLevel; }
من السهل أن ترى أنه كلما كانت اللعبة أكثر تعقيدًا ، زادت المتغيرات العديدة. مع ما يكفي من التعقيد ، يمكن أن ننتهي بفئة عملاقة تحتوي على متغيرات model.playerABCDFoo
. ستعمل عناصر التداخل على تبسيط إكمال الكود وإعطاء مساحة للتبديل بين أشكال البيانات المختلفة.
// Model.cs class Model { public PlayerModel player; // Container of the Player data. public GameModel game; // Container of the Game data. }
// GameModel.cs class GameModel { public float speed; // Game running speed (influencing the difficulty) public int level; // Current game level/stage loaded }
// PlayerModel.cs class PlayerModel { public float health; // Player health from 0.0 to 1.0. public int lives; // Player “retry” count after he dies. public GunModel[] guns; // Now a Player can have an array of guns to switch ingame. }
// GunModel.cs class GunModel { public GunType type; // Enumeration of Gun types. public GameObject prefab; // Template of the 3D Asset of the weapon. public int ammo; // Current number of bullets public int clips; // Number of reloads possible }
باستخدام هذا التكوين للفئات ، يمكن للمطورين التنقل بشكل بديهي في مفهوم واحد في الكود المصدري في كل مرة. لنفترض لعبة مطلق النار من منظور شخص أول ، حيث يمكن أن تتعدد الأسلحة وتكويناتها حقًا. حقيقة أن GunModel
في فئة تسمح بإنشاء قائمة Prefabs
الجاهزة (كائنات اللعبة المكونة مسبقًا ليتم نسخها بسرعة وإعادة استخدامها داخل اللعبة) لكل فئة وتخزينها لاستخدامها لاحقًا.
في المقابل ، إذا تم تخزين معلومات البندقية معًا في فئة GunModel
الفردية ، في متغيرات مثل gun0Ammo
، و gun1Ammo
، و gun0Clips
، وما إلى ذلك ، فإن المستخدم ، عند مواجهة الحاجة إلى تخزين بيانات Gun
، سيحتاج إلى تخزين كامل Model
يتضمن بيانات Player
غير المرغوب فيها. في هذه الحالة ، سيكون من الواضح أن فئة GunModel
الجديدة ستكون أفضل.
كما هو الحال مع كل شيء ، هناك وجهان للعملة. في بعض الأحيان ، يمكن للمرء أن يزيد من تعقيد الكود ويزيد من تعقيده دون داع. الخبرة فقط هي التي يمكنها صقل مهاراتك بما يكفي للعثور على أفضل تصنيف لـ MVC لمشروعك.
خاتمة
هناك الكثير من أنماط البرامج بالخارج. في هذا المنشور ، حاولت إظهار الشخص الذي ساعدني أكثر في المشاريع السابقة. يجب على المطورين دائمًا استيعاب المعرفة الجديدة ولكن دائمًا يشككون فيها أيضًا. آمل أن يساعدك هذا البرنامج التعليمي على تعلم شيء جديد ، وفي نفس الوقت ، يكون بمثابة نقطة انطلاق أثناء تطوير أسلوبك الخاص.
أيضًا ، أشجعك حقًا على البحث عن أنماط أخرى والعثور على الأنماط التي تناسبك بشكل أفضل. نقطة انطلاق جيدة هي مقالة ويكيبيديا هذه بقائمة ممتازة من الأنماط وخصائصها.
إذا كنت تحب نمط AMVCC وترغب في اختباره ، فلا تنس تجربة مكتبتي ، Unity MVC ، التي تحتوي على جميع الفئات الأساسية اللازمة لبدء تطبيق AMVCC.
مزيد من القراءة على مدونة Toptal Engineering:
- تطوير الذكاء الاصطناعي للوحدة: برنامج تعليمي لآلة الحالة المحدودة