Here we are back to another day of Unity development! Today on day 23 we’re going to learn how to spawn enemy waves.
Currently, the game isn’t challenging, we only have one enemy!
Today, we’re going to fix this by spawning waves of enemies to defeat, making the game a lot tougher to survive!
It’s going to be a big change requiring multiple days, so let’s get right to it!
Creating an Enemy Wave Spawning Point
If we were to recall from the Unity Survival Shooter tutorial, to spawn enemies, we need to create a SpawnManager class that creates new instances of the enemy.
In our Spawn Manager, the primary thing we need to give it, among many other things that we’ll want to add, are the:
location of where we would spawn the enemies
enemies that we want to spawn
However, as a challenge, as opposed to the Survival shooter, where the game would continue for it, we’re going to have a limited amount of enemy spawn so we can win.
There was a lot of work involved in the wave system and I borrowed a lot of ideas from Unity’s Enemy Spawner example.
Creating the initial game objects
The first thing we want to do is create a new Empty Game Object that we’ll call EnemyManager. We’re going to make this a child of our GameManager.
Next, we’ll create a new script for our new game object that we’ll call EnemyManager.
We’re going to do a couple of things with our manager:
Keep track of what wave we’re in
Keep track of how many enemies we defeated in a wave
Keep track of how many enemies per wave
By keeping track of the number of enemies and waves, we can tell when to move to the next wave and if we win.
Here’s our initial code for EnemyManager.cs:
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
public int EnemiesPerWave;
public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
public Wave[] Waves; // class to hold information per wave
public Transform[] SpawnPoints;
public float TimeBetweenEnemies = 2f;
private int _totalEnemiesInCurrentWave;
private int _enemiesInWaveLeft;
private int _spawnedEnemies;
private int _currentWave;
private int _totalWaves;
void Start ()
{
_currentWave = -1; // avoid off by 1
_totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
StartNextWave();
}
void StartNextWave()
{
_currentWave++;
// win
if (_currentWave > _totalWaves)
{
return;
}
_totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
_enemiesInWaveLeft = 0;
_spawnedEnemies = 0;
StartCoroutine(SpawnEnemies());
}
// Coroutine to spawn all of our enemies
IEnumerator SpawnEnemies()
{
GameObject enemy = Waves[_currentWave].Enemy;
while (_spawnedEnemies < _totalEnemiesInCurrentWave)
{
_spawnedEnemies++;
_enemiesInWaveLeft++;
int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
Instantiate(enemy, SpawnPoints[spawnPointIndex].position, SpawnPoints[spawnPointIndex].rotation);
yield return new WaitForSeconds(TimeBetweenEnemies);
}
yield return null;
}
// called by an enemy when they're defeated
public void EnemyDefeated()
{
_enemiesInWaveLeft--;
// We start the next wave once we have spawned and defeated them all
if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
{
StartNextWave();
}
}
}
Now this is a lot to take in, which is why I added comments, however, here’s the run through of the code.
About the Wave Class
Before we talk about our variables, I want to introduce the Wave class.
Wave is a container for us to hold the data for each wave that we’re going to face.
If you remember from the Space Shooter tutorial, we did something similar. We created a new class to hold information and we made it Serializable so that Unity knows how to show it in the editor.
Originally, I was going to just pass each of its content to our SpawnManager, but that’s prone to us causing mix-ups of how many enemies to spawn per wave and which enemies.
About the variables
For our public variable we have:
Waves — An array of the Wave class that we created for an easy way for us to access data for each wave
SpawnPoints — An array of locations that we’re going to instantiate our enemies
TimeBetweenEnemies — The delay we wait before we spawn our next enemy
For our private variables to keep track of the enemies, we have:
_totalEnemiesInCurrentWave — Self explanatory
_enemiesInWaveLeft — The number of enemies that are still alive in the wave
_spawnedEnemies — Self explanatory
We also keep track of what wave we’re in:
_currentWave — Self explanatory
_totalWaves — Self explanatory
The Code Flow
Now that we know the variable we’re using, we can walk through the rest of the code.
In Start() we initialize all our variables to 0. Note that we set _currentWave to be -1 and our _totalWaves is the length of our array — 1. For those who aren’t familiar, all of this is, because we’re working in a 0-based indexing for arrays (meaning everything starts from 0 instead of 1).
From Start() we also call StartNextWave() this code handles what happens when we clear our wave. We increment our _currentWave and assuming we haven’t finished the game, we’ll setup the enemies we need to encounter for the next wave and call SpawnEnemies()</