Menjadikan Tower Defense sebagai Game Persatuan - Bagian 2

gambar

Ini adalah bagian kedua dari tutorial “Membuat game Tower Defense di Unity” . Kami membuat game bergenre tower defense di Unity, dan pada akhir bagian pertama , kami belajar cara menempatkan dan meningkatkan monster. Kami juga memiliki satu cookie yang menyerang musuh.

Namun, musuh belum tahu ke mana harus mencari! Selain itu, serangan saja terlihat aneh. Di bagian tutorial ini, kami akan menambahkan gelombang musuh dan monster lengan sehingga mereka dapat mempertahankan cookie yang berharga.

Mulai bekerja


Buka proyek di Unity, yang kami hentikan di bagian terakhir. Jika Anda bergabung dengan kami sekarang, unduh konsep proyek dan buka TowerDefense-Part2-Starter .

Buka GameScene dari folder Adegan .

Ubah musuh


Pada akhir tutorial sebelumnya, musuh belajar bergerak di sepanjang jalan, tetapi tampaknya ia tidak tahu ke mana harus mencari.

Buka skrip MoveEnemy.cs di IDE dan tambahkan metode berikut untuk memperbaiki situasi.

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 memutar musuh sehingga selalu RotateIntoMoveDirection ke depan. Dia melakukannya sebagai berikut:

  1. Menghitung arah bug saat ini, mengurangi posisi titik jalan saat ini dari posisi titik berikutnya.
  2. Menggunakan Mathf.Atan2 untuk menentukan sudut dalam radian di mana diarahkan arah baru (titik nol di sebelah kanan). 180 / Mathf.PI hasilnya dengan 180 / Mathf.PI , mengubah sudut menjadi derajat.
  3. Akhirnya, ia mendapatkan anak Sprite dan memutar derajat rotationAngle sumbu. Perhatikan bahwa kita memutar anak , bukan orang tua, sehingga strip energi yang kita tambahkan nanti tetap horisontal.

Di Update() , ganti komentar // TODO: panggilan berikutnya ke RotateIntoMoveDirection :

 RotateIntoMoveDirection(); 

Simpan file dan kembali ke Unity. Jalankan adegan; sekarang musuh tahu ke mana dia bergerak.


Sekarang bug tahu ke mana ia pergi.

Musuh satu-satunya tidak terlihat sangat mengesankan. Kami membutuhkan gerombolan! Dan seperti dalam setiap game menara pertahanan, gerombolan berlari dalam gelombang!

Menginformasikan pemain


Sebelum kita mulai menggerakkan gerombolan, kita perlu memperingatkan pemain tentang pertempuran yang akan datang. Selain itu, ada baiknya menampilkan nomor gelombang saat ini di bagian atas layar.

Informasi Wave diperlukan oleh beberapa GameObjects, jadi kami akan menambahkannya ke komponen GameManagerBehavior dari GameManager .

Buka GameManagerBehavior.cs di IDE dan tambahkan dua variabel berikut:

 public Text waveLabel; public GameObject[] nextWaveLabels; 

waveLabel menyimpan tautan ke label output nomor gelombang di sudut kanan atas layar. nextWaveLabels menyimpan dua GameObjects yang membuat kombinasi animasi yang akan kami tampilkan di awal gelombang baru:


Simpan file dan kembali ke Unity. Pilih GameManager di Hirarki . Klik lingkaran di sebelah kanan Wave Label dan di kotak dialog Select Text , pilih WaveLabel dari tab Scene .

Sekarang atur Label Ukuran untuk Gelombang Selanjutnya ke 2 . Sekarang atur Elemen 0 ke NextWaveBottomLabel , dan untuk Elemen 1 NextWaveTopLabel sama seperti yang kita lakukan dengan Wave Label.


Seperti inilah perilaku Manajer Game sekarang

Jika pemain kalah, maka dia seharusnya tidak melihat pesan tentang gelombang berikutnya. Untuk menangani situasi ini, kembali ke GameManagerBehavior.cs dan tambahkan variabel lain:

 public bool gameOver = false; 

Di gameOver kami akan menyimpan nilai apakah pemain kalah.

