مطلق النار غيبوبة بسيطة على الوحدة

مرحبا بالجميع! قريباً ، ستبدأ الدروس في المجموعة الأولى من دورة Unity Games Developer . تحسبا لبداية الدورة ، تم عقد درس مفتوح حول إنشاء مطلق النار غيبوبة على الوحدة. استضاف الندوة نيكولاي زابولنوف ، مطور أول لعبة من Rovio Entertainment Corporation. كما كتب مقالة مفصلة ، والتي نلفت انتباهكم إليها.



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



إخلاء المسئولية رقم 1: هذه المقالة مخصصة للمبتدئين. إذا أكلت كلبًا في Unity ، فقد يبدو ذلك مملًا لك.

إخلاء المسئولية رقم 2: لقراءة هذا المقال ، تحتاج إلى معرفة برمجية أساسية على الأقل. كحد أدنى ، يجب أن لا تخيفك الكلمات "class" و "method".

الحذر ، وحركة المرور تحت خفض!

مقدمة في الوحدة


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

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

يمكنك كتابة المكونات بنفسك ، باستخدام لغة البرمجة C # لهذا الغرض. هذه هي الطريقة التي يتم بها كتابة منطق اللعبة. أدناه سنرى كيف يتم ذلك ، ولكن الآن دعونا نلقي نظرة على المحرك نفسه.

عند بدء تشغيل المحرك وإنشاء مشروع جديد ، سترى نافذة أمامك حيث يمكنك تحديد أربعة عناصر رئيسية:



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

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

في الجزء العلوي الأيمن توجد نافذة "المفتش". في هذه النافذة ، تعرض الوحدة معلمات الكائن المحدد ويمكننا تحريرها. على وجه الخصوص ، يمكننا أن نرى أن الكاميرا المحددة تحتوي على مكونين - "Transform" ، الذي يحدد موضع الكاميرا في عالم اللعبة ، وفي الواقع ، "الكاميرا" ، التي تنفذ وظائف الكاميرا.

بالمناسبة ، مكون Transform في شكل أو آخر في جميع كائنات اللعبة في Unity.

وأخيرًا ، يوجد في الأسفل علامة التبويب "المشروع" ، حيث يمكننا رؤية جميع الأصول المزعومة الموجودة في مشروعنا. الأصول هي ملفات بيانات مثل القوام ، العفاريت ، النماذج ثلاثية الأبعاد ، الرسوم المتحركة ، الأصوات والموسيقى ، ملفات التكوين. أي ، أي بيانات يمكننا استخدامها لإنشاء مستويات أو واجهة المستخدم. تتفهم Unity عددًا كبيرًا من التنسيقات القياسية (على سبيل المثال ، png و jpg للصور ، أو fbx للنماذج ثلاثية الأبعاد) ، لذلك لن تكون هناك مشكلات في تحميل البيانات في مشروع. وإذا كنت ، مثلي ، لا تعرف كيفية الرسم ، فيمكنك تنزيل الأصول من Unity Asset Store ، الذي يحتوي على مجموعة ضخمة من جميع أنواع الموارد: المجانية والمباعة مقابل المال.

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

إنشاء لعبة العالم


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

من هذه الأصول ، جمعت مستوى بسيطًا سنعمل به:



لا سحر ، لقد سحبت الكائنات التي أعجبتني من نافذة Project واستخدام الماوس رتبتها كما أحب:



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

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

الكسالى بحثا عن وسيلة


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

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

نقطة مهمة: لكي تعرف الوحدة الأشياء التي يجب تضمينها في الحساب ، في المفتش لكل كائن (يمكنك تحديد كل شيء مرة واحدة في نافذة التسلسل الهرمي) ، انقر على السهم لأسفل بجوار خيار "ثابت" وتحقق من "التنقل الثابت":



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

الآن سوف نستخدم عنصر القائمة Window⇨AI⇨Navigation وفي النافذة التي تفتح ، حدد علامة التبويب "Bake". ستعرض علينا الوحدة هنا تعيين معلمات مثل ارتفاع ونصف قطر الشخصية ، والحد الأقصى لزاوية ميل الأرض التي لا يزال بإمكانك المشي ، والحد الأقصى لارتفاع الخطوات ، وما إلى ذلك. لن نقوم بتغيير أي شيء حتى الآن فقط اضغط على زر "خبز".



