[
Bagian pertama ,
kedua ,
ketiga dan
keempat tutorial]
- Mendukung musuh ukuran kecil, sedang dan besar.
- Buat skenario game dengan berbagai gelombang musuh.
- Pemisahan konfigurasi aset dan status permainan.
- Mulai, jeda, menang, kalahkan, dan percepat permainan.
- Buat skenario berulang tanpa henti.
Ini adalah bagian kelima dari serangkaian tutorial tentang cara membuat gim
pertahanan menara sederhana. Di dalamnya, kita akan belajar cara membuat skenario gameplay yang menghasilkan gelombang berbagai musuh.
Tutorial ini dibuat di Unity 2018.4.6f1.
Sudah cukup nyaman.Lebih banyak musuh
Tidak terlalu menarik untuk membuat kubus biru yang sama setiap saat. Langkah pertama untuk mendukung skenario gameplay yang lebih menarik adalah mendukung beberapa jenis musuh.
Konfigurasi musuh
Ada banyak cara untuk membuat musuh unik, tetapi kami tidak akan mempersulitnya: kami mengklasifikasikan mereka sebagai kecil, sedang dan besar. Untuk menandai mereka, buat enumerasi
EnemyType
.
public enum EnemyType { Small, Medium, Large }
Ubah
EnemyFactory
sehingga mendukung ketiga jenis musuh, bukan satu. Untuk ketiga musuh, bidang konfigurasi yang sama diperlukan, jadi kami menambahkan kelas
EnemyConfig
bersarang yang berisi semuanya, dan kemudian menambahkan tiga bidang konfigurasi jenis ini ke pabrik. Karena kelas ini hanya digunakan untuk konfigurasi dan kami tidak akan menggunakannya di tempat lain, Anda dapat dengan mudah mempublikasikan bidangnya sehingga pabrik dapat mengaksesnya.
EnemyConfig
sendiri tidak harus bersifat publik.
public class EnemyFactory : GameObjectFactory { [System.Serializable] class EnemyConfig { public Enemy prefab = default; [FloatRangeSlider(0.5f, 2f)] public FloatRange scale = new FloatRange(1f); [FloatRangeSlider(0.2f, 5f)] public FloatRange speed = new FloatRange(1f); [FloatRangeSlider(-0.4f, 0.4f)] public FloatRange pathOffset = new FloatRange(0f); } [SerializeField] EnemyConfig small = default, medium = default, large = default; … }
Mari kita juga membuat kesehatan dapat disesuaikan untuk setiap musuh, karena itu logis bahwa musuh besar memiliki lebih dari yang kecil.
[FloatRangeSlider(10f, 1000f)] public FloatRange health = new FloatRange(100f);
Tambahkan parameter tipe untuk
Get
sehingga Anda bisa mendapatkan tipe musuh tertentu, dan tipe standarnya adalah sedang. Kami akan menggunakan tipe untuk mendapatkan konfigurasi yang benar, yang mana metode terpisah berguna, dan kemudian membuat dan menginisialisasi musuh seperti sebelumnya, hanya dengan argumen kesehatan yang ditambahkan.
EnemyConfig GetConfig (EnemyType type) { switch (type) { case EnemyType.Small: return small; case EnemyType.Medium: return medium; case EnemyType.Large: return large; } Debug.Assert(false, "Unsupported enemy type!"); return null; } public Enemy Get (EnemyType type = EnemyType.Medium) { EnemyConfig config = GetConfig(type); Enemy instance = CreateGameObjectInstance(config.prefab); instance.OriginFactory = this; instance.Initialize( config.scale.RandomValueInRange, config.speed.RandomValueInRange, config.pathOffset.RandomValueInRange, config.health.RandomValueInRange ); return instance; }
Tambahkan parameter kesehatan yang diperlukan untuk
Enemy.Initialize
dan gunakan untuk mengatur kesehatan alih-alih menentukan ukuran musuh.
public void Initialize ( float scale, float speed, float pathOffset, float health ) { … Health = health; }
Kami menciptakan desain musuh yang berbeda
Anda dapat memilih apa desain dari ketiga musuh itu, tetapi dalam tutorial saya akan berusaha untuk kesederhanaan maksimum. Saya menggandakan cetakan asli musuh dan menggunakannya untuk ketiga ukuran, hanya mengubah bahan: kuning untuk kecil, biru untuk sedang dan merah untuk besar. Saya tidak mengubah skala pabrikan kubus, tetapi menggunakan konfigurasi skala pabrik untuk mengatur dimensi. Juga, tergantung pada ukurannya, saya meningkatkan kesehatan mereka dan mengurangi kecepatan.
Pabrik untuk kubus musuh dengan tiga ukuran.Cara tercepat adalah membuat ketiga jenis muncul dalam permainan dengan mengubah
Game.SpawnEnemy
sehingga ia mendapatkan tipe musuh acak daripada yang tengah.
void SpawnEnemy () { GameTile spawnPoint = board.GetSpawnPoint(Random.Range(0, board.SpawnPointCount)); Enemy enemy = enemyFactory.Get((EnemyType)(Random.Range(0, 3))); enemy.SpawnOn(spawnPoint); enemies.Add(enemy); }
Musuh dari berbagai jenis.Beberapa pabrik
Sekarang pabrik musuh menetapkan banyak tiga musuh. Pabrik yang ada menciptakan kubus dengan tiga ukuran, tetapi tidak ada yang mencegah kami membuat pabrik lain yang menciptakan sesuatu yang lain, misalnya, bola dengan tiga ukuran. Kami dapat mengubah musuh yang dibuat dengan menunjuk pabrik lain di game, sehingga beralih ke topik yang berbeda.
Musuh bulat.Gelombang musuh
Langkah kedua dalam membuat skenario gameplay akan menjadi penolakan pemijahan musuh dengan frekuensi konstan. Musuh harus dibuat dalam gelombang yang berurutan sampai skrip berakhir atau pemain kalah.
Urutan penciptaan
Satu gelombang musuh terdiri dari sekelompok musuh yang dibuat satu demi satu hingga gelombang selesai. Gelombang dapat berisi berbagai jenis musuh, dan keterlambatan antara kreasi mereka dapat bervariasi. Agar tidak menyulitkan implementasi, kita akan mulai dengan urutan pemijahan sederhana yang menciptakan tipe musuh yang sama dengan frekuensi konstan. Maka gelombang akan menjadi daftar sekuens semacam itu.
Untuk mengkonfigurasi setiap urutan, buat kelas
EnemySpawnSequence
. Karena cukup rumit, letakkan di file terpisah. Urutan harus tahu pabrik mana yang digunakan, jenis musuh apa yang harus dibuat, jumlah dan frekuensinya. Untuk menyederhanakan konfigurasi, kita akan membuat parameter terakhir menjadi jeda, yang menentukan berapa banyak waktu yang harus dilewati sebelum membuat musuh berikutnya. Perhatikan bahwa pendekatan ini memungkinkan Anda untuk menggunakan beberapa pabrik musuh dalam gelombang.
using UnityEngine; [System.Serializable] public class EnemySpawnSequence { [SerializeField] EnemyFactory factory = default; [SerializeField] EnemyType type = EnemyType.Medium; [SerializeField, Range(1, 100)] int amount = 1; [SerializeField, Range(0.1f, 10f)] float cooldown = 1f; }
Ombaknya
Gelombang adalah array sederhana dari urutan penciptaan musuh. Buat jenis
EnemyWave
EnemyWave untuk itu yang dimulai dengan satu urutan standar.
using UnityEngine; [CreateAssetMenu] public class EnemyWave : ScriptableObject { [SerializeField] EnemySpawnSequence[] spawnSequences = { new EnemySpawnSequence() }; }
Sekarang kita bisa membuat gelombang musuh. Sebagai contoh, saya membuat gelombang yang menghasilkan sekelompok musuh kubik, mulai dengan sepuluh yang kecil, dengan frekuensi dua per detik. Mereka diikuti oleh lima rata-rata, dibuat sekali per detik, dan, akhirnya, satu musuh besar dengan jeda lima detik.
Gelombang kubus yang meningkat.Bisakah saya menambahkan jeda antar urutan?Anda dapat menerapkannya secara tidak langsung. Misalnya, masukkan jeda empat detik antara kubus kecil dan menengah, kurangi jumlah kubus kecil menjadi satu, dan masukkan urutan satu kubus kecil dengan jeda empat detik.
Delay empat detik antara kubus kecil dan menengah. Skenario
Skenario gameplay dibuat dari urutan gelombang. Untuk ini, buat
GameScenario
aset
GameScenario
dengan satu larik gelombang, lalu gunakan untuk membuat skenario.
using UnityEngine; [CreateAssetMenu] public class GameScenario : ScriptableObject { [SerializeField] EnemyWave[] waves = {}; }
Sebagai contoh, saya membuat skenario dengan dua gelombang musuh kecil-menengah-besar (MSC), pertama dengan kubus, kemudian dengan bola.
Skenario dengan dua gelombang MSC.Gerakan urutan
Jenis aset digunakan untuk membuat skrip, tetapi karena ini adalah aset, mereka harus berisi data yang tidak berubah selama permainan. Namun, untuk memajukan skenario, kita perlu melacak status mereka. Salah satu caranya adalah menduplikasi aset yang digunakan dalam gim sehingga duplikat melacak kondisinya. Tetapi kita tidak perlu menduplikasi seluruh aset, cukup nyatakan saja dan tautan ke aset itu sudah cukup. Jadi mari kita buat kelas
State
terpisah, pertama untuk
EnemySpawnSequence
. Karena ini hanya berlaku untuk urutan, kami membuatnya bersarang. Ini valid hanya jika memiliki referensi ke urutan, jadi kami akan memberikannya metode konstruktor dengan parameter urutan.
Tipe status bertingkat yang mengacu pada urutannya. public class EnemySpawnSequence { … public class State { EnemySpawnSequence sequence; public State (EnemySpawnSequence sequence) { this.sequence = sequence; } } }
Ketika kita ingin mulai bergerak maju secara berurutan, kita memerlukan instance baru untuk ini. Tambahkan urutan ke metode
Begin
, yang membangun dan mengembalikan negara. Berkat ini, semua orang yang memanggil
Begin
akan bertanggung jawab untuk mencocokkan keadaan, dan urutan itu sendiri akan tetap tanpa kewarganegaraan. Bahkan mungkin untuk maju secara paralel beberapa kali dalam urutan yang sama.
public class EnemySpawnSequence { … public State Begin () => new State(this); public class State { … } }
Agar negara dapat bertahan setelah reboot panas, Anda harus membuatnya serializable.
[System.Serializable] public class State { … }
Kelemahan dari pendekatan ini adalah bahwa setiap kali kita menjalankan urutan, kita perlu membuat objek keadaan baru. Kita dapat menghindari alokasi memori dengan menjadikannya struktur alih-alih kelas. Ini normal asalkan kondisinya tetap kecil. Perlu diingat bahwa status adalah tipe nilai. Ketika ditransfer, itu disalin, jadi lacak di satu tempat.
[System.Serializable] public struct State { … }
Keadaan urutan hanya terdiri dari dua aspek: jumlah musuh yang dihasilkan dan kemajuan waktu jeda. Kami menambahkan metode
Progress
, yang meningkatkan nilai jeda per delta waktu, dan kemudian menyetel ulang ketika nilai yang dikonfigurasi tercapai, mirip dengan apa yang terjadi dengan waktu pembuatan di
Game.Update
. Kami akan menambah jumlah musuh setiap kali ini terjadi. Selain itu, nilai jeda harus dimulai dengan nilai maksimum sehingga urutan menciptakan musuh tanpa jeda di awal.
int count; float cooldown; public State (EnemySpawnSequence sequence) { this.sequence = sequence; count = 0; cooldown = sequence.cooldown; } public void Progress () { cooldown += Time.deltaTime; while (cooldown >= sequence.cooldown) { cooldown -= sequence.cooldown; count += 1; } }
Negara hanya berisi data yang diperlukan.Bisakah saya mengakses EnemySpawnSequence.cooldown dari State?Ya, karena State
diatur dalam cakupan yang sama. Oleh karena itu, tipe bersarang tahu tentang anggota pribadi dari tipe yang mengandungnya.
Kemajuan harus berlanjut sampai jumlah musuh yang diinginkan dibuat dan jeda berakhir. Pada titik ini,
Progress
harus melaporkan penyelesaian, tetapi kemungkinan besar kita akan sedikit melompati nilainya. Oleh karena itu, pada saat ini kita harus mengembalikan waktu ekstra untuk menggunakannya dalam kemajuan dalam urutan berikut. Agar ini berfungsi, Anda perlu mengubah delta waktu menjadi parameter. Kita juga perlu menunjukkan bahwa kita belum selesai, dan ini dapat diwujudkan dengan mengembalikan nilai negatif.
public float Progress (float deltaTime) { cooldown += deltaTime; while (cooldown >= sequence.cooldown) { cooldown -= sequence.cooldown; if (count >= sequence.amount) { return cooldown; } count += 1; } return -1f; }
Buat musuh di mana saja
Agar urutan untuk menelurkan musuh, kita perlu mengkonversi
Game.SpawnEnemy
ke metode statis publik lainnya.
public static void SpawnEnemy (EnemyFactory factory, EnemyType type) { GameTile spawnPoint = instance.board.GetSpawnPoint( Random.Range(0, instance.board.SpawnPointCount) ); Enemy enemy = factory.Get(type); enemy.SpawnOn(spawnPoint); instance.enemies.Add(enemy); }
Karena
Game
itu sendiri tidak akan lagi menghasilkan musuh, kita dapat menghapus pabrik musuh, kecepatan pembuatan, proses promosi pembuatan dan kode pembuatan musuh dari
Update
.
void Update () { }
Kami akan memanggil
Game.SpawnEnemy
di
EnemySpawnSequence.State.Progress
setelah meningkatkan jumlah musuh.
public float Progress (float deltaTime) { cooldown += deltaTime; while (cooldown >= sequence.cooldown) { … count += 1; Game.SpawnEnemy(sequence.factory, sequence.type); } return -1f; }
Kemajuan gelombang
Mari kita mengambil pendekatan yang sama untuk bergerak sepanjang urutan seperti ketika bergerak sepanjang gelombang. Mari kita berikan
EnemyWave
metode
Begin
sendiri, yang mengembalikan contoh baru dari struktur
State
bersarang. Dalam kasus ini, negara berisi indeks gelombang dan keadaan urutan aktif, yang kami inisialisasi dengan awal urutan pertama.
Keadaan gelombang yang berisi kondisi urutan. public class EnemyWave : ScriptableObject { [SerializeField] EnemySpawnSequence[] spawnSequences = { new EnemySpawnSequence() }; public State Begin() => new State(this); [System.Serializable] public struct State { EnemyWave wave; int index; EnemySpawnSequence.State sequence; public State (EnemyWave wave) { this.wave = wave; index = 0; Debug.Assert(wave.spawnSequences.Length > 0, "Empty wave!"); sequence = wave.spawnSequences[0].Begin(); } } }
Kami juga menambahkan metode
Progress
metode
EnemyWave.State
, yang menggunakan pendekatan yang sama seperti sebelumnya, dengan perubahan kecil. Kami mulai dengan bergerak di sepanjang urutan aktif dan mengganti delta waktu dengan hasil panggilan ini. Sementara ada waktu yang tersisa, kami pindah ke urutan berikutnya, jika diakses, dan melakukan kemajuan di atasnya. Jika tidak ada urutan yang tersisa, maka kami mengembalikan sisa waktu; jika tidak kembalikan nilai negatif.
public float Progress (float deltaTime) { deltaTime = sequence.Progress(deltaTime); while (deltaTime >= 0f) { if (++index >= wave.spawnSequences.Length) { return deltaTime; } sequence = wave.spawnSequences[index].Begin(); deltaTime = sequence.Progress(deltaTime); } return -1f; }
Promosi skrip
Tambahkan
GameScenario
proses yang sama. Dalam hal ini, negara berisi indeks gelombang dan keadaan gelombang aktif.
public class GameScenario : ScriptableObject { [SerializeField] EnemyWave[] waves = {}; public State Begin () => new State(this); [System.Serializable] public struct State { GameScenario scenario; int index; EnemyWave.State wave; public State (GameScenario scenario) { this.scenario = scenario; index = 0; Debug.Assert(scenario.waves.Length > 0, "Empty scenario!"); wave = scenario.waves[0].Begin(); } } }
Karena kita berada di tingkat atas, metode
Progress
tidak memerlukan parameter dan Anda dapat menggunakan
Time.deltaTime
secara langsung. Kami tidak perlu mengembalikan waktu yang tersisa, tetapi kami perlu menunjukkan apakah skrip selesai. Kami akan mengembalikan
false
setelah akhir dari gelombang terakhir dan
true
untuk menunjukkan bahwa skrip masih aktif.
public bool Progress () { float deltaTime = wave.Progress(Time.deltaTime); while (deltaTime >= 0f) { if (++index >= scenario.waves.Length) { return false; } wave = scenario.waves[index].Begin(); deltaTime = wave.Progress(deltaTime); } return true; }
Jalankan skrip
Untuk memainkan skrip
Game
, Anda memerlukan bidang konfigurasi skrip dan pelacakan statusnya. Kami hanya akan menjalankan skrip di Sedar dan menjalankan
Update
di atasnya sampai status dari sisa permainan diperbarui.
[SerializeField] GameScenario scenario = default; GameScenario.State activeScenario; … void Awake () { board.Initialize(boardSize, tileContentFactory); board.ShowGrid = true; activeScenario = scenario.Begin(); } … void Update () { … activeScenario.Progress(); enemies.GameUpdate(); Physics.SyncTransforms(); board.GameUpdate(); nonEnemies.GameUpdate(); }
Sekarang skrip yang dikonfigurasi akan diluncurkan pada awal permainan. Promosi di atasnya akan dilakukan sampai selesai, dan setelah itu tidak ada yang terjadi.
Dua gelombang dipercepat 10 kali.Mulai dan akhiri permainan
Kami dapat mereproduksi satu skenario, tetapi setelah selesai, musuh baru tidak akan muncul. Agar permainan dapat terus berlanjut, kami harus memungkinkan untuk memulai skenario baru, baik secara manual, atau karena pemain kalah / menang. Anda juga dapat menerapkan pilihan beberapa skenario, tetapi dalam tutorial ini kami tidak akan mempertimbangkannya.
Awal dari sebuah game baru
Idealnya, kita membutuhkan kesempatan untuk memulai permainan baru pada waktu tertentu. Untuk melakukan ini, Anda perlu mengatur ulang keadaan saat ini dari seluruh permainan, yaitu, kita harus mengatur ulang banyak objek. Pertama, tambahkan metode
Clear
ke
GameBehaviorCollection
yang memanfaatkan semua perilakunya.
public void Clear () { for (int i = 0; i < behaviors.Count; i++) { behaviors[i].Recycle(); } behaviors.Clear(); }
Ini menunjukkan bahwa semua perilaku dapat dihilangkan, tetapi sejauh ini tidak demikian. Untuk membuatnya berfungsi, tambahkan metode
Recycle
abstrak ke
GameBehavior
.
public abstract void Recycle ();
Metode
Recycle
kelas
WarEntity
harus secara eksplisit
WarEntity
.
public override void Recycle () { originFactory.Reclaim(this); }
Enemy
belum memiliki metode
Recycle
, jadi tambahkan saja. Yang harus dia lakukan adalah memaksa pabrik untuk mengembalikannya. Lalu kami memanggil
Recycle
mana pun kami langsung mengakses pabrik.
public override bool GameUpdate () { if (Health <= 0f) { Recycle(); return false; } progress += Time.deltaTime * progressFactor; while (progress >= 1f) { if (tileTo == null) { Recycle(); return false; } … } … } public override void Recycle () { OriginFactory.Reclaim(this); }
GameBoard
juga perlu disetel ulang, jadi mari kita berikan metode
Clear
, yang mengosongkan semua ubin, mengatur ulang semua titik pembuatan dan memperbarui konten, dan kemudian menetapkan titik awal dan akhir standar. Kemudian, alih-alih mengulangi kode, kita dapat memanggil
Clear
di akhir
Initialize
.
public void Initialize ( Vector2Int size, GameTileContentFactory contentFactory ) { … for (int i = 0, y = 0; y < size.y; y++) { for (int x = 0; x < size.x; x++, i++) { … } } Clear(); } public void Clear () { foreach (GameTile tile in tiles) { tile.Content = contentFactory.Get(GameTileContentType.Empty); } spawnPoints.Clear(); updatingContent.Clear(); ToggleDestination(tiles[tiles.Length / 2]); ToggleSpawnPoint(tiles[0]); }
Sekarang kita bisa menambahkan metode
BeginNewGame
ke
Game
, membuang musuh, objek lain dan bidang, dan kemudian memulai skrip baru.
void BeginNewGame () { enemies.Clear(); nonEnemies.Clear(); board.Clear(); activeScenario = scenario.Begin(); }
Kami akan memanggil metode ini dalam
Update
jika Anda menekan B sebelum beralih ke skrip.
void Update () { … if (Input.GetKeyDown(KeyCode.B)) { BeginNewGame(); } activeScenario.Progress(); … }
Kehilangan
Tujuan permainan ini adalah untuk mengalahkan semua musuh sebelum sejumlah dari mereka mencapai titik akhir. Jumlah musuh yang diperlukan untuk memicu kondisi kekalahan tergantung pada kesehatan awal pemain, untuk itu kami akan menambahkan bidang konfigurasi ke
Game
. Karena kita menghitung musuh, kita akan menggunakan integer, bukan float.
[SerializeField, Range(0, 100)] int startingPlayerHealth = 10;
Awalnya, seorang pemain memiliki 10 kesehatan.Dalam kasus Sedar atau dimulainya permainan baru, kami menetapkan nilai awal untuk kesehatan pemain saat ini.
int playerHealth; … void Awake () { playerHealth = startingPlayerHealth; … } void BeginNewGame () { playerHealth = startingPlayerHealth; … }
Tambahkan metode
EnemyReachedDestination
statis publik
EnemyReachedDestination
musuh dapat memberi tahu
Game
bahwa mereka telah mencapai titik akhir. Ketika ini terjadi, kurangi kesehatan pemain.
public static void EnemyReachedDestination () { instance.playerHealth -= 1; }
Panggil metode ini di
Enemy.GameUpdate
pada waktu yang tepat.
if (tileTo == null) { Game.EnemyReachedDestination(); Recycle(); return false; }
Sekarang kita dapat memeriksa kondisi kekalahan di
Game.Update
. Jika kesehatan pemain sama dengan atau kurang dari nol, kondisi kekalahan dipicu. Kami cukup mencatat informasi ini dan segera memulai permainan baru sebelum bergerak maju. Tetapi kami akan melakukan ini hanya dengan kesehatan awal yang positif. Ini memungkinkan kami untuk menggunakan 0 sebagai kesehatan awal, sehingga tidak mungkin hilang. Jadi akan nyaman bagi kita untuk menguji skrip.
if (playerHealth <= 0 && startingPlayerHealth > 0) { Debug.Log("Defeat!"); BeginNewGame(); } activeScenario.Progress();
Kemenangan
Alternatif untuk mengalahkan adalah kemenangan, yang dicapai pada akhir skenario, jika pemain masih hidup. Yaitu, ketika hasil
GameScenario.Progess
false
, kami menampilkan pesan kemenangan di log, memulai permainan baru, dan segera melanjutkannya.
if (playerHealth <= 0) { Debug.Log("Defeat!"); BeginNewGame(); } if (!activeScenario.Progress()) { Debug.Log("Victory!"); BeginNewGame(); activeScenario.Progress(); }
Namun, kemenangan akan datang setelah akhir jeda terakhir, bahkan jika masih ada musuh di lapangan. Kita perlu menunda kemenangan sampai semua musuh menghilang, yang dapat diwujudkan dengan memeriksa apakah kumpulan musuh kosong. Kami berasumsi bahwa ia memiliki properti
IsEmpty
.
if (!activeScenario.Progress() && enemies.IsEmpty) { Debug.Log("Victory!"); BeginNewGame(); activeScenario.Progress(); }
Tambahkan properti yang diinginkan ke
GameBehaviorCollection
.
public bool IsEmpty => behaviors.Count == 0;
Kontrol waktu
Mari kita juga mengimplementasikan fitur manajemen waktu, ini akan membantu dalam pengujian dan seringkali merupakan fungsi gameplay. Untuk memulai, biarkan
Game.Update
memeriksa
Game.Update
spasi, dan gunakan acara ini untuk mengaktifkan / menonaktifkan jeda dalam game. Ini dapat dilakukan dengan beralih nilai
Time.timeScale
antara nol dan satu. Ini tidak akan mengubah logika permainan, tetapi akan membuat semua objek membeku di tempatnya. Atau Anda dapat menggunakan nilai yang sangat kecil alih-alih 0, misalnya 0,01, untuk membuat gerakan yang sangat lambat.
const float pausedTimeScale = 0f; … void Update () { … if (Input.GetKeyDown(KeyCode.Space)) { Time.timeScale = Time.timeScale > pausedTimeScale ? pausedTimeScale : 1f; } if (Input.GetKeyDown(KeyCode.B)) { BeginNewGame(); } … }
Kedua, kami akan menambahkan Game
kecepatan game ke slider sehingga Anda dapat mempercepat waktu. [SerializeField, Range(1f, 10f)] float playSpeed = 1f;
Kecepatan game.Jika jeda tidak diaktifkan dan nilai jeda tidak ditetapkan untuk skala waktu, kami membuatnya sama dengan kecepatan permainan. Juga, saat menghapus jeda, kami menggunakan kecepatan gim alih-alih persatuan. if (Input.GetKeyDown(KeyCode.Space)) { Time.timeScale = Time.timeScale > pausedTimeScale ? pausedTimeScale : playSpeed; } else if (Time.timeScale > pausedTimeScale) { Time.timeScale = playSpeed; }
Skenario loop
Dalam beberapa skenario, mungkin perlu melewati semua gelombang beberapa kali. Dimungkinkan untuk mengimplementasikan dukungan untuk fungsi seperti itu dengan memungkinkan untuk mengulang skenario dengan mengulang semua gelombang beberapa kali. Anda dapat lebih meningkatkan fungsi ini, misalnya, dengan mengaktifkan pengulangan hanya gelombang terakhir, tetapi dalam tutorial ini kami hanya akan mengulangi keseluruhan skrip.Kemajuan siklus pada gelombang
Tambahkan ke GameScenario
penggeser konfigurasi untuk mengatur jumlah siklus, secara default, berikan nilai 1. Minimal, buat nol, dan skrip akan diulang tanpa henti. Jadi kita akan membuat skenario bertahan hidup yang tidak bisa dikalahkan, dan intinya adalah untuk memeriksa seberapa banyak pemain bisa bertahan. [SerializeField, Range(0, 10)] int cycles = 1;
Skenario dua siklus.Sekarang GameScenario.State
harus melacak nomor siklus. int cycle, index; EnemyWave.State wave; public State (GameScenario scenario) { this.scenario = scenario; cycle = 0; index = 0; wave = scenario.waves[0].Begin(); }
Di Progress
kami akan mengeksekusi setelah selesainya penambahan siklus, dan kembali false
hanya jika jumlah siklus yang cukup telah berlalu. Jika tidak, kami mereset indeks gelombang ke nol dan terus bergerak. public bool Progress () { float deltaTime = wave.Progress(Time.deltaTime); while (deltaTime >= 0f) { if (++index >= scenario.waves.Length) { if (++cycle >= scenario.cycles && scenario.cycles > 0) { return false; } index = 0; } wave = scenario.waves[index].Begin(); deltaTime = wave.Progress(deltaTime); } return true; }
Akselerasi
Jika pemain berhasil mengalahkan siklus sekali, maka ia akan dapat mengalahkannya lagi tanpa masalah. Untuk menjaga skenario tetap kompleks, kita perlu meningkatkan kompleksitas. Cara termudah untuk melakukan ini, mengurangi siklus berikutnya semua jeda antara penciptaan musuh. Maka musuh akan muncul lebih cepat dan pasti akan mengalahkan pemain dalam skenario bertahan hidup.Tambahkan GameScenario
bilah konfigurasi untuk mengontrol akselerasi per siklus. Nilai ini ditambahkan ke skala waktu setelah setiap siklus hanya untuk mengurangi jeda. Misalnya, dengan akselerasi 0,5, siklus pertama memiliki kecepatan jeda × 1, siklus kedua memiliki kecepatan × 1,5, yang ketiga × 2, yang keempat × 2,5, dan seterusnya. [SerializeField, Range(0f, 1f)] float cycleSpeedUp = 0.5f;
Sekarang Anda perlu menambahkan skala waktu dan GameScenario.State
. Awalnya selalu sama dengan 1 dan meningkat dengan nilai percepatan setelah setiap siklus. Gunakan untuk mengukur Time.deltaTime
sebelum bergerak di sepanjang gelombang. float timeScale; EnemyWave.State wave; public State (GameScenario scenario) { this.scenario = scenario; cycle = 0; index = 0; timeScale = 1f; wave = scenario.waves[0].Begin(); } public bool Progress () { float deltaTime = wave.Progress(timeScale * Time.deltaTime); while (deltaTime >= 0f) { if (++index >= scenario.waves.Length) { if (++cycle >= scenario.cycles && scenario.cycles > 0) { return false; } index = 0; timeScale += scenario.cycleSpeedUp; } wave = scenario.waves[index].Begin(); deltaTime = wave.Progress(deltaTime); } return true; }
Tiga siklus dengan peningkatan kecepatan penciptaan musuh; dipercepat sepuluh kali.Apakah Anda ingin menerima informasi tentang rilis tutorial baru? Ikuti halaman saya di Patreon ! Artikel PDFrepositori