Di sini kita kembali menggunakan properti untuk menyinkronkan elemen-elemen permainan dengan gelombang saat ini. Tambahkan kode berikut ke 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); } } 

Membuat variabel pribadi, properti, dan pengambil harus sudah menjadi hal yang akrab bagi Anda. Tetapi dengan setter, sekali lagi, semuanya sedikit lebih menarik.

Kami memberikan wave value baru.

Kemudian kami memeriksa apakah game sudah selesai. Jika tidak, maka lewati semua label NextLaveLabel - label ini memiliki komponen Animator . Untuk mengaktifkan animasi Animator , kami menetapkan pemicu Gelombang berikutnya .

Terakhir, kami mengatur text waveLabel ke wave + 1 . Mengapa memberi +1 ? Orang biasa tidak mulai menghitung dari awal (ya, ini aneh).

Di Start() mengatur nilai properti ini:

 Wave = 0; 

Kami memulai penghitungan dengan angka 0 Wave .

Simpan file dan jalankan adegan di Unity. Label Wave akan menunjukkan dengan benar 1.


Untuk seorang pemain, semuanya dimulai dengan gelombang 1.

Gelombang: membuat banyak musuh


Ini mungkin terlihat jelas, tetapi untuk menyerang dengan gerombolan perlu untuk membuat lebih banyak musuh - sementara kita tidak tahu bagaimana melakukan ini. Selain itu, kita tidak harus membuat gelombang berikutnya sampai yang sekarang dihancurkan.

Artinya, gim harus bisa mengenali keberadaan musuh di TKP, dan tag adalah cara yang baik untuk mengidentifikasi objek gim di sini.

Penandaan musuh


Pilih prefab Musuh di Browser Proyek. Di bagian atas Inspektur, klik pada daftar drop-down Tag dan pilih Add Tag .


Buat tag yang disebut Musuh .


Pilih Musuh cetakan. Di Inspektur, atur tag Musuh untuknya.

Mendefinisikan gelombang musuh


Sekarang kita perlu mengatur gelombang musuh. Buka SpawnEnemy.cs di IDE dan tambahkan implementasi kelas berikut sebelum SpawnEnemy :

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

Wave berisi enemyPrefab - dasar untuk membuat instance dari semua musuh dalam wave ini, spawnInterval - waktu antara musuh dalam wave dalam hitungan detik dan maxEnemies - jumlah musuh yang dibuat dalam wave ini.

Kelas Serializable , yaitu, kita dapat mengubah nilainya di Inspektur.

Tambahkan variabel berikut ke kelas SpawnEnemy :

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

Di sini kita mengatur variabel untuk memunculkan musuh, yang sangat mirip dengan bagaimana kita memindahkan musuh di antara titik-titik pada rute.

Kami mengatur gelombang masing-masing musuh dalam waves dan melacak jumlah musuh yang dibuat dan waktu mereka diciptakan dalam enemiesSpawned dan lastSpawnTime .

Setelah semua pembunuhan ini, pemain perlu waktu untuk bernapas, jadi atur waktu antara timeBetweenWaves ke 5 detik.

Ganti konten Start() kode berikut.

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

Di sini kita menetapkan lastSpawnTime nilai waktu saat ini, yaitu waktu script dimulai setelah adegan dimuat. Lalu kita dapatkan GameManagerBehavior sudah akrab.

Tambahkan kode berikut ke 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); } 

Mari kita analisa langkah demi langkah:

  1. Kami mendapatkan indeks gelombang saat ini dan memeriksa apakah itu yang terakhir.
  2. Jika demikian, maka kami menghitung waktu yang telah berlalu setelah musuh sebelumnya muncul dan memeriksa apakah sudah waktunya untuk membuat musuh. Di sini kita memperhitungkan dua kasus. Jika ini adalah musuh pertama dalam wave, maka kami memeriksa apakah timeInterval dari timeBetweenWaves . Jika tidak, kami memeriksa apakah timeInterval dari spawnInterval wave. Bagaimanapun, kami memeriksa bahwa kami belum menciptakan semua musuh di gelombang ini.
  3. Jika perlu, buat musuh, buat instance dari enemyPrefab . Juga tingkatkan nilai enemiesSpawned .
  4. Periksa jumlah musuh di layar. Jika mereka tidak ada di sana, dan ini adalah musuh terakhir dalam gelombang, maka kita buat gelombang berikutnya. Juga di akhir gelombang, kami memberi pemain 10 persen dari semua emas yang tersisa.
  5. Setelah mengalahkan gelombang terakhir, animasi kemenangan dalam permainan dimainkan di sini.

