نمذجة لعبة محمولة ، ومن أين تبدأ ، وكيفية القيام بذلك. الجزء 3 (النهائي)

في الجزء الأول ، ناقشت لماذا النماذج الأولية وبصفة عامة ، من أين نبدأ
- الجزء 1

في الجزء الثاني ، تشغيل القليل من خلال الفئات الرئيسية
والهندسة المعمارية - الجزء 2

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

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

لذلك vidosik ، تحتاج إلى إلقاء نظرة على النصوص مرتبة حسب المستوى ، وليس في اللعب):



استطراد صغير


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

لكنني أرغب في النشر للهيكل والبناء فيما يتعلق بالنماذج الأولية:
  1. بغض النظر عن مدى روعتك ، لا يمكنك أن تتوقع فقط أنه لن يتم رهنه ، ويمكن أن يكون هناك رهن كبير لما هو غير مطلوب. لذلك ، في أي حال ، سوف تكون إعادة البناء أو التوسع ضرورية. إذا لم تتمكن من وصف الفوائد المحددة للرمز / النهج ، فمن الأفضل عدم قضاء الكثير من الوقت على هذا الرمز.
  2. لماذا في نظري OOP / Event Model / Composition أسهل للنماذج الأولية من ECS و Unity COOP و DI FrameWorks والأطر التفاعلية ، إلخ. أقل خربشة ، جميع الاتصالات مرئية في الشفرة ، لأن المهمة الرئيسية للنموذج الأولي هي الإجابة على السؤال الرئيسي - هل من الممكن تشغيله ، وعدد من الاتصالات الثانوية - الأفضل للعب ، هذا أو ذاك. لذلك ، تحتاج إلى تنفيذ الوظيفة الضرورية في أسرع وقت ممكن. لماذا تقديم إطار عمل في مشروع صغير ، يصف كل غير المرغوب فيه من أجل تنفيذ ثلاثة كيانات لعبة؟ كل منها في الواقع فئة من 50-100 خطوط. يجب التفكير في البنية كجزء من مهام النموذج الأولي ، وكجزء من امتداد ممكن لألفا ، لكن الثانية مطلوبة في الرأس أكثر من الشفرة ، حتى لا تحترق عند إضافة رمز


حول المعدلات:


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

فئة المعدل
public class PushSideModificator : MonoBehaviour { [SerializeField] TypeOfForce typeOfForce = TypeOfForce.Push; [SerializeField] private float force; [SerializeField] DropPanelConfig dropPanelConfig; private float boundsOfCollider; private void OnTriggerEnter(Collider other) { boundsOfCollider = other.bounds.extents.x; GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { TypeOfForce = typeOfForce, Force = force, ColliderBound = boundsOfCollider, CenterOfObject = transform.position, IsAdded = true }); } private void OnTriggerExit(Collider other) { GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { CenterOfObject = transform.position, IsAdded = false }); } } 


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

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


أساس اللعب


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



فئة لوحة
  [RequireComponent (typeof(CanvasGroup))] public class DragAndDropModifiersPanel : MonoBehaviour { [SerializeField] private DropModifiersIcon iconPrfb; [SerializeField] private DropPanelConfig config; private CanvasGroup canvasGroup; private void Awake() { GlobalEventAggregator.EventAggregator.AddListener<ButtonStartPressed>(this, RocketStarted); canvasGroup = GetComponent<CanvasGroup>(); } private void RocketStarted(ButtonStartPressed obj) { canvasGroup.DOFade(0, 1); (canvasGroup.transform as RectTransform).DOAnchorPosX(100, 1); } private void Start() { for (var x = 0; x< 3; x++) { var mod = config.GetModifierByType(TypeOfForce.Push); var go = Instantiate(iconPrfb, transform); go.Init(mod); } for (var x = 0; x< 1; x++) { var mod = config.GetModifierByType(TypeOfForce.AddSpeed); var go = Instantiate(iconPrfb, transform); go.Init(mod); } } } 



توقع الأسئلة:
 for (var x = 0; x< 3; x++) for (var x = 0; x< 1; x++) 


