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()
- SpawnEnemies() is a coroutine that we use to create our enemies. The function will spawn the enemy for the wave based from one of the random spawn points we set. The code will wait 2 seconds before we spawn the next enemy so we don’t flood the players with all the enemies in a wave at once.
- Finally, we have public EnemyDefeated() which means this function is called from something else. In this case, our enemy will call this when they’re killed. We will then decrement our enemy counter and if we have defeated all the enemies in a wave, we’ll call StartNextWave()
That’s the basic summary of the code. That wasn’t so bad, now was it? RIGHT?!
Setting up our SpawnManager script
Now that we have our script up and running, the last thing we need to do is to setup our Wave and create our Spawn Points.
Setting up the Wave
For now, let’s just make 2 waves for our SpawnManager that will create our knight enemy.
There are 2 things we need to provide for our Wave class:
- The knight game object
- How many enemies to spawn for the wave
Let’s first set the knight game object.
We could just drag and drop our knight from our hierarchy into the spot and everything would be fine. However, the correct way to do this, is to first create a prefab of our knight.
We can think of a prefab is a template of a game object that we can drag and share to different scenes and games. In our case, we can also instantiate them like what we’re doing here.
The main benefit is that, if you were to change prefab, anytime we need to change something, we can just change the prefab and anything that instantiates it from code will also get the changes.
The first thing we’re going to do is:
- Create a folder in our Assets folder called Prefabs
- Create our prefab, by dragging our Knight game object into the PrefabsNote, in this example our Knight is already a prefab, but that’s okay, we’ve already changed some things about it, so let’s make another prefab from it.
On a side note: we can delete the Knight game object from our hierarchy. He’s served us well, but we won’t be needing his services anymore.
Now that we have everything we need, we can get started in Creating our waves.
Back in our SpawnManger script, expand Waves and set Size to be 2 so we can create new waves.
Here’s what it’ll look like:
Setting up the Spawn Point
Now that we have the wave system setup, we need to create our spawn points.
Exactly like the survival shooter, we need to add some game object that we’ll use as the location that we’re going to spawn the enemies.
Let’s get to it!
Create 3 Empty game objects: I call them SpawnPoint1 — SpawnPoint3. I made them the child of the SpawnManager, however, it doesn’t really matter where we put them.
Right now, we can’t see the position of our SpawnPoints in the game, however we can add a Label to them to see where the objects are.
We just want to set the Spawn points to be in 3 of the houses.
Here are my Position values, yours might be different:
SpawnPoint1: 98, 0, 118
SpawinPoint2: 72, 0, 89
SpawnPoint3: 106, 0, 80
Finally, go back to our SpawnManager and add our SpawnPoints in.
Expand Spawn Points and then for the Size change it to 3, and then drag our SpawnPoints into the empty game spots.
With all of this in place, if we play the game, we should now have multiple enemies coming at us.
Conclusion
We’re not quite done with our Spawning System, however we laid the ground works for spawning enemies.
We still need to implement the logic for generating new enemies and finally creating our victory condition. We’ll start implementing those tomorrow on Day 24!
With that being said, have a good night!
Source: Day 23
Visit the 100 Days of Unity VR Development main page.
Visit our Homepage