Mengatur interval spawn


Simpan file dan kembali ke Unity. Pilih objek Jalan di Hirarki . Di Inspektur, atur objek Size of the Waves ke 4 .

Untuk saat ini, pilih objek Musuh untuk keempat elemen sebagai Prefab Musuh . Konfigurasikan bidang Spawn Interval dan Max Enemies sebagai berikut:

  • Elemen 0 : Spawn Interval: 2.5 , Max Enemies: 5
  • Elemen 1 : Interval Spawn: 2 , Max Musuh: 10
  • Elemen 2 : Interval Spawn: 2 , Max Musuh: 15
  • Elemen 3 : Interval Spawn: 1 , Max Musuh: 5

Skema yang sudah selesai akan terlihat seperti ini:


Tentu saja, Anda dapat bereksperimen dengan nilai-nilai ini untuk menambah atau mengurangi kompleksitas.

Luncurkan game. Ya! Kumbang telah memulai perjalanan ke cookie Anda!

bug

Tugas tambahan: tambahkan berbagai jenis musuh


Tidak ada game menara pertahanan yang dapat dianggap lengkap hanya dengan satu jenis musuh. Untungnya, ada juga Enemy2 di folder Rak itan .

Di Inspektur, pilih Rak itan \ Enemy2 dan tambahkan skrip MoveEnemy ke sana. Setel Kecepatan ke 3 dan atur tag Musuh . Sekarang Anda dapat menggunakan musuh cepat ini sehingga pemain tidak rileks!

Pembaruan Kehidupan Pemain


Meskipun gerombolan musuh menyerang cookie, pemain tidak mengalami kerusakan. Tapi segera kami akan memperbaikinya. Pemain harus menderita jika dia membiarkan musuh menyelinap.

Buka GameManagerBehavior.cs di IDE dan tambahkan dua variabel berikut:

 public Text healthLabel; public GameObject[] healthIndicator; 

Kami menggunakan healthLabel untuk mengakses nilai kehidupan pemain, dan healthIndicator untuk mengakses lima monster hijau kecil yang mengunyah cookie - mereka hanya melambangkan kesehatan pemain; ini lebih lucu daripada indikator kesehatan standar.

Manajemen kesehatan


Sekarang tambahkan properti yang menyimpan kesehatan pemain di 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); } } } } 

Inilah cara kami mengelola kesehatan pemain. Dan lagi, bagian utama dari kode terletak di setter:

  1. Jika kami mengurangi kesehatan pemain, kami menggunakan komponen CameraShake untuk menciptakan efek getaran yang indah. Skrip ini termasuk dalam proyek yang dapat diunduh dan kami tidak akan mempertimbangkannya di sini.
  2. Kami memperbarui variabel pribadi dan label kesehatan di sudut kiri atas layar.
  3. Jika kesehatan turun ke 0 dan akhir game belum datang, maka gameOver menjadi true dan mulai animasi gameOver .
  4. Kami menghapus salah satu monster dari cookie. Jika kita mematikannya, maka bagian ini dapat ditulis lebih mudah, tetapi di sini kami mendukung inklusi kembali jika kesehatan ditambahkan.

Kami menginisialisasi Health in Start() :

 Health = 5; 

Kami menetapkan Health ke 5 saat adegan mulai diputar.

Setelah melakukan semua ini, kami sekarang dapat memperbarui kesehatan pemain ketika bug sampai ke cookie. Simpan file dan pergi ke IDE ke skrip MoveEnemy.cs .

Perubahan kesehatan


Untuk mengubah kesehatan Anda, temukan komentar di Update() dengan kata-kata // TODO: dan ganti dengan kode ini:

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

Jadi kami mendapatkan GameManagerBehavior dan mengurangi unit dari Health .