3 و 1 - ما يسمى الأرقام السحرية التي تؤخذ ببساطة من الرأس وإدراجها في الرمز ، وهذا ينبغي تجنبه ، ولكن لماذا هم هنا؟ لم يتم بعد تحديد مبدأ تشكيل اللوحة اليمنى ، وقد تقرر ببساطة اختبار النموذج الأولي مع العديد من المعدلات فقط على النموذج الأولي.

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

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

النظر في فئة التكوين:
فئة التكوين
  [CreateAssetMenu(fileName = "DropModifiersPanel", menuName = "Configs/DropModifier", order = 2)] public class DropPanelConfig : ScriptableObject { [SerializeField] private ModifierBluePrintSimple[] modifierBluePrintSimples; public DropModifier GetModifierByType(TypeOfForce typeOfModifiers) { return modifierBluePrintSimples.FirstOrDefault(x => x.GetValue.TypeOfModifier == typeOfModifiers).GetValue; } } [System.Serializable] public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; } 



ما هو جوهر عمله؟ يقوم بتخزين فئة بيانات معدّل مخصصة.
  public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; } 


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

العمل مع config بسيط للغاية - ننتقل إلى config for data لنوع معين ، حيث نحصل على هذه البيانات والإعدادات المرئية والإعدادات الممكنة من هذه البيانات.

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

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

من الأيقونة إلى الملعب


فئة أيقونة معدل
 public class DropModifiersIcon : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler { [SerializeField] private Image icon; [Header("       ")] [SerializeField] private RectTransform canvas; private CanvasGroup canvasGroup; private DropModifier currentModifier; private Vector3 startPoint; private Vector3 outV3; private GameObject currentDraggedObj; private void Start() { canvasGroup = GetComponent<CanvasGroup>(); startPoint = transform.position; canvas = GetComponentInParent<Canvas>().transform as RectTransform; } public void Init(DropModifier dropModifier) { icon.sprite = dropModifier.Icon; currentModifier = dropModifier; } public void OnBeginDrag(PointerEventData eventData) { BlockRaycast(false); currentDraggedObj = Instantiate(currentModifier.Modifier, WorldSpaceCoord(), Quaternion.identity); GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = true }); } private void BlockRaycast(bool state) { canvasGroup.blocksRaycasts = state; } public void OnDrag(PointerEventData eventData) { Vector2 outV2; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, Input.mousePosition, null, out outV2); transform.position = canvas.transform.TransformPoint(outV2); if (currentDraggedObj != null) currentDraggedObj.transform.position = WorldSpaceCoord(); } private Vector3 WorldSpaceCoord() { RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas, Input.mousePosition, Camera.main, out outV3); return outV3; } public void OnEndDrag(PointerEventData eventData) { GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = false }); if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) { Destroy(currentDraggedObj); transform.SetAsLastSibling(); canvasGroup.blocksRaycasts = true; } else Destroy(gameObject); } } public struct ImOnDragEvent { public bool IsDragging; } 



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

دعنا نقول أننا نغير رأينا حول وضع معدل وإعادته إلى اللوحة ،
  if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) 

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

نتيجة لذلك ، اتضح أننا إذا أطلقنا الأيقونة الموجودة أعلى اللوحة ، فسوف نعود إلى اللوحة ، فإذا بقي المُعدل في الملعب أعلى الملعب ، فسيتم حذف الأيقونة.

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

الاستنتاجات والتوصيات


  1. وضع الهندسة المعمارية الحد الأدنى ، ولكن مع ذلك الهندسة المعمارية
  2. اتبع المبادئ الأساسية ، ولكن بدون تعصب)
  3. اختيار حلول بسيطة
  4. بين التنوع والسرعة - من الأفضل اختيار سرعة للنموذج الأولي
  5. للمشاريع الكبيرة / المتوسطة الحجم ، يعني أنه من الأفضل إعادة كتابة المشروع من نقطة الصفر. على سبيل المثال ، الآن الاتجاه في Unity هو DOTS ، يجب عليك كتابة الكثير من المكونات والأنظمة ، إنها سيئة على المدى القصير ، وتضيع الوقت ، على المدى الطويل - عندما يتم تسجيل جميع المكونات والأنظمة ، يبدأ كسب الوقت. لا أظن أنه من الجيد قضاء الكثير من الوقت في اتجاه الهندسة المعمارية ومعرفة نوع النموذج الأولي

نماذج ناجحة للجميع.

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


All Articles