Spawning Waves of Enemies in Unity
Right now, the spawn manager in my 2D arcade shooter drops enemies over a short random range of time to fall at the player until defeated. We have three new enemies to add to the system, and we want them to each have their own percent chance to spawn. We need the spawner to spawn a specific number of enemies per wave and then pause and wait for all enemies to be dealt with. Only then can the next (slightly larger) wave fall. What once started as a slow drip will eventually become a torrent — IF the player can survive.
Let’s get dripping.
There are five methods to my SpawnManager’s operation:
- The phase manager routine that manages the phases of spawning and not spawning.
- The wave spawner routine iterates the wave and runs the spawner once for each wave that has passed.
- The probability assignment method makes sure the spawner knows the probability for each enemy type in the current wave.
- The spawner rolls the dice to decide which enemy is spawned.
- The lifesign monitor bool function checks to see if the current wave is alive.
This is the overall loop. With this, as long as the spawner is switched on, it will trigger all the necessary methods in the proper timing. First, it runs the wave spawner. When that returns, we wait while the player cleans up the field, and then after an appropriate wait between waves we do it all over again.
This IEnumerator increments the wave each cycle, and then fetches the new probabilities for each enemy. Then there’s a for loop iterating on the current wave count that will spawn one enemy for each wave start that has passed. So, the first wave will have one enemy. the second will have two, and so on.
A switch on the wave count loads the lists for each escalation of probabilities. I made sure that the total probability spread equals 100, to make it easier to understand the actual probability of a given spawn. This array is paired with the _enemies array (referenced in the Spawner section below) so that their addresses match — that is, _enemies and _enemySpawnProb both refer to the Basic enemy type.
The core of this is the for loop that iterates on the length of the _enemies array, which is serialized in the inspector for the Spawn Manager game object in Unity (pictured below). For each enemy on the list, we set the high and low boundaries of probability: the low boundary being set by the previous high boundary, and the high boundary being set by the spawn probability for this enemy. If the random roll (1–100, since I do want 0 to mean 0 in this case) is between the high and low, then the enemy is spawned. If not, then we try again with the next enemy on the list.
The important part is at the end. Each new enemy must be added to a List, which I’ve initialized at the beginning of the script. This _enemyList holds the identity of each enemy spawned to the field. This list will get checked in the next section.
First things first, we’ll need access to Language-Integrated Query. So, add the System.Linq namespace to your script.
From there, we simply check if the enemies on the list are null. If they are, we rewrite the list. When the list count is 0, then we return to the Phase Manager to start all over again.
I hope you found this informative. In the next article, I’ll add some more wacky abilities to our enemies, like the ability to shoot backwards or to shoot powerups!