Simpan file dan kembali ke Unity.

Pilih GameManager di Hierarchy dan pilih HealthLabel untuk Label Kesehatannya .

Perluas objek Cookie di Hierarchy dan seret HealthIndikatorator lima anaknya ke dalam array Indikator Kesehatan GameManager - indikator kesehatan akan berupa monster hijau kecil yang memakan cookie.

Jalankan adegan dan tunggu sampai bug mencapai cookie. Jangan lakukan apa pun sampai Anda kalah.

serangan cookie

Balas dendam monster


Monster di tempat? Ya Apakah musuh menyerang? Ya, dan mereka terlihat mengancam! Sudah waktunya untuk menjawab hewan-hewan ini!

Untuk melakukan ini, kita perlu yang berikut:

  • Jalur kesehatan sehingga pemain tahu musuh mana yang kuat dan mana yang lemah
  • Mendeteksi musuh dalam jangkauan monster
  • Membuat keputusan - musuh yang akan ditembak
  • Banyak kerang

Bilah kesehatan musuh


Untuk menerapkan pita kesehatan, kami menggunakan dua gambar - satu untuk latar belakang gelap, dan yang kedua (bilah hijau sedikit lebih kecil) kami akan skala sesuai dengan kesehatan musuh.

Seret dari Browser Proyek ke adegan Prefab \ Musuh .

Kemudian di Hierarki, seret dan lepas Gambar \ Objek \ HealthBarBackground ke Musuh untuk menambahkannya sebagai seorang anak.

Di Inspektur, atur Posisi HealthBarBackground ke (0, 1, -4) .

Kemudian di Browser Proyek, pilih Gambar \ Objek \ HealthBar dan pastikan Pivot -nya adalah Kiri . Kemudian tambahkan sebagai anak Musuh dalam Hirarki dan tetapkan nilai Posisinya (-0,63, 1, -5) . Untuk Skala X , atur nilainya menjadi 125 .

Tambahkan skrip C # baru yang disebut HealthBar ke objek game HealthBar . Nanti kita akan mengubahnya sehingga mengubah panjang bar kesehatan.

Setelah memilih objek Musuh dalam Hirarki , pastikan posisinya adalah (20, 0, 0) .

Klik Terapkan di bagian atas Inspektur untuk menyimpan semua perubahan sebagai bagian dari cetakan. Akhirnya, hapus objek Musuh dalam Hirarki .


Sekarang ulangi semua langkah ini untuk menambahkan bilah kesehatan untuk Prefabs \ Enemy2 .

Ubah panjang bilah kesehatan


Buka IDE HealthBar.cs dan tambahkan variabel berikut:

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

Di maxHealth , kesehatan maksimum musuh disimpan, dan di saat ini, currentHealth - kesehatan yang tersisa. Akhirnya, dalam originalScale adalah ukuran awal bar kesehatan.

Simpan objek originalScale di Start() :

 originalScale = gameObject.transform.localScale.x; 

Kami menyimpan nilai x properti localScale .

Tetapkan skala bilah kesehatan dengan menambahkan kode berikut ke Update() :

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

Kami dapat menyalin localScale ke variabel sementara karena kami tidak dapat mengubah nilai x -nya secara terpisah. Kemudian kami menghitung skala x baru berdasarkan kesehatan kumbang saat ini dan sekali lagi menetapkan nilai localScale ke variabel sementara.

Simpan file dan luncurkan game di Unity. Selama musuh Anda akan melihat garis-garis kesehatan.


Ketika permainan sedang berjalan, perluas salah satu objek Musuh (Klon) di Hierarki dan pilih HealthBar anaknya. Ubah nilainya Kesehatan Saat Ini dan lihat bagaimana bilah kesehatannya berubah.


Deteksi musuh dalam jangkauan


Sekarang monster kita perlu mencari tahu musuh mana yang harus dibidik. Tetapi sebelum Anda menyadari peluang ini, Anda perlu menyiapkan Monster dan Musuh.

Pilih Project Browser Prefabs \ Monster dan tambahkan komponen Circle Collider 2D ke dalam Inspektur .