ستقوم الوحدة بإجراء الحسابات اللازمة وتبين لنا النتيجة:



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

امتلاك شبكة تنقل محسوبة ، يمكننا استخدام مكون NavMeshAgent للبحث عن طريق الحركة والتحكم في حركة كائنات اللعبة على مستوىنا.

لنقم بإنشاء كائن لعبة "Zombie" ، وقم بإضافة نموذج ثلاثي الأبعاد من zombies من الأصول إليه ، وكذلك مكون NavMeshAgent:



إذا بدأت اللعبة الآن ، فلن يحدث شيء. نحن بحاجة إلى إخبار مكون NavMeshAgent إلى أين تذهب. للقيام بذلك ، سنقوم بإنشاء المكون الأول لدينا في C #.

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



النقر المزدوج على البرنامج النصي سيفتحه في المحرر. دعونا نرى ما خلقت الوحدة بالنسبة لنا.

using System.Collections; using System.Collections.Generic; using UnityEngine; public class Zombie : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } } 

هذا مكون قياسي فارغ. كما نرى ، قامت Unity بتوصيل System.Collections و System.Collections.Generic مكتبات إلينا (الآن ليست هناك حاجة إليها ، لكنها غالبًا ما تكون مطلوبة في كود ألعاب Unity ، لذلك يتم تضمينها في القالب القياسي) ، وكذلك مكتبة UnityEngine ، التي تحتوي على جميع API المحرك الأساسي.

أيضا ، أنشأت Unity فئة Zombie بالنسبة لنا (الاسم يطابق اسم الملف ، وهذا مهم: إذا لم تتطابق ، فلن تتمكن Unity من مطابقة البرنامج النصي مع المكون في المشهد). يتم توريث الفئة من MonoBehaviour - هذه هي الفئة الأساسية للمكونات التي أنشأها المستخدم.

داخل الفصل ، ابتكرت الوحدة طريقتين: البدء والتحديث. سيطلق المشغل هذه الأساليب بنفسه: ابدأ - فور تحميل المشهد ، وتحديث - كل إطار. في الواقع ، هناك الكثير من الوظائف التي يطلق عليها المحرك ، لكن معظمها لن نحتاج إليه اليوم. يمكن دائمًا العثور على القائمة الكاملة وكذلك تسلسل مكالماتهم في الوثائق: https://docs.unity3d.com/Manual/ExecutionOrder.html

دعونا نجعل الكسالى تتحرك على الخريطة!

أولاً ، نحتاج إلى توصيل مكتبة UnityEngine.AI. أنه يحتوي على فئة NavMeshAgent والفئات الأخرى المتعلقة بشبكة التنقل. للقيام بذلك ، قم بإضافة التوجيه باستخدام UnityEngine.AI إلى بداية الملف.

بعد ذلك ، نحتاج إلى الوصول إلى مكون NavMeshAgent. للقيام بذلك ، يمكننا استخدام الأسلوب GetComponent القياسي. يتيح لك الحصول على رابط لأي مكون في نفس كائن اللعبة حيث يوجد المكون الذي نسميه هذه الطريقة (في حالتنا ، يكون كائن اللعبة "Zombie"). دعنا نحصل على حقل NavMeshAgent navMeshAgent في الفصل ، في طريقة البدء ، نحصل على رابط إلى NavMeshAgent ونطلب منه الانتقال إلى النقطة (0 ، 0 ، 0). يجب أن نحصل على هذا البرنامج النصي:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Zombie : MonoBehaviour { NavMeshAgent navMeshAgent; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); navMeshAgent.SetDestination(Vector3.zero); } // Update is called once per frame void Update() { } } 

عند بدء اللعبة ، سنرى كيف ينتقل الزومبي إلى وسط الخريطة:



الكسالى مطاردة الضحية


رائع. لكن الزومبي لدينا يشعرون بالملل والوحدة ، دعنا نضيف ضحية اللاعب للعبة.

