كيفية تطوير منصة أخرى باستخدام الوحدة. برنامج تعليمي آخر

مرحبا يا هبر!


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


تحتوي المقالة على نص كثير (عادي ومصدر) والعديد من الصور.


قبل أن أبدأ مقالتي الأولى ، دعنا نتعرف عليك. اسمي دينيس. أنا أعمل كمسؤول نظام مع خبرة إجمالية قدرها 7 سنوات. ليس لي أن أخبرك أن مسؤول النظام هو نوع من موظفي تكنولوجيا المعلومات الذين ينتشرون مرة واحدة بعناية ، ثم يفكرون في وميض الشخصيات المختلفة على الشاشة. مع مرور الوقت ، توصلت إلى استنتاج مفاده أن الوقت قد حان لتوسيع حدود المعرفة والتحول إلى البرمجة. دون الخوض في التفاصيل ، حاولت أن أقوم بأي مشاريع في C ++ و Python. ولكن بعد عام من الدراسة ، توصلت إلى استنتاج مفاده أن برمجة تطبيقي وبرمجيات النظام ليست لي. لأسباب مختلفة.


بعد التفكير بشكل أعمق ، سألت نفسي السؤال: ما الذي يعجبني حقًا القيام به مع أنواع مختلفة من معدات الحوسبة؟ طرحني سؤالي بنفسي بعيدًا في مرحلة الطفولة ، أي في الساعات السعيدة التي قضيها في PS1 ، و PS2 ، و Railroad Tycoon 3 للكمبيوتر الشخصي ... ، حسنًا ، أنت تفهم ذلك. لعبة فيديو!


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


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


منذ حوالي أسبوعين ، رأيت ترجمة لمقال على محور يسمى Level Design Patterns للألعاب ثنائية الأبعاد ( https://habr.com/en/post/456152/ ) وفكرت بنفسي - لم لا؟ تحتوي المقالة على جدول بسيط وواضح مع قائمة بما يجب أن يكون في اللعبة بحيث تكون مثيرة للاهتمام. تفضلت بنسخ الجدول بنفسي في OneNote وقمت بتمييز كل نمط مع علامة الحالات (التي يمكن تعليمها مكتملة).


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


سأبدأ برنامجي الطويل لبرمجة منصة أخرى.


الصورة الرمزية


كيان يسيطر عليه اللاعبون داخل اللعبة. على سبيل المثال ، Mario and Luigi في Super Mario Bros (Nintendo، 1985).


هناك العديد من المهام الفرعية التي يجب تنفيذها لإعطاء حياة البطل. وهي:


•   ( ) •      •      •       •        

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


صورة


انقر فوق تطبيق ، ثم SpriteEditor .


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


أيضًا ، توفر الوحدة القدرة على تمييز حدود الكائنات داخل العفريت تلقائيًا. للقيام بذلك ، في نافذة Sprite Editor ، يجب أن تنقر فوق الزر Slice . في القائمة المنسدلة ، يجب أن يكون لديك Type => Automatic ، Pivot => Center . كل ما عليك فعله هو النقر على زر الشريحة . بعد ذلك ، سيتم تحديد جميع الكائنات داخل العفريت تلقائيا.



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



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


يمكنك تلبية كلمة التحويل في معنيين مختلفين:


  • التحول هو فئة. نظرًا لأن هذه الفئة عبارة عن برنامج ، فإن Transform يصف تطبيق برنامج لما سيحدثه هذا الكائن وما هي أبعاده.
  • تحويل هو مثيل لفئة. وهذا يعني أنه يمكنك الرجوع إلى كائن معين وتغيير موضعه أو حجمه على الساحة. على سبيل المثال ، سيكون هناك سطر في الكود:
     transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY);. 


    سيكون هذا الخط مسؤولاً عن حركة Lucas على المسرح.

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



أولاً ، نصف الحقول التي ستخزن معلومات حول المكون المرئي والمادي لـ Lucas:


المفسد العنوان
 private Animator animator; //      . private Rigidbody2D rb2d; //rb     

بعد ذلك ، الحقول التي ستستجيب لحركة الشخصية:


المفسد العنوان
 /*  ,     */ Vector3 localScale; bool facingRight = true; [SerializeField] private float dirX, dirY; //    [Range(1f, 20f)] [SerializeField] private float moveSpeed; //    private SpriteRenderer sprite; //   SpriteRenderer /*   ,     */ 

لقد تم البدء ، ممتاز. بعد ذلك ، سيتم وصف التعداد وسيتم كتابة خاصية تكون مسؤولة عن تبديل حالة الرسوم المتحركة. يجب كتابة هذا التعداد خارج الفصل:


المفسد العنوان
 /* *      . *         *    . */ public enum CharState { idle, //   0 Run, //   1 Walk, //   2 Die //   3 } 

نقوم بتنفيذ خاصية ستتلقى وتعيين حالة حركة جديدة:


المفسد العنوان
 public CharState State { get {//    CharState    animator       int return (CharState)animator.GetInteger("State"); } set { //    animator     int    State       ,      int. animator.SetInteger("State", (int)value); } } 

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


للقيام بذلك ، يجب عليك ربط العفاريت التي تم إنشاؤها مسبقًا مع كائن لعبة فارغ. كل ما عليك القيام به هو تحديد الإطارات في Unity Explorer واسحبها إلى كائن لعبة فارغ ، والذي تم إصلاح البرنامج النصي له من قبل.


صورة


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


صورة


بعد ذلك ، يجب أن تشير صراحةً إلى ما ينبغي أن تكون عليه الولاية بالضبط من أجل بدء الرسوم المتحركة الضرورية. انتبه إلى لقطة الشاشة ، أي الجزء الأيسر منها. علامة التبويب المعلمات . يتم إنشاء متغير من النوع int State فيه. بعد ذلك ، انتبه إلى الجانب الأيمن. بادئ ذي بدء ، من الانتقال للرسوم المتحركة ، تحتاج إلى إلغاء تحديد خانة الاختيار " Can Transaction To Self" . ستوفر لك هذه العملية تحولات غريبة وأحيانًا غير مفهومة تمامًا للرسوم المتحركة لنفسها وقسم الشروط ، حيث أشرنا إلى أن انتقال الحركة هذا تم تعيينه على القيمة 3 لمتغير الحالة. بعد ذلك ، ستعرف الوحدة الرسوم المتحركة التي سيتم تشغيلها.
كل شيء يتم لحركة الشخصيات المتحركة. دعنا ننتقل.


والخطوة التالية هي لتعليم لوكاس التحرك على المسرح. هذا هو البرمجة تماما. لتحريك الشخصية حول مكان الحادث ، ستحتاج إلى أزرار ، بالنقر فوق Lucas التي ستذهب ذهابًا وإيابًا. للقيام بذلك ، في علامة تبويب Assets Store ، نحتاج إلى استيراد Standart Assets ، ولكن ليس كلها ، فقط بعض المكونات الإضافية ، وهي:


• CrossPlatformInput
• محرر
• البيئة


بعد استيراد الأصول ، يجب تعديل نافذة الوحدة الرئيسية وستظهر علامة تبويب إضافية واحدة. نحن تفعيلها.


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



إلى كل زر ، قم بإضافة مكون AxisTouchButton . يحتوي هذا البرنامج النصي على 4 حقول فقط. حقل اسم المحور يعني الاسم الذي يجب الرد عليه عند الاتصال. يعد حقل axisValue مسؤولاً عن الاتجاه الذي سيتحرك فيه Lucas. حقل responseSpeed مسؤول عن مدى سرعة تطوير Lucas لسرعته. يعد حقل returnToCentreSpeed مسؤولاً عن سرعة عودة الزر إلى المركز. للزر أمامي ، اتركه كما هو. للزر الخلفي ، قم بتغيير قيمة axisValue إلى -1 بحيث ينتقل Lucas إلى الخلف. لأزرار أعلى وأسفل ، قم بتغيير axisName إلى عمودي . بالنسبة لمحور الزر " أعلى" ، قم بتعيين القيمة إلى 1 ، لأسفل -1.


بعد ذلك ، قم بتعديل HeroScript.cs . إضافة مساحة الاسم إلى التوجيه باستخدام


 using UnityStandardAssets.CrossPlatformInput; //    . 

المفسد العنوان
        : /*  ,     */ Vector3 localScale; //   bool facingRight = true; [SerializeField] private float dirX, dirY; //    [Range(1f, 20f)] [SerializeField] private float moveSpeed; //    private SpriteRenderer sprite; //   SpriteRenderer /*   ,     */ 

في طريقة البدء القياسية ، أضف الكود التالي:


المفسد العنوان
  void Start() { localScale = transform.localScale; animator = GetComponent<Animator>(); //     . sprite = GetComponent<SpriteRenderer>(); //  SpriteRenderer rb = GetComponent<Rigidbody2D>(); State = CharState.idle; } 

نخلق طريقة ستكون مسؤولة عن تحريك البطل:


المفسد العنوان
 public void MoveHero() { dirX = CrossPlatformInputManager.GetAxis ("Horizontal") * moveSpeed * Time.deltaTime; dirY = CrossPlatformInputManager.GetAxis ("Vertical") * moveSpeed * Time.deltaTime; transform.position = new Vector2 (transform.position.x + dirX, transform.position.y + dirY); } 

كما ترون ، كل شيء بسيط. يسجل حقلي dirx و DirY معلومات حول اتجاه المحور ( أفقي ورأسي ) مضروبًا بالسرعة (التي ستحتاج إلى تحديد في المحرر) وضربها في وقت السفر من الإطار الأخير.
convert.position يكتب الموضع الجديد لمكون Transform من كائن اللعبة لدينا.


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


المفسد العنوان
 void CheckWhereToFace () { if (dirX > 0) { facingRight = true; State = CharState.Walk; } if (dirX < 0) { facingRight = false; State = CharState.Walk; } if (dirX == 0) { State = CharState.idle; } if (dirY < 0) { State = CharState.Walk; } if (dirY > 0) { State = CharState.Walk; } if (((facingRight) && (localScale.x < 0)) || ((!facingRight) && (localScale.x > 0))) localScale.x *= -1; transform.localScale = localScale; 

هذا الجزء من الكود أيضًا ليس صعبًا. تصف الطريقة أنه إذا كان dirX > 0 (إذا ذهبنا إلى اليمين) ، فإننا ندير العفريت في هذا الاتجاه ونبدأ الرسوم المتحركة للمشي. إذا كان أقل من 0 ، فقم بتدوير Lucas 180 درجة وابدأ الرسوم المتحركة. إذا كان dirX يساوي الصفر ، فحينئذٍ يقف Lucas وتحتاج إلى بدء حركة الانتظار.


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


نحن نضع وظيفة CheckWhereToFace () في وظيفة Update () ، من أجل مراقبة كل إطار على حدة.


ممتاز. يتم الانتهاء من أول 2 نقاط من 5. دعنا ننتقل إلى احتياجات لوكاس. دعنا نقول أن لوكاس سيكون لديها 3 أنواع من الاحتياجات التي يجب الوفاء بها من أجل البقاء على قيد الحياة. هذا هو مستوى المعيشة ، ومستوى الجوع ومستوى العطش. تحتاج إلى إنشاء لوحة بسيطة ومفهومة مع مؤشر لكل عنصر. لإنشاء مثل هذه اللوحة ، انقر بزر الماوس الأيمن وحدد UI => اللوحة .


دعونا بمناسبة ذلك تقريبا كما هو موضح أدناه


صورة


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


صورة


صورة


تحديد متغيرات ثابتة جديدة:


المفسد العنوان
 /*  ,     */ [SerializeField] public static float Health = 100, Eat = 100, Water = 100, _Eat = 0.05f, _Water = 0.1f; //        .    _  . /*   ,     */ /*  ,      */ [SerializeField] Image iHealt, iEat, iWater; //      /*   ,      */ 

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


المفسد العنوان
 private float fEat(float x) { Eat = Eat - x * Time.deltaTime; iEat.fillAmount = Eat / 100f; // ,        return Eat; } private float fWater(float x) { Water = Water - x * Time.deltaTime; iWater.fillAmount = Water / 100; return Water; } 

ثم نكتب طريقة تجمع المعلومات حول الرغبة في تناول الطعام والشراب:


المفسد العنوان
 private void Needs() { if (fEat(_Eat) < 0) { Debug.Log(Eat); } else if (fEat(0) == 0) { StartCoroutine(ifDie()); } if (fWater(_Water) < 0) { Debug.Log(Water); } else if (fWater(0) == 0) { StartCoroutine(ifDie()); } 

يتم وضع الدالة Needs () في وظيفة Update () ويتم استدعاء كل إطار. وفقا لذلك ، في السطور


 if (fEat(_Eat) < 0) 

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


 StartCoroutine(ifDie()); 

الذي يبدأ الرسوم المتحركة الموت وإعادة تشغيل المستوى:


المفسد العنوان
 IEnumerator ifDie() { State = CharState.Die; yield return new WaitForSeconds(2); SceneManager.LoadScene("WoodDay", LoadSceneMode.Single); } 

البلاط الصلب


كائن لعبة لا يسمح للاعب بالمرور عبره. مثال: الجنس في Super Mario Bros (نينتندو ، 1985).


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



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


صورة


عرض


جزء من مستوى / عالم اللعبة المرئي حاليًا للاعب.


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


المفسد العنوان
 public GameObject objectToFollow; public float speed = 2.0f; void Update () { CamFoll(); } private void CamFoll() { float interpolation = speed * Time.deltaTime; Vector3 position = this.transform.position; position.y = Mathf.Lerp(this.transform.position.y, objectToFollow.transform.position.y, interpolation); position.x = Mathf.Lerp(this.transform.position.x, objectToFollow.transform.position.x, interpolation); this.transform.position = position; } 

في حقل objectToFollow من نوع GameObject ، سيتم تعيين كائن ليتم مراقبته ، وفي مجال السرعة ، السرعة التي من الضروري التحرك فيها بسلاسة خلف لعبة GameObject المعينة.


يتم تسجيل المعلومات حول سرعة الحركة منذ الإطار الأخير في مجال الاستيفاء. بعد ذلك ، سيتم استخدام طريقة Lerp ، والتي ستضمن حركة سلسة للكاميرا خلف Lucas عندما تتحرك على طول محوري X و U. لسوء الحظ ، لا أستطيع شرح تشغيل الخط


 position.y = Mathf.Lerp(this.transform.position.y, objectToFollow.transform.position.y, interpolation); 

من حيث الرياضيات. لذلك ، سأقول أبسط - هذه الطريقة ستمدد وقت تنفيذ أي إجراء. في حالتنا ، هذه هي حركة الكاميرا وراء الكائن.


خطر


المفسد العنوان

الكيانات التي تمنع اللاعب من إكمال مهمته. مثال: المسامير من 1001 Spikes (Nicalis و 8 bits Fanatics ، 2014).


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


إنشاء GameObject فارغة وتوصيل مكونات SpriteRenderer و PolygonCollider2D به. في مكون SpriteRenderer ، نقوم بتوصيل شبح الزر الشائك أو أي كائن آخر حسب الرغبة. أيضا ، تعيين العلامة = شوكة لارتفاع.


بعد ذلك ، على Lucas GameObject ، نقوم بإنشاء برنامج نصي سيكون مسؤولاً عما سيحدث له إذا تصادم Lucas مع مصادمات أخرى. في حالتي ، دعوتها ColliderReaction.cs


المفسد العنوان
 private Rigidbody2D rb2d; void Start() { rb2d = GetComponent<Rigidbody2D>(); } public void OnTriggerEnter2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Thorn": { rb2d.AddForce(transform.up * 4, ForceMode2D.Impulse); HeroScript.Health = HeroScript.Health - 5; } break; } } 

جوهر النص بسيط مثل 2x2. عندما تصطدم علامة Thorn بكائن اللعبة ، فإن بيان Switch يقارن مع المرشحين الذين حددناهم. في حالتنا ، في الوقت الحالي ، إنه ثورن . أولاً ، يتم طرح Lucas ، ثم ننتقل إلى متغير ثابت ونأخذ 5 وحدات حياة من Lucas. في المستقبل ، أستطيع أن أقول أنه من المنطقي أن أصف نفس الشيء عن الصراع مع الأعداء:


المفسد العنوان
 case "Enemy": { rb2d.AddForce(transform.up * 2, ForceMode2D.Impulse); HeroScript.Health = HeroScript.Health - 10; } break; 

بعد ذلك ، أقترح قتل عصفورين بحجر واحد.


جمعت البند والقاعدة.


كائن لعبة يمكن للاعبين التقاطه.


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


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


المفسد العنوان
 [SerializeField] private Transform inst; //     [SerializeField] private GameObject FireWoodPref; //   [SerializeField] private int fireWood; //        

عندما يبدأ المستوى ، نكتب قيمة عشوائية في fireWood :


المفسد العنوان
 void Awake() { fireWood = Random.Range(4,10); } 

يصف طريقة ذات معلمة ستكون مسؤولة عن عدد السجلات التي ستقع في ضربة واحدة:


المفسد العنوان
 public int fireWoodCounter(int x) { for (int i = 0; i < fireWood; i++) { fireWood = fireWood - x; InstantiateFireWood(); } return fireWood; } 

وهناك طريقة من شأنها أن تخلق استنساخ السجل على خشبة المسرح.
الفراغ الخاص InstantiateFireWood ():


المفسد العنوان
  { Instantiate(FireWoodPref, inst.position, inst.rotation); } 


لنقم بإنشاء سجل وتوصيل نص برمجي بالترميز التالي:


المفسد العنوان
 public void OnTriggerEnter2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Player": { if (InventoryOnHero.woodCount > 10) { Debug.Log("   !"); } else { InventoryOnHero.woodCount = InventoryOnHero.woodCount + 1; Destroy(this.gameObject); } } break; } } 

بعد ذلك ، سنقوم أيضًا بإنشاء فصل يكون مسؤولاً عن المخزون.


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


لإنشاء جسر ، نحن بحاجة إلى 2 الجاهزة مع النصف الأيسر والأيمن من الجسر. BoxCollider2D . , , - , .


:


المفسد العنوان
 [SerializeField] private Transform inst1, inst2; //        [SerializeField] private GameObject bridgePref1, bridgePref2; //   [SerializeField] private int BridgeCount; //   ,   .    

:


المفسد العنوان
 public void BuildBridge() { if (InventoryOnHero.woodCount == 0) { Debug.LogWarning (" !"); } if (InventoryOnHero.woodCount > 0) { BridgeCount = BridgeCount - 1; InventoryOnHero.woodCount = InventoryOnHero.woodCount - 1; } switch (BridgeCount) { case 5: Inst1(); break; case 0: Inst2(); break; default: Debug.LogWarning("-      "); break; } } 

, , . , 10 , 12 8.


, , , , . , 1 , 1 . , 5, , . 0, . , .


.


, ColliderReaction.cs :


المفسد العنوان
 void OnTriggerStay2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Ladder": { rb2d.gravityScale = 0; } break; } } void OnTriggerExit2D(Collider2D collision) { switch (collision.gameObject.tag) { case "Ladder": { rb2d.gravityScale = 1; } break; } } 

OnTriggerStay2D , . , 0. , . OnTriggerExit2D , .



, .


19 , . , , , , , .


GO, SpriteRenderer , BoxCollider2D , Rigidbody2D . , — , . , ru.stackoverflow.com.


صورة


Trees .


, . , -, , Raycast 2 (4 ). , , , ( ). ( ), . , . , , . , . , , ( , ).


, . , - .


:


المفسد العنوان
 [SerializeField] private GameObject area; private bool m1 = true, m2; // m  move private void fGreenMonster() { float dist = Vector3.Distance(greenMonster.transform.position, area.transform.position); Debug.Log(dist); if (m1) { if (dist < 3f) { transform.position += new Vector3(speed,0,0) * Time.deltaTime; SR.flipX = true; } else { m1 = false; m2 = true; } } if (m2) { if(dist >= 1f) { transform.position += new Vector3(-speed,0,0) * Time.deltaTime; SR.flipX = false; } else { m2 = false; m1 = true; } } } 

Update() , . , 3 , . 3, , .


صورة


, .


المفسد العنوان
 private void fSunFlower() { canBullet = canBullet - minus * Time.deltaTime; if (canBullet <= 0 && SR.flipX == false) { GameObject newArrow = Instantiate(sunFlowerBullet) as GameObject; newArrow.transform.position = transform.position; Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); rb.velocity = sunFlowerTrans.transform.forward * -sunFlowerBulletSpeed; canBullet = 2; } if (canBullet <= 0 && SR.flipX == true) { GameObject newArrow = Instantiate(sunFlowerBullet) as GameObject; newArrow.transform.position = transform.position; Rigidbody2D rb = newArrow.GetComponent<Rigidbody2D>(); rb.velocity = sunFlowerTrans.transform.forward * sunFlowerBulletSpeed; canBullet = 2; } 

 canBullet = canBullet - minus * Time.deltaTime; 

, .


المفسد العنوان
 if (canBullet <= 0 && SR.flipX == false) { GameObject newArrow = Instantiate(sunFlowerBullet) } 

, , , , :


المفسد العنوان
 public int Damage(int x) { Health = Health - x; return Health; } 

, , :


المفسد العنوان
 public void ifDie() { if (Damage(0) <= 0) { Destroy(this.gameObject); } } 

0, .


, :


المفسد العنوان
 if (bGreenMonster) { fGreenMonster(); } if (bSunFlower) { fSunFlower(); } 

, .


صورة


.


, ?


, .


:



:


المفسد العنوان
 [SerializeField] private Transform Hero; //         [SerializeField] private float distWhatHeroSee; //   [SerializeField] private LayerMask Tree, BridgeBuild, LadderBuild ,drinkingWater, lEnemy; //   

, :


المفسد العنوان
 private void AttackBtn() { if (CrossPlatformInputManager.GetButtonDown("Attack")) { GameObject.Find("Hero").GetComponent<HeroScript>().State = CharState.AttackA; Collider2D[] Trees = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, Tree); for (int i = 0; i < Trees.Length; i++) { Trees[i].GetComponent<TreeControl>().fireWoodCounter(1); Debug.Log("Trees Collider"); HeroScript.Water = HeroScript.Water - 0.7f; } // BB  BridgeBuild Collider2D[] BB = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, BridgeBuild); for (int i = 0; i < BB.Length; i++) { BB[i].GetComponent<BridgeBuilding>().BuildBridge(); HeroScript.Water = HeroScript.Water - 0.17f; } 

 GameObject.Find("Hero").GetComponent<HeroScript>().State = CharState.AttackA; 

, .
, :


المفسد العنوان
 Collider2D[] Trees = Physics2D.OverlapCircleAll(Hero.position, distWhatHeroSee, Tree); for (int i = 0; i < Trees.Length; i++) { Trees[i].GetComponent<TreeControl>().fireWoodCounter(1); Debug.Log("Trees Collider"); HeroScript.Water = HeroScript.Water - 0.7f; } 

Trees , . , , , . .
, . Simple as that!


, - :


صورة


, — .
, . , , , .


2 , .


!


.


https://opengameart.org/ , :


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


All Articles