Atur parameter Radius dari collider ke 2.5 - ini akan menunjukkan radius serangan monster.

Pilih kotak centang Is Trigger sehingga objek melewati area ini daripada bertabrakan dengannya.

Akhirnya, di bagian atas Inspektur , atur Layer of the Monster ke Abaikan Raycast . Di kotak dialog, klik Ya, ubah anak-anak . Jika Abaikan Raycast tidak dipilih, collider akan merespons peristiwa klik mouse. Ini akan menjadi masalah karena monster memblokir acara yang ditujukan untuk objek Openspot di bawahnya.


Untuk memastikan bahwa musuh terdeteksi di area pemicu, kita perlu menambahkan collider dan body yang kaku padanya, karena Unity hanya mengirimkan event trigger ketika body yang kaku terpasang ke salah satu colliders.

Di Browser Proyek, pilih Rak itan \ Musuh . Tambahkan komponen 2D Rigidbody dan pilih Kinematik untuk Jenis Tubuh . Ini berarti bahwa tubuh tidak akan terpengaruh oleh fisika.

Tambahkan Circle Collider 2D dengan Radius 1 . Ulangi langkah ini untuk Rak itan \ Musuh 2 .

Pemicu dikonfigurasikan, jadi monster akan memahami bahwa musuh berada dalam radius aksi mereka.

Kita perlu menyiapkan satu hal lagi: naskah memberi tahu monster saat musuh dihancurkan sehingga mereka tidak mengeluarkan pengecualian sambil terus menembak.

Buat skrip C # baru bernama EnemyDestructionDelegate dan tambahkan ke prefab Enemy dan Enemy2 .

Buka EnemyDestructionDelegate.cs di IDE dan tambahkan deklarasi delegasi berikut:

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

Di sini kita membuat delegate , yaitu wadah untuk fungsi yang dapat dilewatkan sebagai variabel.

Catatan : delegasi digunakan ketika satu objek game harus secara aktif memberitahukan objek perubahan game lainnya. Baca lebih lanjut tentang delegasi dalam dokumentasi Unity .

Tambahkan metode berikut:

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

Ketika objek game dihancurkan, Unity secara otomatis memanggil metode ini dan memeriksa delegasi untuk ketidaksetaraan null . Dalam kasus kami, kami menyebutnya dengan gameObject sebagai parameter. Ini memungkinkan semua responden yang terdaftar sebagai delegasi mengetahui bahwa musuh dihancurkan.

Simpan file dan kembali ke Unity.

Kami memberi monster izin untuk membunuh


Dan sekarang monster dapat mendeteksi musuh dalam radius aksi mereka. Tambahkan skrip C # baru ke Rakasa Rakasa dan beri nama ShootEnemies .

Buka ShootEnemies.cs di IDE dan tambahkan konstruksi berikut untuk mengakses Generics .

 using System.Collections.Generic; 

Tambahkan variabel untuk melacak semua musuh dalam jangkauan:

 public List<GameObject> enemiesInRange; 

Di dalam enemiesInRange kami akan menyimpan semua musuh dalam jangkauan.

Inisialisasi bidang di Start() .

 enemiesInRange = new List<GameObject>(); 

Pada awalnya, tidak ada musuh dalam radius aksi, jadi kami membuat daftar kosong.

Isi daftar enemiesInRange ! Tambahkan kode berikut ke skrip:

 // 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. Di OnEnemyDestroy kami menghapus musuh dari enemiesInRange . Ketika musuh OnTriggerEnter2D pemicu di sekitar monster, OnTriggerEnter2D .
  2. Kemudian kita menambahkan musuh ke daftar enemiesInRange dan menambahkan acara OnEnemyDestroy . Jadi kami menjamin bahwa setelah penghancuran musuh OnEnemyDestroy akan dipanggil. Kami tidak ingin monster menghabiskan amunisi pada musuh yang mati, bukan?
  3. Di OnTriggerExit2D kami menghapus musuh dari daftar dan membatalkan pendaftaran delegasi. Sekarang kita tahu musuh mana yang ada dalam jangkauan.