عن طريق القياس مع الزومبي ، سنقوم بإنشاء كائن لعبة "Player" (هذه المرة سنختار نموذج ثلاثي الأبعاد لضابط شرطة) ، وسنضيف أيضًا مكون NavMeshAgent وسيناريو Player المنشأ حديثًا إليه. لن نلمس محتويات البرنامج النصي Player حتى الآن ، لكننا سنحتاج إلى إجراء تغييرات على البرنامج النصي Zombie. أيضًا ، أوصي بتعيين قيمة خاصية الأولوية للاعب إلى 10 في مكون NavMeshAgent (أو أي قيمة أخرى أقل من المستوى القياسي 50 ، أي إعطاء اللاعب أولوية أعلى). في هذه الحالة ، إذا التقى اللاعب والزومبي على الخريطة ، فلن تتمكن الزومبي من تحريك اللاعب ، بينما سيكون اللاعب قادرًا على إخراج الزومبي.

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

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Zombie : MonoBehaviour { NavMeshAgent navMeshAgent; Player player; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); player = FindObjectOfType<Player>(); } // Update is called once per frame void Update() { navMeshAgent.SetDestination(player.transform.position); } } 

قم بتشغيل اللعبة وتأكد من أن الزومبي وجد ضحيته:



الهروب الهروب


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

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

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

افتح البرنامج النصي Player وقم بتغييره كما يلي:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class Player : MonoBehaviour { NavMeshAgent navMeshAgent; public float moveSpeed; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); } // Update is called once per frame void Update() { Vector3 dir = Vector3.zero; if (Input.GetKey(KeyCode.LeftArrow)) dir.z = -1.0f; if (Input.GetKey(KeyCode.RightArrow)) dir.z = 1.0f; if (Input.GetKey(KeyCode.UpArrow)) dir.x = -1.0f; if (Input.GetKey(KeyCode.DownArrow)) dir.x = 1.0f; navMeshAgent.velocity = dir.normalized * moveSpeed; } } 

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

تعيين 10 سرعة:



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

بعد قيامنا بتكوين ناقل اتجاه لاتجاه الحركة dir ، نقوم بتطبيعه (وإلا ، إذا أراد اللاعب التحرك قطريًا ، فسيكون الموجه أطول قليلاً من ناقل واحد وستكون هذه الحركة أسرع من الحركة المباشرة) وتتكاثر بسرعة الحركة المحددة. يتم تمرير النتيجة إلى navMeshAgent.velocity وسيعمل الوكيل الباقي.

من خلال إطلاق اللعبة ، يمكننا أخيرًا محاولة الهروب من الزومبي إلى مكان آمن:



لجعل الكاميرا تتحرك مع المشغل ، دعنا نكتب نصًا بسيطًا آخر. دعنا نسميها "PlayerCamera":

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerCamera : MonoBehaviour { Player player; Vector3 offset; // Start is called before the first frame update void Start() { player = FindObjectOfType<Player>(); offset = transform.position - player.transform.position; } // Update is called once per frame void LateUpdate() { transform.position = player.transform.position + offset; } } 

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

إذا قمت الآن بإرفاق هذا المكون بكائن لعبة "الكاميرا الرئيسية" وبدء اللعبة ، فستظل شخصية اللاعب دائمًا في دائرة الضوء!

لحظة الرسوم المتحركة


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

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

دعونا إنشاء متحرك الرسوم المتحركة لالكسالى. للقيام بذلك ، قم بإنشاء دليل Animations في المشروع (تذكر الترتيب في المشروع) ، وفيه - باستخدام الزر الأيمن - Animator Controller. ودعونا نطلق عليه "غيبوبة". انقر نقرًا مزدوجًا - وسيظهر المحرر أمامنا:



لا توجد ولايات هنا حتى الآن ، ولكن هناك نقطتي دخول ("الدخول" و "أي دولة") ونقطة خروج واحدة ("الخروج"). اسحب زوجين من الرسوم المتحركة من الأصول:



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

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

يوجد زران في الزاوية العلوية اليسرى من نافذة المحرر: "الطبقات" و "المعلمات". افتراضيًا ، يتم تحديد علامة التبويب "الطبقات" ، لكننا بحاجة إلى التبديل إلى "المعلمات". الآن يمكننا إضافة معلمة جديدة من النوع float باستخدام زر "+". دعنا نسميها "السرعة":



نحتاج الآن إلى إخبار Unity بأنه يجب تشغيل الرسوم المتحركة "Z_run" عندما تكون السرعة أكبر من 0 و "Z_idle_A" عندما تكون السرعة صفراً. للقيام بذلك ، يجب علينا إنشاء انتقالين: واحد من "Z_idle_A" إلى "Z_run" ، والآخر في الاتجاه المعاكس.

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



قياسًا على ذلك ، نقوم بإنشاء انتقال في الاتجاه المعاكس ، ولكن كشرط نحدد الآن "السرعة" أقل من 0.0001. لا توجد عمليات تدقيق للمساواة لمعلمات type float ، يمكن فقط مقارنتها بأكثر / أقل:



الآن تحتاج إلى ربط وحدة التحكم إلى كائن اللعبة. سنختار النموذج ثلاثي الأبعاد للزومبي في المشهد (هذا هو طفل لكائن "Zombie") وسحب وحدة التحكم بالماوس إلى الحقل المقابل في مكون Animator:



يبقى فقط لكتابة السيناريو الذي سوف يتحكم في المعلمة السرعة!

قم بإنشاء البرنامج النصي MovementAnimator بالمحتويات التالية:

 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public class MovementAnimator : MonoBehaviour { NavMeshAgent navMeshAgent; Animator animator; // Start is called before the first frame update void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); animator = GetComponentInChildren<Animator>(); } // Update is called once per frame void Update() { animator.SetFloat("speed", navMeshAgent.velocity.magnitude); } } 

نحن هنا ، كما هو الحال في البرامج النصية الأخرى ، في طريقة البدء ، نتمكن من الوصول إلى NavMeshAgent. يمكننا أيضًا الوصول إلى مكون Animator ، ولكن بما أننا سوف نعلق مكون "MovementAnimator" على كائن لعبة "Zombie" ويكون Animator في الكائن الفرعي ، فبدلاً من GetComponent ، نحتاج إلى استخدام الأسلوب GetComponentInChildren القياسي.

في طريقة التحديث ، نطلب من NavMeshAgent تحديد متجه السرعة ، وحساب طوله وتمريره إلى الرسوم المتحركة كمعلمة السرعة. لا سحر ، كل ما في العلم!

أضف الآن مكون MovementAnimator إلى كائن لعبة Zombie ، وإذا بدأت اللعبة ، نرى أن الزومبي الآن متحركون:



لاحظ أنه نظرًا لأننا وضعنا رمز التحكم في الرسوم المتحركة في مكون منفصل لـ MovementAnimation ، فيمكن إضافته بسهولة إلى المشغل. لا نحتاج حتى إلى إنشاء وحدة تحكم من الصفر - يمكنك نسخ وحدة تحكم zombie (يمكن القيام بذلك عن طريق تحديد ملف "Zombie" وضغط Ctrl + D) واستبدال الرسوم المتحركة في مستطيلات الحالة بـ "m_idle_" و "m_run". كل شيء آخر هو مثل غيبوبة. سأترك هذا لك كتمرين (حسنا ، أو قم بتنزيل الكود في نهاية المقالة).

إضافة صغيرة واحدة مفيدة لجعل إضافة الأسطر التالية إلى فئة Zombie:

في طريقة البدء:

 navMeshAgent.updateRotation = false; 

في طريقة التحديث:

 transform.rotation = Quaternion.LookRotation(navMeshAgent.velocity.normalized); 

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

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

أرى الهدف


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

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

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



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

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



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

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



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

الآن دعنا نسحب شبح المؤشر من الأصول إلى المشهد (يمكنني استخدام أصول Spags⇨Textures⇨Demo⇨white_hip⇨white_hip_14):



أضفت دورانًا بزاوية 90 درجة حول محور X إلى المؤشر بحيث يكون وضعه أفقيًا على الأرض ، واضبط المقياس على 0.25 بحيث لم يكن كبيرًا للغاية واضبط إحداثي Y على 0.01. هذا الأخير مهم حتى لا يكون هناك أي تأثير يسمى "القتال Z". تستخدم بطاقة الفيديو حسابات الفاصلة العائمة لتحديد الكائنات القريبة من الكاميرا. إذا قمت بتعيين المؤشر إلى 0 (أي نفس مؤشر مستوى الأرض) ، ثم نظرًا لوجود أخطاء في هذه العمليات الحسابية ، بالنسبة لبعض وحدات البكسل ، ستقرر بطاقة الفيديو أن المؤشر هو الأقرب ، والأرض الأخرى. علاوة على ذلك ، في إطارات مختلفة ، ستكون مجموعات البكسل مختلفة ، مما سيخلق تأثيرًا غير سارة لتلميع قطع المؤشر من الأرض و "الخفقان" عندما يتحرك. قيمة 0.01 كبيرة بما يكفي لتعويض الأخطاء في حساب بطاقة الفيديو ، ولكنها ليست كبيرة لدرجة أن العين لاحظت أن المؤشر معلق في الهواء.

الآن إعادة تسمية كائن اللعبة إلى المؤشر وإنشاء برنامج نصي بنفس الاسم والمحتوى التالي:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Cursor : MonoBehaviour { SpriteRenderer spriteRenderer; int layerMask; // Start is called before the first frame update void Start() { spriteRenderer = GetComponent<SpriteRenderer>(); layerMask = LayerMask.GetMask("Ground"); } // Update is called once per frame void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (!Physics.Raycast(ray, out hit, 1000, layerMask)) spriteRenderer.enabled = false; else { transform.position = new Vector3(hit.point.x, transform.position.y, hit.point.z); spriteRenderer.enabled = true; } } } 

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

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

في طريقة التحديث ، نصل إلى الكاميرا الرئيسية للمشهد باستخدام Camera.main ونطلب منه إعادة حساب إحداثيات الماوس ثنائية الأبعاد (التي تم الحصول عليها باستخدام Input.mousePosition) في شعاع ثلاثي الأبعاد. بعد ذلك ، نقوم بتمرير هذا الشعاع إلى طريقة Physics.Raycast والتحقق مما إذا كان يتقاطع مع أي كائن في المشهد. قيمة 1000 هي المسافة القصوى. في الرياضيات ، لا تنتهي الأشعة ، لكن موارد الحوسبة وذاكرة الكمبيوتر ليست كذلك. لذلك ، الوحدة تطلب منا تحديد بعض المسافة القصوى المعقولة.

إذا لم يكن هناك تقاطع ، فسنقوم بإيقاف تشغيل SpriteRenderer وستختفي صورة المؤشر من الشاشة. إذا تم العثور على التقاطع ، فإننا ننقل المؤشر إلى نقطة التقاطع.يرجى ملاحظة أننا لا نغير إحداثي Y ، لأن نقطة تقاطع الشعاع مع الأرض ستكون Y تساوي الصفر وتعيينها لمؤشرنا ، نحصل مرة أخرى على تأثير Z-fight ، الذي حاولنا التخلص منه أعلاه. لذلك ، نأخذ إحداثيات X و Z فقط من نقطة التقاطع ، ويظل Y كما هو.

إضافة مكون المؤشر إلى كائن لعبة المؤشر.

الآن ، دعنا ننهي البرنامج النصي Player: أولاً ، أضف حقل المؤشر Cursor. ثم في طريقة البدء ، أضف الأسطر التالية:

 cursor = FindObjectOfType<Cursor>(); navMeshAgent.updateRotation = false; 

وأخيرًا ، حتى يتحول اللاعب دائمًا نحو المؤشر ، في طريقة التحديث ، أضف:

 Vector3 forward = cursor.transform.position - transform.position; transform.rotation = Quaternion.LookRotation(new Vector3(forward.x, 0, forward.z)); 

هنا نحن أيضا لا تأخذ في الاعتبار تنسيق Y.

تبادل لاطلاق النار من أجل البقاء


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

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

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

دعنا ننشئ دليل المواد في المشروع وداخله ، دعنا نسميه الأصفر. كتظليل ، حدد Unlit / Color. لا يحتوي هذا التظليل القياسي على الإضاءة ، لذلك ستكون الرصاصة مرئية حتى في الظلام. حدد اللون الأصفر:



الآن بعد إنشاء المادة ، يمكنك تعيينها إلى LineRenderer:



إنشاء نص برمجي أطلق عليه الرصاص:

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Shot : MonoBehaviour { LineRenderer lineRenderer; bool visible; // Start is called before the first frame update void Start() { lineRenderer = GetComponent<LineRenderer>(); } // Update is called once per frame void FixedUpdate() { if (visible) visible = false; else gameObject.SetActive(false); } public void Show(Vector3 from, Vector3 to) { lineRenderer.SetPositions(new Vector3[]{ from, to }); visible = true; gameObject.SetActive(true); } } 

يحتاج هذا البرنامج النصي ، كما تخمينه بالفعل ، إلى إضافته إلى كائن لعبة Shot.

استخدمت هنا حيلة صغيرة لعرض لقطة على الشاشة لإطار واحد بالضبط مع الحد الأدنى من الكود. أولاً ، استخدم FixedUpdate بدلاً من التحديث. يتم استدعاء الأسلوب FixedUpdate بالتردد المحدد (افتراضيًا - 60 إطارًا في الثانية) ، حتى إذا كان معدل الإطارات الحقيقي غير مستقر. ثانياً ، قمت بتعيين المتغير المرئي ، والذي أضبطه على "صحيح" عندما أعرض اللقطة على الشاشة. في FixedUpdate التالي ، أعيد تعيينه إلى false ، وفقط في الإطار التالي ، أقوم بإيقاف تشغيل كائن لعبة اللقطة. بشكل أساسي ، أستخدم متغير منطقي كعداد من 1 إلى 0.

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

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

ولكن عليك أولاً أن تكون قادرًا على الحصول على إحداثيات برميل البندقية حتى تأتي الطلقة من الفتحة الصحيحة. للقيام بذلك ، ابحث عن Bip001⇨Bip001 Pelvis⇨Bip001 Spine⇨Bip001 R Clavicle⇨Bip001 R UpperArm⇨Bip001 R Forearm⇨Bip001 R HandearR⇨hand_container⇨w_handgun object في النموذج الثلاثي الأبعاد للاعب وأضف كائن GunBarrel التابع إليه. ضعه بحيث يكون بجوار برميل البندقية:



الآن في البرنامج النصي Player ، أضف الحقول:

 Shot shot; public Transform gunBarrel; 


أضف إلى طريقة البدء لسيناريو المشغل:

 shot = FindObjectOfType<Shot>(); 

وفي طريقة التحديث:

 if (Input.GetMouseButtonDown(0)) { var from = gunBarrel.position; var target = cursor.transform.position; var to = new Vector3(target.x, from.y, target.z); shot.Show(from, to); } 

كما يمكنك تخمين ذلك ، فإن الحقل العام gunBarrel المضافة ، مثل moveSpeed ​​سابقًا ، سيكون متاحًا في المفتش. لنقم بتعيين كائن اللعبة الحقيقي الذي أنشأناه:



إذا بدأنا اللعبة الآن ، فيمكننا أخيرًا إطلاق النار على الزومبي!



هناك خطأ ما هنا! يبدو أن الطلقات لا تقتل الزومبي ، ولكن ببساطة تطير من خلالها!

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

هذا سهل جدا للإصلاح. في رمز معالجة نقرات الماوس في فئة "المشغل" ، بعد أن يتغير السطر إلى = ... وقبل السطر shot.Show (...) ، أضف الأسطر التالية:

 var direction = (to - from).normalized; RaycastHit hit; if (Physics.Raycast(from, to - from, out hit, 100)) to = new Vector3(hit.point.x, from.y, hit.point.z); else to = from + direction * 100; 

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

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

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



الآن لن تطير الرصاصة في الزومبي. ومع ذلك ، فإن الكسالى لا تزال خالدة.

الكسالى - الموت غيبوبة!


دعونا أولا إضافة الرسوم المتحركة الموت إلى وحدة تحكم الرسوم المتحركة غيبوبة. للقيام بذلك ، اسحب AssetPacks⇨ToonyTinyPeople⇨TT_demo⇨animation⇨zombie⇨Z_death_A للرسوم المتحركة. لتنشيطه ، إنشاء معلمة جديدة توفي مع نوع الزناد. على عكس المعلمات الأخرى (bool ، و float ، وما إلى ذلك) ، لا تتذكر المشغلات حالتها وهي أشبه باستدعاء دالة: لقد قامت بتنشيط مشغل - عملت عملية النقل وإعادة تعيين المشغل مرة أخرى. وبما أن الزومبي يمكن أن يموت في أي ولاية - وإذا كان لا يزال قائماً ، وإذا كان قيد التشغيل ، فسنضيف الانتقال من حالة أي دولة:



أضف الحقول التالية إلى Zombie script:

 CapsuleCollider capsuleCollider; Animator animator; MovementAnimator movementAnimator; bool dead; 

في طريقة بدء الفصل Zombie ، أدخل:

 capsuleCollider = GetComponent<CapsuleCollider>(); animator = GetComponentInChildren<Animator>(); movementAnimator = GetComponent<MovementAnimator>(); 

في بداية طريقة التحديث ، تحتاج إلى إضافة فحص:

 if (dead) return; 

وأخيرًا ، أضف الأسلوب العام Kill إلى فئة Zombie:

 public void Kill() { if (!dead) { dead = true; Destroy(capsuleCollider); Destroy(movementAnimator); Destroy(navMeshAgent); animator.SetTrigger("died"); } } 

إن تعيين حقول جديدة ، كما أعتقد ، أمر واضح للغاية. بالنسبة إلى طريقة Kill - حيث نقوم (في حالة عدم موتنا) بتعيين علم الموت الزومبي وإزالة مكونات CapsuleCollider و MovementAnimator و NavMeshAgent من كائن لعبتنا ، وبعد ذلك نقوم بتنشيط تشغيل الرسوم المتحركة الموت من وحدة تحكم الرسوم المتحركة.

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

بحيث نجح كل هذا في النهاية ، بقي آخر لمسة. في فئة "المشغل" ، في طريقة التحديث ، حيث نسمي Physics.Raycast ، في الفرع لمعرفة الحالة التي تم فيها العثور على تقاطع ، نقوم بإضافة التحقق:

 if (hit.transform != null) { var zombie = hit.transform.GetComponent<Zombie>(); if (zombie != null) zombie.Kill(); } 

Physics.Raycast يستدعي معلومات التقاطع في متغير hit. على وجه الخصوص ، في مجال التحويل سيكون هناك رابط لمكون Transform لكائن اللعبة الذي يتقاطع معه الشعاع. إذا كان كائن اللعبة يحتوي على مكون Zombie ، فعندئذ فهو غيبوبة ونقتلها. ! الابتدائية

حسنًا ، حتى تبدو وفاة العدو مذهلة ، نضيف نظامًا بسيطًا للجزيئات إلى الزومبي.

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

إضافة نظام جسيم إلى كائن لعبة Zombie (انقر بزر الماوس الأيمن فوقه وحدد Effects⇨Particle System):

أقترح الخيارات التالية:
Transform:

  • الموقف: Y 0.5
  • الدوران: X -90

نظام الجسيمات
  • المدة: 0.2
  • حلقات: كاذبة
  • بدء عمر: 0.8
  • حجم البدء: 0.5
  • بدء اللون: الأخضر
  • معدل الجاذبية: 1
  • اللعب على مستيقظا: خطأ
  • الانبعاثات:
  • معدل بمرور الوقت: 100
  • الشكل:
  • نصف القطر: 0.25

يجب أن يبدو مثل هذا:



يبقى لتنشيطه في طريقة Kill لفئة Zombie:

 GetComponentInChildren<ParticleSystem>().Play(); 

والآن مسألة مختلفة تماما!



هجوم الكسالى في القطيع


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

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

 using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemySpawner : MonoBehaviour { public float Period; public GameObject Enemy; float TimeUntilNextSpawn; // Start is called before the first frame update void Start() { TimeUntilNextSpawn = Random.Range(0, Period); } // Update is called once per frame void Update() { TimeUntilNextSpawn -= Time.deltaTime; if (TimeUntilNextSpawn <= 0.0f) { TimeUntilNextSpawn = Period; Instantiate(Enemy, transform.position, transform.rotation); } } } 

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

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

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



لقد أضفت ثلاثة مخالب أخرى في المشروع من أجل التغيير (لذلك ، في النهاية ، لدي 4 منهم). وماذا حدث:



هنا! يبدو بالفعل وكأنه نهاية العالم غيبوبة!

استنتاج


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

بشكل عام ، أيها الأصدقاء ، أتمنى أن تستمتعوا بمقالتي. اكتب أسئلتك في التعليقات ، سأحاول الإجابة. يمكن تنزيل التعليمات البرمجية المصدر للمشروع على github: https://github.com/zapolnov/otus_zombies . ستحتاج إلى Unity 2019.3.0f3 أو أعلى ، ويمكن تنزيله مجانًا تمامًا وبدون رسائل نصية قصيرة من الموقع الرسمي: https://store.unity.com/download .

روابط إلى الأصول المستخدمة في المقال:

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


All Articles