[
Bagian pertama dan
kedua tutorial]
- Kami menempatkan di bidang menara.
- Kami membidik musuh dengan bantuan fisika.
- Kami melacak mereka selagi mungkin.
- Kami menembak mereka dengan sinar laser.
Ini adalah bagian ketiga dari serangkaian tutorial tentang cara membuat genre menara pertahanan sederhana. Ini menggambarkan penciptaan menara, membidik dan menembak musuh.
Tutorial dibuat di Unity 2018.3.0f2.
Mari kita panaskan musuh.Penciptaan menara
Dinding hanya memperlambat musuh, menambah panjang jalan yang harus dilalui. Tetapi tujuan permainan ini adalah untuk menghancurkan musuh sebelum mereka mencapai titik akhir. Masalah ini diselesaikan dengan menempatkan menara di lapangan yang akan menembak mereka.
Konten Ubin
Towers adalah tipe lain dari konten ubin, jadi
GameTileContent
tambahkan entri untuk mereka di
GameTileContent
.
public enum GameTileContentType { Empty, Destination, Wall, SpawnPoint, Tower€ }
Dalam tutorial ini, kami hanya akan mendukung satu jenis menara, yang dapat diimplementasikan dengan memberikan satu tautan ke prefab menara kepada
GameTileContentFactory
, sebuah instance yang juga dapat dibuat melalui
Get
.
[SerializeField] GameTileContent towerPrefab = default; public GameTileContent Get (GameTileContentType type) { switch (type) { … case GameTileContentType.Tower€: return Get(towerPrefab); } … }
Tetapi menara harus menembak, sehingga kondisinya perlu diperbarui dan mereka membutuhkan kode mereka sendiri. Buat kelas
Tower
untuk tujuan ini yang memperluas kelas
GameTileContent
.
using UnityEngine; public class Tower : GameTileContent {}
Anda dapat membuat prefab menara memiliki komponennya sendiri dengan mengubah jenis bidang pabrik ke
Tower
. Karena kelas masih dianggap sebagai
GameTileContent
, tidak ada lagi yang perlu diubah.
Tower towerPrefab = default;
Cetakan rumah
Buat cetakan untuk menara. Anda bisa mulai dengan menduplikasi prefab dinding dan mengganti komponen
GameTileContent
dengan komponen
Tower
, dan kemudian ubah tipenya menjadi
Tower . Untuk membuat menara sesuai dengan dinding, simpan kubus dinding sebagai dasar menara. Kemudian letakkan kubus lain di atasnya. Saya memberinya skala 0,5. Letakkan kubus lain di atasnya, menunjukkan menara, bagian ini akan bertujuan dan menembak musuh.
Tiga kubus membentuk menara.Menara akan berputar, dan karena memiliki collider, itu akan dilacak oleh mesin fisik. Tetapi kita tidak perlu begitu tepat, karena kita menggunakan penghubung menara hanya untuk memilih sel. Ini dapat dilakukan kira-kira. Lepaskan collider kubus menara dan ubah collider menara kubus sehingga menutupi kedua kubus.
Menara kubus collider.Menara akan menembakkan sinar laser. Ini dapat divisualisasikan dalam banyak cara, tetapi kami hanya menggunakan kubus transparan, yang akan kami regangkan untuk membentuk balok. Setiap menara harus memiliki balok sendiri, jadi tambahkan ke prefab menara. Tempatkan di dalam turret sehingga tersembunyi secara default dan berikan skala yang lebih kecil, misalnya 0,2. Mari kita menjadikannya anak dari root prefab, bukan kubus menara.
Kubus tersembunyi dari sinar laser.Buat bahan yang cocok untuk sinar laser. Saya hanya menggunakan bahan hitam tembus standar dan mematikan semua pantulan, dan juga memberinya warna merah.
Bahan dari sinar laser.Pastikan sinar laser tidak memiliki collider, dan matikan juga cast dan shadow-nya.
Sinar laser tidak berinteraksi dengan bayangan.Setelah menyelesaikan pembuatan prefab menara, kami akan menambahkannya ke pabrik.
Pabrik dengan menara.Penempatan menara
Kami akan menambah dan menghapus menara menggunakan metode switching lain. Anda cukup menduplikasi
GameBoard.ToggleWall
dengan mengubah nama metode dan tipe konten.
public void ToggleTower (GameTile tile) { if (tile.Content.Type == GameTileContentType.Tower€) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else if (tile.Content.Type == GameTileContentType.Empty) { tile.Content = contentFactory.Get(GameTileContentType.Tower€); if (!FindPaths()) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } } }
Di
Game.HandleTouch
, menahan tombol shift akan beralih menara daripada dinding.
void HandleTouch () { GameTile tile = board.GetTile(TouchRay); if (tile != null) { if (Input.GetKey(KeyCode.LeftShift)) { board.ToggleTower(tile); } else { board.ToggleWall(tile); } } }
Menara di lapangan.Pemblokiran jalur
Sejauh ini, hanya dinding yang dapat menghalangi pencarian jalan, sehingga musuh bergerak melalui menara. Mari kita tambahkan properti tambahan ke
GameTileContent
yang menunjukkan apakah konten memblokir jalan. Jalan itu diblokir jika itu adalah dinding atau menara.
public bool BlocksPath => Type == GameTileContentType.Wall || Type == GameTileContentType.Tower€;
Gunakan properti ini di
GameTile.GrowPathTo
alih-alih memeriksa jenis konten.
GameTile GrowPathTo (GameTile neighbor, Direction direction) { … return
Sekarang jalan terhalang oleh dinding dan menara.Pasang kembali dinding
Kemungkinan besar, pemain akan sering mengganti dinding dengan menara. Ini akan merepotkan baginya untuk menghapus dinding terlebih dahulu, dan selain itu, musuh dapat menembus celah yang muncul sementara ini. Anda dapat menerapkan penggantian langsung dengan memaksa
GameBoard.ToggleTower
untuk memeriksa apakah dinding saat ini ada di ubin. Jika demikian, maka segera ganti dengan menara. Dalam hal ini, kita tidak perlu mencari cara lain, karena ubin masih memblokirnya.
public void ToggleTower (GameTile tile) { if (tile.Content.Type == GameTileContentType.Tower) { tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else if (tile.Content.Type == GameTileContentType.Empty) { … } else if (tile.Content.Type == GameTileContentType.Wall) { tile.Content = contentFactory.Get(GameTileContentType.Tower); } }
Kami membidik musuh
Sebuah menara dapat memenuhi tugasnya hanya ketika menemukan musuh. Setelah menemukan musuh, dia harus memutuskan bagian mana yang akan dibidik.
Membidik titik
Untuk mendeteksi target, kami akan menggunakan mesin fisika. Seperti dalam kasus collider menara, kita tidak perlu collider musuh untuk bertepatan dengan bentuknya. Anda dapat memilih collider yang paling sederhana, yaitu sphere. Setelah mendeteksi musuh, kita akan menggunakan posisi objek permainan dengan collider yang melekat padanya sebagai titik untuk membidik.
Kami tidak dapat melampirkan collider ke objek root musuh, karena itu tidak selalu bertepatan dengan posisi model dan akan membuat menara mengarah ke tanah. Artinya, Anda perlu menempatkan collider di suatu tempat pada model. Mesin fisika akan memberi kita tautan ke objek ini, yang dapat kita gunakan untuk membidik, tetapi kita masih membutuhkan akses ke komponen
Enemy
dari objek root. Untuk menyederhanakan tugas, mari kita buat komponen
TargetPoint
. Mari kita berikan properti untuk tugas pribadi dan penerimaan publik dari komponen
Enemy
, dan properti lain untuk mendapatkan posisinya di dunia.
using UnityEngine; public class TargetPoint : MonoBehaviour { public Enemy Enemy€ { get; private set; } public Vector3 Position => transform.position; }
Mari berikan metode
Awake
yang mengatur tautan ke komponen
Enemy
. Langsung ke objek root menggunakan
transform.root
. Jika komponen
Enemy
tidak ada, maka kami membuat kesalahan saat membuat musuh, jadi mari kita tambahkan pernyataan untuk ini.
void Awake () { Enemy€ = transform.root.GetComponent<Enemy>(); Debug.Assert(Enemy€ != null, "Target point without Enemy root!", this); }
Selain itu, collider harus dilampirkan ke objek game yang sama dengan
TargetPoint
dilampirkan.
Debug.Assert(Enemy€ != null, "Target point without Enemy root!", this); Debug.Assert( GetComponent<SphereCollider>() != null, "Target point without sphere collider!", this );
Tambahkan komponen dan collider ke kubus prefab musuh. Ini akan membuat menara mengarah ke pusat kubus. Kami menggunakan collider bola dengan jari-jari 0,25. Kubus memiliki skala 0,5, sehingga radius sebenarnya dari collider adalah 0,125. Berkat ini, musuh harus secara visual melintasi lingkaran jangkauan menara, dan hanya setelah beberapa waktu tujuan yang sebenarnya menjadi. Ukuran collider juga dipengaruhi oleh skala acak musuh, sehingga ukurannya dalam game juga akan sedikit berbeda.
Musuh dengan titik bidik dan collider di kubus.Lapisan Musuh
Menara hanya peduli pada musuh, dan mereka tidak membidik yang lain, jadi kami akan menempatkan semua musuh di lapisan yang terpisah. Kami akan menggunakan layer 9. Ubah namanya menjadi
Musuh di jendela
Layers & Tag , yang dapat dibuka melalui opsi
Edit Layers di menu drop-down
Layers di sudut kanan atas editor.
Layer 9 akan digunakan untuk musuh.Lapisan ini diperlukan hanya untuk pengenalan musuh, dan bukan untuk interaksi fisik. Mari kita tunjukkan dengan menonaktifkannya di
Layer Collision Matrix , yang terletak di panel
Fisika dari parameter proyek.
Matriks tabrakan lapisan.Pastikan bahwa objek game dari titik bidik berada di lapisan yang diinginkan. Sisa cetakan musuh mungkin ada di lapisan lain, tetapi akan lebih mudah untuk mengoordinasikan semuanya dan menempatkan seluruh cetakan di lapisan
Musuh . Jika Anda mengubah lapisan objek root, Anda akan diminta untuk mengubah lapisan untuk semua objek anaknya.
Musuh di lapisan kanan.Mari kita tambahkan pernyataan bahwa
TargetPoint
benar-benar di lapisan kanan.
void Awake () { … Debug.Assert(gameObject.layer == 9, "Target point on wrong layer!", this); }
Selain itu, tindakan pemain harus diabaikan oleh collider musuh. Ini bisa dicapai dengan menambahkan argumen layer mask ke
Physics.Raycast
di
GameBoard.GetTile
. Metode ini memiliki bentuk yang mengambil jarak ke balok dan layer mask sebagai argumen tambahan. Kami akan memberikan jarak maksimum dan layer mask secara default, yaitu 1.
public GameTile GetTile (Ray ray) { if (Physics.Raycast(ray, out RaycastHit hit, float.MaxValue, 1)) { … } return null; }
Bukankah seharusnya layer mask menjadi 0?Indeks lapisan default adalah nol, tapi kami melewati layer mask. Topeng mengubah bit individu dari integer ke 1 jika lapisan perlu dihidupkan. Dalam hal ini, Anda hanya perlu menetapkan bit pertama, yaitu yang paling signifikan, yang berarti 2 0 , yang sama dengan 1.
Memperbarui Konten Ubin
Towers hanya dapat melakukan tugasnya ketika statusnya diperbarui. Hal yang sama berlaku untuk isi seluruh ubin, meskipun sisanya tidak melakukan apa-apa sejauh ini. Oleh karena itu, tambahkan metode virtual
GameUpdate
ke
GameUpdate
, yang tidak melakukan apa-apa secara default.
public virtual void GameUpdate () {}
Mari kita buat
Tower
mendefinisikan ulang, bahkan jika untuk saat ini hanya menampilkan di konsol bahwa ia sedang mencari target.
public override void GameUpdate () { Debug.Log("Searching for target..."); }
GameBoard
berurusan dengan ubin dan isinya, sehingga ia juga akan melacak konten apa yang perlu diperbarui. Untuk melakukan ini, tambahkan daftar ke dalamnya dan metode
GameUpdate
publik, yang memperbarui semua yang ada dalam daftar.
List<GameTileContent> updatingContent = new List<GameTileContent>(); … public void GameUpdate () { for (int i = 0; i < updatingContent.Count; i++) { updatingContent[i].GameUpdate(); } }
Dalam tutorial kami, Anda hanya perlu memperbarui menara. Ubah
ToggleTower
sehingga menambah dan menghapus konten jika perlu. Jika konten lain juga diperlukan, kami akan memerlukan pendekatan yang lebih umum, tetapi untuk saat ini, ini sudah cukup.
public void ToggleTower (GameTile tile) { if (tile.Content.Type == GameTileContentType.Tower) { updatingContent.Remove(tile.Content); tile.Content = contentFactory.Get(GameTileContentType.Empty); FindPaths(); } else if (tile.Content.Type == GameTileContentType.Empty) { tile.Content = contentFactory.Get(GameTileContentType.Tower);
Untuk membuat ini berfungsi, sekarang cukup bagi kami untuk memperbarui bidang di
Game.Update
. Kami akan memperbarui bidang setelah musuh. Berkat ini, menara akan dapat mengarahkan tepat di mana musuh berada. Jika kami melakukan sebaliknya, menara akan mengarah ke tempat musuh berada di bingkai terakhir.
void Update () { … enemies.GameUpdate(); board.GameUpdate(); }
Membidik jangkauan
Menara memiliki radius bidik terbatas. Mari kita membuatnya khusus dengan menambahkan bidang ke kelas
Tower
. Jarak diukur dari pusat ubin menara, jadi pada kisaran 0,5 hanya akan mencakup ubin sendiri. Oleh karena itu, kisaran minimum dan standar yang masuk akal adalah 1,5, yang mencakup sebagian besar ubin tetangga.
[SerializeField, Range(1.5f, 10.5f)] float targetingRange = 1.5f;
Membidik kisaran 2.5.Mari memvisualisasikan rentang dengan alat. Kita tidak perlu melihatnya terus-menerus, oleh karena itu kita akan membuat metode
OnDrawGizmosSelected
yang hanya dipanggil untuk objek yang dipilih. Kami menggambar kerangka kuning bola dengan jari-jari yang sama dengan jarak dan terpusat relatif terhadap menara. Tempatkan sedikit di atas tanah agar selalu terlihat jelas.
void OnDrawGizmosSelected () { Gizmos.color = Color.yellow; Vector3 position = transform.localPosition; position.y += 0.01f; Gizmos.DrawWireSphere(position, targetingRange); }
Gizmo membidik jangkauan.Sekarang kita bisa melihat musuh mana yang merupakan target yang terjangkau untuk masing-masing menara. Tetapi memilih menara di jendela adegan tidak nyaman, karena kita harus memilih salah satu kubus anak, dan kemudian beralih ke objek root menara. Jenis konten ubin lainnya juga mengalami masalah yang sama. Kami dapat memaksa pemilihan akar konten
GameTileContent
di jendela adegan dengan menambahkan atribut
SelectionBase
ke
GameTileContent
.
[SelectionBase] public class GameTileContent : MonoBehaviour { … }
Target penangkapan
Tambahkan bidang
TargetPoint
ke kelas
Tower
sehingga dapat melacak target yang ditangkap. Kemudian kami
GameUpdate
untuk memanggil metode
AquireTarget
baru, yang mengembalikan informasi tentang apakah ia menemukan target. Setelah terdeteksi, itu akan menampilkan pesan di konsol.
TargetPoint target; public override void GameUpdate () { if (AcquireTarget()) { Debug.Log("Acquired target!"); } }
Di
AcquireTarget
kami mendapatkan semua target yang tersedia dengan memanggil
Physics.OverlapSphere
dengan posisi menara dan rentang sebagai argumen. Hasilnya akan menjadi array
Collider
berisi semua colliders yang bersentuhan dengan bola. Jika panjang array positif, maka setidaknya ada satu titik tujuan, dan kami cukup memilih yang pertama. Ambil komponen
TargetPoint
-nya, yang harus selalu ada, tetapkan ke bidang target dan laporkan keberhasilannya. Kalau tidak, kami menghapus target dan melaporkan kegagalan.
bool AcquireTarget () { Collider[] targets = Physics.OverlapSphere( transform.localPosition, targetingRange ); if (targets.Length > 0) { target = targets[0].GetComponent<TargetPoint>(); Debug.Assert(target != null, "Targeted non-enemy!", targets[0]); return true; } target = null; return false; }
Kami dijamin mendapatkan poin bidikan yang tepat, jika memperhitungkan colliders hanya pada lapisan musuh. Ini adalah layer 9, jadi kami akan melewati layer mask yang sesuai.
const int enemyLayerMask = 1 << 9; … bool AcquireTarget () { Collider[] targets = Physics.OverlapSphere( transform.localPosition, targetingRange, enemyLayerMask ); … }
Bagaimana cara kerja bitmask ini?Karena layer musuh memiliki indeks 9, bit kesepuluh dari bitmask harus memiliki nilai 1. Ini terkait dengan bilangan bulat 2 9 , yaitu 512. Tetapi catatan bitmask semacam itu tidak intuitif. Kita juga dapat menulis biner literal, misalnya 0b10_0000_0000
, tetapi kemudian kita harus menghitung nol. Dalam hal ini, entri yang paling mudah adalah menggunakan operator shift kiri <<
, yang menggeser bit ke kiri. yang sesuai dengan angka dalam kekuatan dua.
Anda dapat memvisualisasikan target yang ditangkap dengan menggambar garis alat antara posisi menara dan target.
void OnDrawGizmosSelected () { … if (target != null) { Gizmos.DrawLine(position, target.Position); } }
Visualisasi tujuan.Mengapa tidak menggunakan metode seperti OnTriggerEnter?Keuntungan dari memeriksa sasaran lintas sektor secara manual adalah bahwa kita hanya dapat melakukan ini bila perlu. Tidak ada alasan untuk memeriksa target jika menara sudah memilikinya. Selain itu, dengan memperoleh semua sasaran potensial sekaligus, kami tidak harus memproses daftar sasaran potensial untuk setiap menara, yang terus berubah.
Kunci Target
Target yang dipilih untuk ditangkap tergantung pada urutan di mana mereka diwakili oleh mesin fisik, yaitu, pada kenyataannya, itu sewenang-wenang. Oleh karena itu, akan terlihat bahwa target yang ditangkap berubah tanpa alasan. Setelah menara menerima target, lebih logis baginya untuk melacaknya, dan tidak beralih ke yang lain. Tambahkan metode
TrackTarget
yang mengimplementasikan pelacakan seperti itu dan mengembalikan informasi tentang apakah itu berhasil. Pertama, kami akan memberi tahu Anda jika target ditangkap.
bool TrackTarget () { if (target == null) { return false; } return true; }
Kami akan memanggil metode ini di
GameUpdate
dan hanya ketika mengembalikan false kita akan memanggil
AcquireTarget
. Jika metode itu kembali benar, maka kita memiliki tujuan. Ini dapat dilakukan dengan menempatkan kedua pemanggilan metode dalam cek
if
dengan operator ATAU, karena jika operan pertama mengembalikan
true
, yang kedua tidak akan diperiksa, dan panggilan akan dilewatkan. Operator AND bertindak dengan cara yang sama.
public override void GameUpdate () { if (TrackTarget() || AcquireTarget()) { Debug.Log("Locked on target!"); } }
Melacak sasaran.Akibatnya, menara ditetapkan pada target sampai mencapai titik akhir dan dihancurkan. Jika Anda menggunakan musuh berulang kali, maka alih-alih Anda perlu memeriksa kebenaran tautan, seperti yang dilakukan dengan tautan ke angka yang diproses dalam serangkaian tutorial
Manajemen Objek .
Untuk melacak target hanya ketika berada dalam jangkauan,
TrackTarget
harus melacak jarak antara menara dan target. Jika melebihi nilai kisaran, maka target harus diatur ulang dan kembali salah. Anda dapat menggunakan metode
Vector3.Distance
untuk pemeriksaan ini.
bool TrackTarget () { if (target == null) { return false; } Vector3 a = transform.localPosition; Vector3 b = target.Position; if (Vector3.Distance(a, b) > targetingRange) { target = null; return false; } return true; }
Namun, kode ini tidak memperhitungkan radius collider. Karena itu, sebagai akibatnya, menara mungkin kehilangan target, lalu menangkapnya lagi, hanya untuk berhenti melacaknya di frame berikutnya, dan seterusnya. Kita dapat menghindari ini dengan menambahkan radius collider ke rentang.
if (Vector3.Distance(a, b) > targetingRange + 0.125f) { … }
Ini memberi kita hasil yang benar, tetapi hanya jika skala musuh tidak berubah. Karena kami memberikan skala acak kepada setiap musuh, kami harus memperhitungkannya saat mengubah kisaran. Untuk melakukan ini, kita perlu mengingat skala yang diberikan oleh
Enemy
dan membukanya menggunakan properti pengambil.
public float Scale { get; private set; } … public void Initialize (float scale, float speed, float pathOffset) { Scale = scale; … }
Sekarang kita dapat memeriksa rentang yang benar di
Tower.TrackTarget
.
if (Vector3.Distance(a, b) > targetingRange + 0.125f * target.Enemy€.Scale) { … }
Kami menyinkronkan fisika
Semuanya tampak berfungsi dengan baik, tetapi menara yang bisa mengarah ke tengah lapangan mampu menangkap target yang seharusnya berada di luar jangkauan. Mereka tidak akan dapat melacak tujuan-tujuan ini, sehingga mereka hanya menetapkannya untuk satu frame.
Membidik salah.Ini terjadi karena keadaan mesin fisik tidak sempurna disinkronkan dengan keadaan permainan. Contoh dari semua musuh diciptakan pada asal usul dunia, yang bertepatan dengan pusat lapangan. Kemudian kita memindahkan mereka ke titik penciptaan, tetapi mesin fisika tidak langsung mengetahuinya.
Anda dapat mengaktifkan sinkronisasi sesaat yang terjadi ketika Anda mengubah transformasi objek dengan mengatur
Physics.autoSyncTransforms
menjadi
true
. Tetapi secara default dinonaktifkan, karena jauh lebih efisien untuk menyinkronkan semuanya bersama-sama dan jika perlu. Dalam kasus kami, sinkronisasi hanya diperlukan saat memperbarui status menara. Kita bisa menjalankannya dengan memanggil
Physics.SyncTransforms
antara musuh dan pembaruan bidang di
Game.Update
.
void Update () { … enemies.GameUpdate(); Physics.SyncTransforms(); board.GameUpdate(); }
Abaikan ketinggian
Faktanya, gameplay kami berlangsung dalam 2D. Jadi mari kita ubah
Tower
sehingga ketika membidik dan melacak hanya memperhitungkan koordinat X dan Z. Mesin fisik bekerja dalam ruang 3D, tetapi pada dasarnya kita dapat melakukan
AcquireTarget
dalam 2D: rentangkan bola sehingga mencakup semua colliders, terlepas dari dari posisi vertikal mereka. Ini dapat dilakukan dengan menggunakan kapsul alih-alih bola, titik kedua di mana akan menjadi beberapa unit di atas tanah (misalnya, tiga).
bool AcquireTarget () { Vector3 a = transform.localPosition; Vector3 b = a; by += 3f; Collider[] targets = Physics.OverlapCapsule( a, b, targetingRange, enemyLayerMask ); … }
Apakah tidak mungkin menggunakan mesin 2D fisik?, XZ, 2D- XY. , , 2D- . 3D-.
Juga perlu untuk berubah TrackTarget
. Tentu saja, kita dapat menggunakan vektor 2D dan Vector2.Distance
, tetapi mari kita lakukan perhitungan sendiri dan alih-alih kita akan membandingkan kuadrat jarak, ini sudah cukup. Jadi kita menyingkirkan operasi penghitungan akar kuadrat. bool TrackTarget () { if (target == null) { return false; } Vector3 a = transform.localPosition; Vector3 b = target.Position; float x = ax - bx; float z = az - bz; float r = targetingRange + 0.125f * target.Enemy€.Scale; if (x * x + z * z > r * r) { target = null; return false; } return true; }
Bagaimana cara perhitungan matematika ini bekerja?2D- , . , . , , .
Hindari alokasi memori
Physics.OverlapCapsule
, . ,
OverlapCapsuleNonAlloc
. . . , 1.
OverlapCapsuleNonAlloc
, , .
static Collider[] targetsBuffer = new Collider[1]; … bool AcquireTarget () { Vector3 a = transform.localPosition; Vector3 b = a; by += 2f; int hits = Physics.OverlapCapsuleNonAlloc( a, b, targetingRange, targetsBuffer, enemyLayerMask ); if (hits > 0) { target = targetsBuffer[0].GetComponent<TargetPoint>(); Debug.Assert(target != null, "Targeted non-enemy!", targetsBuffer[0]); return true; } target = null; return false; }
, , . , .
,
Tower
Transform
. .
[SerializeField] Transform turret = default;
.GameUpdate
, . . ,
Transform.LookAt
.
public override void GameUpdate () { if (TrackTarget() || AcquireTarget()) {
.Tower
.
[SerializeField] Transform turret = default, laserBeam = default;
., . -, . , .
void Shoot () { Vector3 point = target.Position; turret.LookAt(point); laserBeam.localRotation = turret.localRotation; }
Kedua, kami skala sinar laser sehingga panjangnya sama dengan jarak antara titik asal lokal menara dan titik tujuan. Kami skala di sepanjang sumbu Z, yaitu, sumbu lokal diarahkan ke target. Untuk mempertahankan skala XY asli, kami menuliskan skala aslinya ketika kami membangun menara Awake. Vector3 laserBeamScale; void Awake () { laserBeamScale = laserBeam.localScale; } … void Shoot () { Vector3 point = target.Position; turret.LookAt(point); laserBeam.localRotation = turret.localRotation; float d = Vector3.Distance(turret.position, point); laserBeamScale.z = d; laserBeam.localScale = laserBeamScale; }
Ketiga, kami menempatkan sinar laser di tengah-tengah antara menara dan titik tujuan. laserBeam.localScale = laserBeamScale; laserBeam.localPosition = turret.localPosition + 0.5f * d * laserBeam.forward;
Penembakan laser.Apakah tidak mungkin membuat sinar laser menjadi anak menara?, , forward. , . .
Ini berfungsi saat turet ditetapkan pada target. Tetapi ketika tidak ada target, laser tetap aktif. Kita dapat mematikan layar laser dengan GameUpdate
mengatur skalanya ke 0. public override void GameUpdate () { if (TrackTarget() || AcquireTarget()) { Shoot(); } else { laserBeam.localScale = Vector3.zero; } }
Menara siaga tidak menyala.Kesehatan Musuh
. , . ,
Enemy
. , 100. , , .
float Health { get; set; } … public void Initialize (float scale, float speed, float pathOffset) { … Health = 100f * scale; }
,
ApplyDamage
, . , , .
public void ApplyDamage (float damage) { Debug.Assert(damage >= 0f, "Negative damage applied."); Health -= damage; }
, .
GameUpdate
.
public bool GameUpdate () { if (Health <= 0f) { OriginFactory.Reclaim(this); return false; } … }
Berkat ini, semua menara pada dasarnya akan menembak secara bersamaan, dan tidak pada gilirannya, yang akan memungkinkan mereka untuk beralih ke target lain jika menara sebelumnya menghancurkan musuh, yang juga mereka tuju.Kerusakan per detik
Sekarang kita perlu menentukan berapa banyak kerusakan yang akan dilakukan laser. Untuk melakukan ini, tambahkan ke Tower
bidang konfigurasi. Karena sinar laser memberikan kerusakan terus-menerus, kami akan menyatakannya sebagai kerusakan per detik. Kami Shoot
menerapkannya pada komponen Enemy
target dengan penggandaan pada waktu delta. [SerializeField, Range(1f, 100f)] float damagePerSecond = 10f; … void Shoot () { … target.Enemy.ApplyDamage(damagePerSecond * Time.deltaTime); }
Kerusakan setiap menara adalah 20 unit per detik.Membidik acak
, , . , , , . , .
, , . , , 100. , , .
static Collider[] targetsBuffer = new Collider[100];
.
bool AcquireTarget () { … if (hits > 0) { target = targetsBuffer[Random.Range(0, hits)].GetComponent<TargetPoint>(); … } target = null; return false; }
., « » - . .