Simpan file dan luncurkan game di Unity. Untuk memastikan semuanya berfungsi, posisikan monster, pilih dan ikuti perubahan dalam daftar enemiesInRange di enemiesInRange .

Pemilihan target


Monster sekarang tahu musuh mana yang berada dalam jangkauan. Tapi apa yang akan mereka lakukan ketika ada beberapa musuh dalam radius?

Tentu saja, mereka akan menyerang yang paling dekat dengan hati!

Buka skrip IDE MoveEnemy.cs dan tambahkan metode baru yang menghitung monster ini:

 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; } 

Kode menghitung panjang jalur yang belum dilalui musuh. Untuk melakukan ini, ia menggunakan Distance , yang dihitung sebagai jarak antara dua instance dari Vector3 .

Kami akan menggunakan metode ini nanti untuk mencari tahu target mana yang akan diserang. Namun, sementara monster kita tidak bersenjata dan tidak berdaya, jadi pertama-tama kita akan melakukannya.

Simpan file dan kembali ke Unity untuk mulai mengatur shell Anda.

Mari kita berikan kerang monster. Banyak sekali kerang!


Seret dari Browser Proyek ke adegan Gambar / Objek / Bullet1 . Atur posisi pada z ke -2 - posisi pada x dan y tidak penting, karena kita mengaturnya setiap kali kita membuat instance baru proyektil ketika program dieksekusi.

Tambahkan skrip C # baru yang disebut BulletBehavior , dan kemudian di IDE tambahkan variabel berikut ke dalamnya:

 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; 

speed menentukan kecepatan proyektil; damage jelas dari namanya.

target , startPosition dan targetPosition menentukan arah proyektil.

distance dan startTime melacak posisi proyektil saat ini. gameManager memberi hadiah kepada pemain saat dia membunuh musuh.

Tetapkan nilai-nilai variabel ini di Start() :

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

startTimekami menetapkan nilai waktu saat ini dan menghitung jarak antara posisi awal dan target. Juga, seperti biasa, kita dapatkan GameManagerBehavior.

Untuk mengontrol pergerakan proyektil, tambahkan Update()kode berikut:

 // 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. Kami menghitung posisi baru proyektil, menggunakan Vector3.Lerpuntuk interpolasi antara posisi awal dan akhir.
  2. Jika proyektil mencapai targetPosition, maka kami memeriksa untuk melihat apakah itu masih ada target.
  3. Kami mendapatkan komponen HealthBartarget dan mengurangi kesehatannya dengan ukuran damageproyektil.
  4. Jika kesehatan musuh berkurang menjadi nol, maka kami menghancurkannya, mereproduksi efek suara dan memberi penghargaan kepada pemain untuk akurasi.

Simpan file dan kembali ke Unity.

Kami membuat kerang besar


Bukankah lebih bagus jika monster itu mulai menembakkan lebih banyak peluru pada level tinggi? Untungnya, ini mudah diimplementasikan.

Seret objek game Bullet1 dari Hirarki ke tab Project untuk membuat prefab proyektil. Hapus objek asli dari tempat kejadian - kita tidak lagi membutuhkannya.

Gandakan cetakan Bullet1 dua kali . Beri nama salinan Bullet2 dan Bullet3 .

Pilih Bullet2 . Di Inspektur, setel bidang Sprite dari komponen Sprite Renderer ke Images / Objects / Bullet2. Jadi kita akan membuat Bullet2 sedikit lebih dari Bullet1.

Ulangi prosedur untuk mengubah sprite dari prefab Bullet3 ke Gambar / Objek / Bullet3 .

Lebih lanjut dalam Perilaku Bullet kita akan menyesuaikan jumlah kerusakan yang disebabkan oleh peluru.

Pilih prefab Bullet1 di tab Project . Di Inspektur, Anda akan melihat Bullet Behavior (Script) , di mana Anda dapat mengatur Damage menjadi 10 untuk Bullet1 , 15 untuk Bullet2 dan 20 untuk Bullet3 - atau nilai lain yang Anda suka.

Catatan : Saya mengubah nilai sehingga pada tingkat yang lebih tinggi harga untuk kerusakan menjadi lebih tinggi. Ini mencegah peningkatan dari memungkinkan pemain untuk meng-upgrade monster pada poin terbaik.


