جعل برج الدفاع لعبة وحدة - الجزء 2

الصورة

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

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

للوصول إلى العمل


افتح المشروع في الوحدة الذي توقفنا عنه في الجزء الأخير. إذا انضممت إلينا الآن ، فقم بتنزيل مشروع المشروع وافتح TowerDefense-Part2-Starter .

افتح GameScene من مجلد Scenes .

اقلب الأعداء


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

افتح البرنامج النصي MoveEnemy.cs في IDE وأضف الطريقة التالية إليه لإصلاح الموقف.

private void RotateIntoMoveDirection() { //1 Vector3 newStartPosition = waypoints [currentWaypoint].transform.position; Vector3 newEndPosition = waypoints [currentWaypoint + 1].transform.position; Vector3 newDirection = (newEndPosition - newStartPosition); //2 float x = newDirection.x; float y = newDirection.y; float rotationAngle = Mathf.Atan2 (y, x) * 180 / Mathf.PI; //3 GameObject sprite = gameObject.transform.Find("Sprite").gameObject; sprite.transform.rotation = Quaternion.AngleAxis(rotationAngle, Vector3.forward); } 

RotateIntoMoveDirection يدور العدو بحيث RotateIntoMoveDirection دائما إلى الأمام. يفعل ذلك على النحو التالي:

  1. لحساب الاتجاه الحالي للخلل ، وطرح موضع نقطة الطريق الحالية من موضع النقطة التالية.
  2. يستخدم Mathf.Atan2 لتحديد الزاوية بالتقدير الدائري حيث newDirection توجيه newDirection (نقطة الصفر على اليمين). يقوم 180 / Mathf.PI النتيجة في 180 / Mathf.PI ، مع تحويل الزاوية إلى درجات.
  3. وأخيرًا ، يحصل على الطفل Sprite ويدور rotationAngle المحور بالدرجات. لاحظ أننا نقوم بتدوير الطفل ، وليس الوالد ، بحيث يبقى شريط الطاقة الذي نضيفه لاحقًا أفقيًا.

في Update() ، استبدل التعليق // TODO: بالمكالمة التالية لـ RotateIntoMoveDirection :

 RotateIntoMoveDirection(); 

احفظ الملف وارجع إلى الوحدة. تشغيل المشهد الآن العدو يعرف أين يتحرك.


الآن يعرف الخلل أين يذهب.

العدو الوحيد لا يبدو مثيرًا للإعجاب. نحن بحاجة إلى جحافل! وكما هو الحال في أي لعبة دفاع عن برج ، فإن الحشود تجري في أمواج!

إبلاغ اللاعب


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

مطلوب معلومات الموجة من قبل العديد من GameObjects ، لذلك سنقوم بإضافتها إلى مكون GameManagerBehavior من GameManager .

افتح GameManagerBehavior.cs في IDE وأضف المتغيرين التاليين:

 public Text waveLabel; public GameObject[] nextWaveLabels; 

يخزن waveLabel ارتباطًا إلى تسمية إخراج رقم الموجة في الزاوية العلوية اليمنى من الشاشة. يخزن nextWaveLabels جهازي GameObjects اللذين ينشئان مجموعة من الرسوم المتحركة التي سنعرضها في بداية موجة جديدة:


احفظ الملف وارجع إلى الوحدة. حدد GameManager في التسلسل الهرمي . انقر فوق الدائرة الموجودة على يسار Wave Label وفي مربع الحوار Select Text ، حدد WaveLabel من علامة التبويب Scene .

الآن قم بتعيين حجم تسميات الموجة التالية إلى 2 . الآن قم بتعيين العنصر 0 على NextWaveBottomLabel ، وبالنسبة للعنصر 1 NextWaveTopLabel هو نفسه الذي فعلناه مع Wave Label.


هذا ما يجب أن يكون عليه سلوك مدير اللعبة الآن

إذا خسر اللاعب ، فلا يجب أن يرى رسالة عن الموجة التالية. للتعامل مع هذا الموقف ، ارجع إلى GameManagerBehavior.cs وأضف متغيرًا آخر:

 public bool gameOver = false; 

في gameOver سنقوم بتخزين قيمة ما إذا كان اللاعب خسر.

هنا نستخدم الخاصية مرة أخرى لمزامنة عناصر اللعبة مع الموجة الحالية. أضف التعليمات البرمجية التالية إلى GameManagerBehavior :

 private int wave; public int Wave { get { return wave; } set { wave = value; if (!gameOver) { for (int i = 0; i < nextWaveLabels.Length; i++) { nextWaveLabels[i].GetComponent<Animator>().SetTrigger("nextWave"); } } waveLabel.text = "WAVE: " + (wave + 1); } } 

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

نعطي wave value جديدة.

ثم نتحقق مما إذا كانت اللعبة قد انتهت. إذا لم يكن الأمر كذلك ، فقم بالانتقال عبر جميع ملصقات nextWaveLabels التالية - تحتوي هذه التسميات على مكون Animator . لتمكين الرسوم المتحركة للرسوم المتحركة ، نحدد مشغل nextWave .

أخيرًا ، قمنا بتعيين text waveLabel على wave + 1 . لماذا +1 ؟ لا يبدأ الناس العاديون بالعد من البداية (نعم ، هذا غريب).

في Start() بتعيين قيمة هذه الخاصية:

 Wave = 0; 

نبدأ العد مع Wave رقم 0 .

قم بحفظ الملف وتشغيل المشهد في الوحدة. ستظهر تسمية Wave بشكل صحيح 1.


بالنسبة للاعب ، يبدأ كل شيء بالموجة 1.

الأمواج: إنشاء أكوام من الأعداء


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

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

وسم العدو


حدد الإعداد المسبق للعدو في Project Browser. في أعلى المفتش ، انقر على القائمة المنسدلة للعلامة وحدد إضافة علامة .


قم بإنشاء علامة تسمى العدو .


حدد العدو الجاهزة. في المفتش ، قم بتعيين علامة العدو لها.

تحديد موجات الأعداء


الآن نحن بحاجة إلى ضبط موجة الأعداء. افتح SpawnEnemy.cs في IDE وأضف تطبيق الفصل التالي قبل SpawnEnemy :

 [System.Serializable] public class Wave { public GameObject enemyPrefab; public float spawnInterval = 2; public int maxEnemies = 20; } 

تحتوي الموجة على enemyPrefab - أساس إنشاء حالات لجميع الأعداء في هذه الموجة ، spawnInterval - الوقت بين الأعداء في الموجة بالثواني maxEnemies - عدد الأعداء الذين تم maxEnemies في هذه الموجة.

الفئة قابلة للتسلسل ، أي يمكننا تغيير قيمها في المفتش.

أضف المتغيرات التالية إلى فئة SpawnEnemy :

 public Wave[] waves; public int timeBetweenWaves = 5; private GameManagerBehavior gameManager; private float lastSpawnTime; private int enemiesSpawned = 0; 

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

نضع موجات الأعداء الفرديين في waves enemiesSpawned عدد الأعداء الذين تم enemiesSpawned والوقت الذي تم enemiesSpawned في enemiesSpawned و lastSpawnTime .

بعد كل هذه القتل ، يحتاج اللاعبون إلى وقت للتنفس ، لذا اضبط الوقت بين timeBetweenWaves بين 5 ثوان.

استبدل محتويات Start() بالكود التالي.

 lastSpawnTime = Time.time; gameManager = GameObject.Find("GameManager").GetComponent<GameManagerBehavior>(); 

هنا نقوم بتعيين lastSpawnTime قيمة الوقت الحالي ، أي وقت بدء البرنامج النصي بعد تحميل المشهد. ثم نحصل على GameManagerBehavior المألوفة بالفعل.

أضف التعليمات البرمجية التالية إلى Update() :

 // 1 int currentWave = gameManager.Wave; if (currentWave < waves.Length) { // 2 float timeInterval = Time.time - lastSpawnTime; float spawnInterval = waves[currentWave].spawnInterval; if (((enemiesSpawned == 0 && timeInterval > timeBetweenWaves) || timeInterval > spawnInterval) && enemiesSpawned < waves[currentWave].maxEnemies) { // 3 lastSpawnTime = Time.time; GameObject newEnemy = (GameObject) Instantiate(waves[currentWave].enemyPrefab); newEnemy.GetComponent<MoveEnemy>().waypoints = waypoints; enemiesSpawned++; } // 4 if (enemiesSpawned == waves[currentWave].maxEnemies && GameObject.FindGameObjectWithTag("Enemy") == null) { gameManager.Wave++; gameManager.Gold = Mathf.RoundToInt(gameManager.Gold * 1.1f); enemiesSpawned = 0; lastSpawnTime = Time.time; } // 5 } else { gameManager.gameOver = true; GameObject gameOverText = GameObject.FindGameObjectWithTag ("GameWon"); gameOverText.GetComponent<Animator>().SetBool("gameOver", true); } 

دعونا نحللها خطوة بخطوة:

  1. نحصل على مؤشر الموجة الحالية ونتحقق مما إذا كانت الأخيرة.
  2. إذا كان الأمر كذلك ، فإننا نحسب الوقت المنقضي بعد تفرخ العدو السابق ونتحقق مما إذا كان الوقت قد حان لإنشاء عدو. هنا نأخذ في الاعتبار حالتين. إذا كان هذا هو العدو الأول في الموجة ، فإننا نتحقق مما إذا كانت timeInterval من timeBetweenWaves . وبخلاف ذلك ، نتحقق مما إذا كانت timeInterval من موجات spawnInterval . على أي حال ، نتحقق من أننا لم نخلق جميع الأعداء في هذه الموجة.
  3. إذا لزم الأمر ، تفرخ العدو ، وخلق مثيل enemyPrefab . أيضا زيادة قيمة enemiesSpawned .
  4. تحقق من عدد الأعداء على الشاشة. إذا لم يكونوا موجودين ، وكان هذا آخر عدو في الموجة ، فإننا ننشئ الموجة التالية. أيضًا في نهاية الموجة ، نعطي اللاعب 10 بالمائة من كل الذهب المتبقي.
  5. بعد هزيمة الموجة الأخيرة ، يتم لعب الرسوم المتحركة للنصر في اللعبة هنا.

تحديد فترات التفرخ


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

في الوقت الحالي ، حدد كائنًا للعدو لجميع العناصر الأربعة مثل Enemy Prefab . قم بتكوين الحقلين الفاصل الزمني Spawn و Max Enemies كما يلي:

  • العنصر 0 : الفاصل الزمني للبيض: 2.5 ، الأعداء الأقصى: 5
  • العنصر 1 : الفاصل الزمني للبيض: 2 ، الأعداء الأقصى: 10
  • العنصر 2 : الفاصل الزمني للبيض: 2 ، الأعداء الأقصى: 15
  • العنصر 3 : الفاصل الزمني للبيض: 1 ، الأعداء الأقصى: 5

يجب أن يبدو المخطط النهائي كالتالي:


بالطبع ، يمكنك تجربة هذه القيم لزيادة التعقيد أو تقليله.

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

البق

مهمة إضافية: إضافة أنواع مختلفة من الأعداء


لا يمكن اعتبار أي لعبة دفاع عن برج كاملة مع نوع واحد فقط من العدو. لحسن الحظ ، هناك أيضًا Enemy2 في مجلد Prefabs .

في المفتش ، حدد Prefabs \ Enemy2 وأضف البرنامج النصي MoveEnemy إليه. تعيين السرعة إلى 3 وتعيين علامة العدو . الآن يمكنك استخدام هذا العدو السريع حتى لا يستريح اللاعب!

تحديث حياة اللاعب


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

افتح GameManagerBehavior.cs في IDE وأضف المتغيرين التاليين:

 public Text healthLabel; public GameObject[] healthIndicator; 

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

الإدارة الصحية


أضف الآن خاصية تخزن صحة اللاعب في GameManagerBehavior :

 private int health; public int Health { get { return health; } set { // 1 if (value < health) { Camera.main.GetComponent<CameraShake>().Shake(); } // 2 health = value; healthLabel.text = "HEALTH: " + health; // 3 if (health <= 0 && !gameOver) { gameOver = true; GameObject gameOverText = GameObject.FindGameObjectWithTag("GameOver"); gameOverText.GetComponent<Animator>().SetBool("gameOver", true); } // 4 for (int i = 0; i < healthIndicator.Length; i++) { if (i < Health) { healthIndicator[i].SetActive(true); } else { healthIndicator[i].SetActive(false); } } } } 

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

  1. إذا CameraShake من صحة اللاعب ، فإننا نستخدم مكون CameraShake لإنشاء تأثير اهتزاز جميل. يتم تضمين هذا البرنامج النصي في المشروع القابل للتنزيل ولن نعتبره هنا.
  2. نقوم بتحديث المتغير الخاص والتسمية الصحية في الزاوية اليسرى العليا من الشاشة.
  3. إذا انخفضت الصحة إلى 0 ولم تصل نهاية اللعبة بعد ، gameOver على true وابدأ الرسوم المتحركة gameOver .
  4. نقوم بإزالة أحد الوحوش من ملفات تعريف الارتباط. إذا قمنا فقط بإيقاف تشغيلها ، فيمكن كتابة هذا الجزء بشكل أسهل ، ولكن هنا ندعم إعادة الإدماج في حالة إضافة الصحة.

نقوم بتهيئة Health in Start() :

 Health = 5; 

نضع Health على 5 عندما يبدأ المشهد في اللعب.

بعد القيام بكل هذا ، يمكننا الآن تحديث صحة اللاعب عندما يصل الخطأ إلى ملف تعريف الارتباط. احفظ الملف وانتقل إلى IDE إلى البرنامج النصي MoveEnemy.cs .

التغيير الصحي


لتغيير صحتك ، ابحث عن التعليق في Update() بالكلمات // TODO: واستبدلها بهذا الرمز:

 GameManagerBehavior gameManager = GameObject.Find("GameManager").GetComponent<GameManagerBehavior>(); gameManager.Health -= 1; 

لذا نحصل على GameManagerBehavior ونطرح الوحدة من Health .

احفظ الملف وارجع إلى الوحدة.

حدد GameManager في التسلسل الهرمي وحدد HealthLabel لملصق Health الخاص به.

قم بتوسيع كائن ملفات تعريف الارتباط في التسلسل الهرمي واسحب مؤشرات صحة الأطفال الخمسة إلى مجموعة مؤشرات صحة GameManager - ستكون المؤشرات الصحية عبارة عن وحوش خضراء صغيرة تأكل ملفات تعريف الارتباط.

شغّل المشهد وانتظر حتى تصل البق إلى ملف تعريف الارتباط. لا تفعل شيئًا حتى تخسر.

هجوم ملفات تعريف الارتباط

انتقام الوحش


وحوش في المكان؟ نعم هل يهاجم الأعداء؟ نعم ، وهم يبدون خطرين! حان الوقت للرد على هذه الحيوانات!

للقيام بذلك ، نحتاج إلى ما يلي:

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

شريط صحة العدو


لتنفيذ النطاق الصحي ، نستخدم صورتين - واحدة للخلفية المظلمة ، والثانية (الشريط الأخضر أصغر قليلاً) سنقوم بالتوسع وفقًا لصحة العدو.

اسحب من مستعرض Project إلى مشهد Prefabs \ Enemy .

ثم في التسلسل الهرمي ، اسحب وإسقاط Images \ Objects \ HealthBarBackground على العدو لإضافته كطفل.

في المفتش ، قم بتعيين موقع HealthBarBackground إلى (0 ، 1 ، -4) .

ثم في Project Browser ، حدد Images \ Objects \ HealthBar وتأكد من أن المحور الخاص به هو اليسار . ثم أضفه كطفل للعدو في التسلسل الهرمي وقم بتعيين قيمة المركز (-0.63 ، 1 ، -5) . بالنسبة إلى مقياس X ، اضبط القيمة على 125 .

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

بعد تحديد كائن العدو في التسلسل الهرمي ، تأكد من أن موضعه هو (20 ، 0 ، 0) .

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


الآن كرر كل هذه الخطوات لإضافة شريط صحة Prefabs \ Enemy2 .

قم بتغيير طول شريط الصحة


افتح IDE HealthBar.cs وأضف المتغيرات التالية:

 public float maxHealth = 100; public float currentHealth = 100; private float originalScale; 

في maxHealth يتم تخزين أقصى صحة العدو ، وفي currentHealth - الصحة المتبقية. أخيرًا ، في originalScale هو الحجم الأولي لشريط الصحة.

حفظ كائن originalScale في Start() :

 originalScale = gameObject.transform.localScale.x; 

نقوم بتخزين القيمة x لخاصية localScale .

قم بتعيين مقياس شريط الصحة بإضافة التعليمات البرمجية التالية إلى Update() :

 Vector3 tmpScale = gameObject.transform.localScale; tmpScale.x = currentHealth / maxHealth * originalScale; gameObject.transform.localScale = tmpScale; 

يمكننا نسخ localScale إلى متغير مؤقت لأنه لا يمكننا تغيير قيمته x بشكل منفصل. ثم نحسب مقياس x الجديد بناءً على الحالة الصحية الحالية localScale مرة أخرى قيمة localScale لمتغير مؤقت.

احفظ الملف وابدأ اللعبة في الوحدة. على الأعداء سترى خطوط الصحة.


أثناء تشغيل اللعبة ، قم بتوسيع أحد كائنات العدو (Clone) في التسلسل الهرمي وحدد HealthBar التابع لها . قم بتغيير قيمته الصحية الحالية وانظر كيف يتغير شريط الصحة الخاص به.


كشف الأعداء ضمن النطاق


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

حدد Prefabs \ Monster Project Browser وأضف مكون Circle Collider 2D إليه في المفتش .

قم بتعيين معلمة نصف القطر للمصادم إلى 2.5 - سيشير هذا إلى نصف قطر هجوم الوحوش.

حدد خانة الاختيار " مشغل " بحيث تمر الكائنات عبر هذه المنطقة بدلاً من الاصطدام بها.

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


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

في مستعرض Project ، حدد Prefabs \ Enemy . أضف مكون Rigidbody 2D وحدد Kinematic لنوع الجسم . هذا يعني أن الجسم لن يتأثر بالفيزياء.

أضف Circle Collider 2D مع نصف قطر 1 . كرر هذه الخطوات لـ Prefabs \ Enemy 2 .

يتم تكوين المشغلات ، بحيث تدرك الوحوش أن الأعداء داخل دائرة نصف قطرها.

نحن بحاجة إلى إعداد شيء آخر: نص يخبر الوحوش عندما يتم تدمير العدو حتى لا يثيروا استثناءًا بينما يواصلون إطلاق النار.

إنشاء برنامج نصي C # جديد يسمى EnemyDestructionDelegate وإضافته إلى Preems Enemy و Enemy2 .

افتح EnemyDestructionDelegate.cs في IDE وأضف إعلان التفويض التالي:

 public delegate void EnemyDelegate (GameObject enemy); public EnemyDelegate enemyDelegate; 

نقوم هنا بإنشاء delegate ، أي حاوية لدالة يمكن تمريرها كمتغير.

ملاحظة : يتم استخدام المفوضين عندما يجب أن يقوم كائن لعبة واحد بإعلام كائنات اللعبة الأخرى بالتغييرات. اقرأ المزيد عن المندوبين في وثائق الوحدة .

أضف الطريقة التالية:

 void OnDestroy() { if (enemyDelegate != null) { enemyDelegate(gameObject); } } 

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

احفظ الملف وارجع إلى الوحدة.

نعطي الوحوش رخصة للقتل


والآن يمكن للوحوش اكتشاف الأعداء داخل دائرة نصف قطر عملهم. قم بإضافة برنامج نصي جديد لـ C # إلى الإعداد المسبق لـ Monster وقم بتسميته ShootEnemies .

افتح ShootEnemies.cs في IDE وأضف ما يلي using البناء إليها للوصول إلى Generics .

 using System.Collections.Generic; 

أضف متغيرًا لتتبع جميع الأعداء داخل النطاق:

 public List<GameObject> enemiesInRange; 

في enemiesInRange سنقوم بتخزين جميع الأعداء داخل النطاق.

تهيئة الحقل في Start() .

 enemiesInRange = new List<GameObject>(); 

في البداية ، لا يوجد أعداء في دائرة العمل ، لذلك ننشئ قائمة فارغة.

املأ قائمة enemiesInRange ! أضف التعليمات البرمجية التالية إلى البرنامج النصي:

 // 1 void OnEnemyDestroy(GameObject enemy) { enemiesInRange.Remove (enemy); } void OnTriggerEnter2D (Collider2D other) { // 2 if (other.gameObject.tag.Equals("Enemy")) { enemiesInRange.Add(other.gameObject); EnemyDestructionDelegate del = other.gameObject.GetComponent<EnemyDestructionDelegate>(); del.enemyDelegate += OnEnemyDestroy; } } // 3 void OnTriggerExit2D (Collider2D other) { if (other.gameObject.tag.Equals("Enemy")) { enemiesInRange.Remove(other.gameObject); EnemyDestructionDelegate del = other.gameObject.GetComponent<EnemyDestructionDelegate>(); del.enemyDelegate -= OnEnemyDestroy; } } 

  1. في OnEnemyDestroy نزيل العدو من أعداء enemiesInRange . عندما يخطو عدو على زناد حول وحش ، يتم OnTriggerEnter2D .
  2. ثم نضيف العدو إلى قائمة EnemyDestructionDelegate حدث OnEnemyDestroy . لذا نضمن أنه عند تدمير العدو سيتم استدعاء OnEnemyDestroy . لا نريد أن تنفق الوحوش الذخيرة على الأعداء القتلى ، أليس كذلك؟
  3. في OnTriggerExit2D نقوم بإزالة العدو من القائمة وإلغاء تسجيل المفوض. الآن نحن نعلم أي الأعداء في النطاق.

احفظ الملف وابدأ اللعبة في الوحدة. للتأكد من أن كل شيء يعمل ، enemiesInRange الوحش ، حدده واتبع التغييرات في قائمة enemiesInRange في enemiesInRange .

اختيار الهدف


تعرف الوحوش الآن العدو الموجود في النطاق. ولكن ماذا سيفعلون عندما يكون هناك العديد من الأعداء في نصف القطر؟

بالطبع ، سيهاجمون الأقرب إلى الكبد!

افتح البرنامج النصي MoveEnemy.cs IDE وأضف طريقة جديدة تحسب هذا الوحش:

 public float DistanceToGoal() { float distance = 0; distance += Vector2.Distance( gameObject.transform.position, waypoints [currentWaypoint + 1].transform.position); for (int i = currentWaypoint + 1; i < waypoints.Length - 1; i++) { Vector3 startPosition = waypoints [i].transform.position; Vector3 endPosition = waypoints [i + 1].transform.position; distance += Vector2.Distance(startPosition, endPosition); } return distance; } 

يحسب الرمز طول المسار الذي لم يسلكه العدو بعد. للقيام بذلك ، يستخدم Distance ، والتي يتم حسابها Vector3 بين مثيلين من Vector3 .

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

احفظ الملف وارجع إلى Unity لبدء إعداد الأصداف الخاصة بك.

دعونا نعطي قذائف الوحوش. الكثير من القذائف!


اسحب من مستعرض Project إلى الصور / الكائنات / المشهد Bullet1 . قم بتعيين الموضع على z إلى -2 - المواضع على x و y ليست مهمة ، لأننا نعينها في كل مرة نقوم فيها بإنشاء مثيل جديد للقذيفة أثناء تنفيذ البرنامج.

قم بإضافة برنامج نصي C # جديد يسمى BulletBehavior ، ثم في IDE أضف المتغيرات التالية إليه:

 public float speed = 10; public int damage; public GameObject target; public Vector3 startPosition; public Vector3 targetPosition; private float distance; private float startTime; private GameManagerBehavior gameManager; 

تحدد السرعة سرعة المقذوفات ؛ damage واضح من الاسم.

target ، startPosition و targetPosition تحديد اتجاه المقذوف.

distance startTime تتبع الوضع الحالي للقذيفة. يكافئ gameManager اللاعب عندما يقتل العدو.

قم بتعيين قيم هذه المتغيرات في Start() :

 startTime = Time.time; distance = Vector2.Distance (startPosition, targetPosition); GameObject gm = GameObject.Find("GameManager"); gameManager = gm.GetComponent<GameManagerBehavior>(); 

startTimeنحدد قيمة الوقت الحالي ونحسب المسافة بين مواضع البداية والهدف. أيضا ، كالعادة ، نحصل GameManagerBehavior.

للتحكم في حركة المقذوف ، أضف Update()الكود التالي:

 // 1 float timeInterval = Time.time - startTime; gameObject.transform.position = Vector3.Lerp(startPosition, targetPosition, timeInterval * speed / distance); // 2 if (gameObject.transform.position.Equals(targetPosition)) { if (target != null) { // 3 Transform healthBarTransform = target.transform.Find("HealthBar"); HealthBar healthBar = healthBarTransform.gameObject.GetComponent<HealthBar>(); healthBar.currentHealth -= Mathf.Max(damage, 0); // 4 if (healthBar.currentHealth <= 0) { Destroy(target); AudioSource audioSource = target.GetComponent<AudioSource>(); AudioSource.PlayClipAtPoint(audioSource.clip, transform.position); gameManager.Gold += 50; } } Destroy(gameObject); } 

  1. نحسب الموضع الجديد للقذيفة ، باستخدام Vector3.Lerpالتقارب بين وضعي البداية والنهاية.
  2. إذا وصلت المقذوفة targetPosition، فإننا نتحقق لمعرفة ما إذا كانت لا تزال موجودة target.
  3. نحصل على عنصر HealthBarالهدف ونخفف من صحته بحجم damageالمقذوف.
  4. إذا تم تخفيض صحة العدو إلى الصفر ، فإننا ندمرها ونعيد إنتاج التأثير الصوتي ونكافئ اللاعب على الدقة.

احفظ الملف وارجع إلى الوحدة.

نصنع قذائف كبيرة


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

اسحب كائن اللعبة Bullet1 من التسلسل الهرمي إلى علامة التبويب " المشروع" لإنشاء مسبقة قذيفة. قم بإزالة الكائن الأصلي من المشهد - لن نحتاجه بعد الآن.

مرتين تكرار الجاهزة Bullet1 . اسم نسخ Bullet2 و Bullet3 .

حدد Bullet2 . في المفتش ، قم بتعيين حقل Sprite لمكون Sprite Renderer إلى Images / Objects / Bullet2. لذا سنجعل Bullet2 أكثر بقليل من Bullet1.

كرر الإجراء لتغيير العفريت من Bullet3 الجاهزة إلى Images / Objects / Bullet3 .

علاوة على ذلك ، في Bullet Behavior سنقوم بتعديل مقدار الضرر الناجم عن القذائف.

حدد Bullet1 الجاهزة في علامة التبويب Project . على المفتش أن ترى رصاصة السلوك (سيناريو) ، والتي يمكن تعيين الأضرار قيمة 10 ل Bullet1 ، 15 ل Bullet2 و 20 ل Bullet3 - أو أي قيم أخرى تفضلها.

ملاحظة : قمت بتغيير القيم بحيث يصبح سعر الضرر أعلى من المستويات الأعلى. هذا يمنع الترقية من السماح للاعب بترقية الوحوش في أفضل النقاط.


قذائف الجاهزة - يزيد الحجم مع المستوى

تغيير مستوى القذائف


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

افتح MonsterData.cs في IDE وأضف إلى MonsterLevelالمتغيرات التالية:

 public GameObject bullet; public float fireRate; 

لذلك قمنا بإعداد المقذوف المسبق وتواتر إطلاق النار لكل مستوى من الوحوش. احفظ الملف وارجع إلى الوحدة لإكمال إعداد الوحش.

حدد الإعداد المسبق من Monster في Project Browser . في المفتش ، قم بتوسيع المستويات في مكون Monster Data (Script) . اضبط معدل الحريق لكل عنصر على 1 . ثم قم بتعيين معلمة Bullet للعنصر 0 و 1 و 2 على Bullet1 و Bullet2 و Bullet3 .

يجب تعيين مستويات الوحش على النحو التالي:


قذائف تقتل الأعداء؟ نعم فعلا! دعونا نفتح النار!

افتح النار


افتح ShootEnemies.cs في IDE وأضف المتغيرات التالية:

 private float lastShotTime; private MonsterData monsterData; 

كما يوحي الاسم ، تتبع هذه المتغيرات وقت آخر لقطة للوحش ، بالإضافة إلى الهيكل MonsterDataالذي يحتوي على معلومات حول نوع قذائف الوحش ، وتواتر إطلاق النار ، وما إلى ذلك.

قم بتعيين قيم هذه الحقول في Start():

 lastShotTime = Time.time; monsterData = gameObject.GetComponentInChildren<MonsterData>(); 

نقوم هنا بتعيين lastShotTimeقيمة الوقت الحالي والحصول على الوصول إلى مكون MonsterDataهذا الكائن.

أضف الطريقة التالية لتنفيذ التصوير:

 void Shoot(Collider2D target) { GameObject bulletPrefab = monsterData.CurrentLevel.bullet; // 1 Vector3 startPosition = gameObject.transform.position; Vector3 targetPosition = target.transform.position; startPosition.z = bulletPrefab.transform.position.z; targetPosition.z = bulletPrefab.transform.position.z; // 2 GameObject newBullet = (GameObject)Instantiate (bulletPrefab); newBullet.transform.position = startPosition; BulletBehavior bulletComp = newBullet.GetComponent<BulletBehavior>(); bulletComp.target = target.gameObject; bulletComp.startPosition = startPosition; bulletComp.targetPosition = targetPosition; // 3 Animator animator = monsterData.CurrentLevel.visualization.GetComponent<Animator>(); animator.SetTrigger("fireShot"); AudioSource audioSource = gameObject.GetComponent<AudioSource>(); audioSource.PlayOneShot(audioSource.clip); } 

  1. نحصل على مواقع البداية والهدف للرصاصة. ضع الموضع z يساوي z bulletPrefab. في السابق ، قمنا بتعيين الوضع المسبق للقذيفة في z بحيث تظهر المقذوفة تحت وحش الرماية ، ولكن فوق الأعداء.
  2. نقوم بإنشاء مثيل لصدفة جديدة باستخدام القشرة bulletPrefabالمناسبة MonsterLevel. تعيين startPositionو targetPositionقذيفة.
  3. نجعل اللعبة أكثر إثارة للاهتمام: عندما يطلق الوحش ، ابدأ الرسوم المتحركة للتصوير وشغل صوت الليزر.

ضع كل ذلك معًا


حان الوقت لتجميع كل شيء. حدد الهدف واجعل الوحش ينظر إليه.


في البرنامج النصي ShootEnemies.cs ، أضف إلى Update()هذا الرمز:

 GameObject target = null; // 1 float minimalEnemyDistance = float.MaxValue; foreach (GameObject enemy in enemiesInRange) { float distanceToGoal = enemy.GetComponent<MoveEnemy>().DistanceToGoal(); if (distanceToGoal < minimalEnemyDistance) { target = enemy; minimalEnemyDistance = distanceToGoal; } } // 2 if (target != null) { if (Time.time - lastShotTime > monsterData.CurrentLevel.fireRate) { Shoot(target.GetComponent<Collider2D>()); lastShotTime = Time.time; } // 3 Vector3 direction = gameObject.transform.position - target.transform.position; gameObject.transform.rotation = Quaternion.AngleAxis( Mathf.Atan2 (direction.y, direction.x) * 180 / Mathf.PI, new Vector3 (0, 0, 1)); } 

خذ بعين الاعتبار هذا الرمز خطوة بخطوة.

  1. تحديد الغرض من الوحش. نبدأ في أقصى مسافة ممكنة في minimalEnemyDistance. ننتقل في دورة من جميع الأعداء داخل النطاق ونجعل العدو هدفًا جديدًا إذا كانت المسافة بينه وبين ملف تعريف الارتباط أقل من أصغر الحالي.
  2. نسمي Shootإذا كان الوقت المنقضي أكبر من تردد إطلاق الوحش وتعيين lastShotTimeقيمة الوقت الحالي.
  3. نحسب زاوية الدوران بين الوحش وهدفه. ندير الوحش إلى هذه الزاوية. الآن سوف ينظر دائمًا إلى الهدف.

احفظ الملف وابدأ اللعبة في الوحدة. سوف تبدأ الوحوش في حماية يائسة ملفات تعريف الارتباط. لقد انتهينا أخيرًا!

إلى أين أذهب بعد ذلك


يمكن تنزيل المشروع النهائي من هنا .

لقد قمنا بعمل رائع في هذا البرنامج التعليمي ولدينا الآن لعبة رائعة.

فيما يلي بعض الأفكار لمزيد من التطوير للمشروع:

  • المزيد من أنواع الأعداء والوحوش
  • طرق مختلفة للأعداء
  • مستويات لعبة مختلفة

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

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

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


All Articles