Kerang pabrikan - ukuran bertambah dengan level

Mengubah tingkat kerang


Tetapkan shell yang berbeda untuk berbagai level monster, sehingga monster yang lebih kuat menghancurkan musuh lebih cepat.

Buka MonsterData.cs di IDE dan tambahkan ke MonsterLevelvariabel berikut:

 public GameObject bullet; public float fireRate; 

Jadi kami mengatur cetakan proyektil dan frekuensi api untuk setiap tingkat monster. Simpan file dan kembali ke Unity untuk menyelesaikan pengaturan monster.

Pilih Rakasa Rakasa di Browser Proyek . Di Inspektur, perluas Level di komponen Monster Data (Script) . Setel Fire Rate masing - masing item menjadi 1 . Kemudian atur parameter Bullet dari Elemen 0, 1, dan 2 ke Bullet1 , Bullet2, dan Bullet3 .

Level monster harus ditetapkan sebagai berikut:


Kerang membunuh musuh? Ya! Mari kita buka apinya!

Api terbuka


Buka ShootEnemies.cs di IDE dan tambahkan variabel berikut:

 private float lastShotTime; private MonsterData monsterData; 

Seperti namanya, variabel-variabel ini melacak waktu tembakan monster terakhir, serta struktur MonsterDatayang berisi informasi tentang jenis kulit monster, frekuensi api, dan sebagainya.

Tetapkan nilai bidang ini di Start():

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

Di sini kita menetapkan lastShotTimenilai waktu saat ini dan mendapatkan akses ke komponen MonsterDataobjek ini.

Tambahkan metode berikut untuk menerapkan pemotretan:

 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. Kami mendapatkan posisi awal dan target peluru. Atur posisi z sama dengan z bulletPrefab. Sebelumnya, kami menetapkan posisi cetakan proyektil di z sehingga proyektil muncul di bawah rakasa penembakan, tetapi di atas musuh.
  2. Kami membuat instance dari shell baru menggunakan yang bulletPrefabsesuai MonsterLevel. Tetapkan startPositiondan targetPositionproyektil.
  3. Kami membuat permainan lebih menarik: ketika monster menembak, mulailah animasi dari penembakan dan mainkan suara laser.

Menyatukan semuanya


Sudah waktunya untuk menyatukan semuanya. Tentukan target dan buat monster melihatnya.


Dalam skrip ShootEnemies.cs, tambahkan ke Update()kode ini:

 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)); } 

Pertimbangkan kode ini selangkah demi selangkah.

  1. Tentukan tujuan monster itu. Kami mulai pada jarak maksimum yang dimungkinkan di minimalEnemyDistance. Kami berkeliling dalam siklus semua musuh dalam jangkauan dan membuat musuh menjadi target baru jika jaraknya ke cookie kurang dari yang terkecil saat ini.
  2. Kami memanggil Shootjika waktu yang berlalu lebih besar dari frekuensi tembakan monster dan mengatur lastShotTimenilai waktu saat ini.
  3. Kami menghitung sudut rotasi antara monster dan targetnya. Kami memutar monster ke sudut ini. Sekarang dia akan selalu melihat target.

Simpan file dan luncurkan game di Unity. Monster akan mulai mati-matian melindungi cookie. Kami akhirnya selesai!

Ke mana harus pergi selanjutnya


Proyek yang sudah selesai dapat diunduh dari sini .

Kami melakukan pekerjaan yang baik dalam tutorial ini dan sekarang kami memiliki permainan yang hebat.

Berikut adalah beberapa ide untuk pengembangan proyek lebih lanjut:

  • Lebih banyak jenis musuh dan monster
  • Rute musuh yang berbeda
  • Level permainan yang berbeda

Masing-masing aspek ini akan membutuhkan perubahan minimal dan dapat membuat permainan lebih menyenangkan. Jika Anda membuat game baru berdasarkan tutorial ini, saya akan dengan senang hati memainkannya, jadi silakan bagikan tautannya.

Pikiran-pikiran yang menarik tentang membuat game mengenai pertahanan menara dapat ditemukan dalam wawancara ini .

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


All Articles