Technical Implementation of an Endless Spawn System


[ 1. Overview ]
The first issue was figuring out the exact parameters required to spawn N of enemy type N at the start of wave N. The "endless" aspect means hard-coding each wave is out of the question (you would go insane after the first 100 waves) and so an algorithm was required to handle the calculations.

[ 2. Kill Threshold ]
This leads into the second issue. How do you determine when a wave is finished (the last enemy of that wave is dead) if the total number of enemies per wave uses procedural generation? Having succumbed to partial insanity after testing a few methods that didn't work, the final solution was a kill threshold. Although it's not possible to know how many will be spawned on a given wave, it is possible to take the algorithm's calculation, and then add it to a kill threshold counter. When the kill threshold is reached, a new wave is started and the threshold is increased based on the algorithm's spawn calculation.

KillThreshold = (KillThreshold + ((CurrentWave * Z) + (CurrentWave * N) + (Mathf.RoundToInt(CurrentWave / H))));

This is how it works: when a new wave starts, the previous kill threshold is added to the total amount of enemies to be spawned that particular round, and that becomes the new kill threshold. This threshold is checked against the total number of kills in order to trigger the next wave (and thus make it truly endless).

if(TotalKills == KillThreshold)
{
    if(SwitchedToNextLevel == false)
    {
        SwitchedToNextLevel = true;
        StartNewWave();
        }
    }

Here's a more detailed breakdown of the system when a new wave is started.

Increment the current wave by 1. This is used both for internal tracking, and to update the game UI.
CurrentWave += 1;

Update the UI for the player.
WaveCounterUI.text = "Wave " + CurrentWave;

Display a notification panel.
"Wave X" to the player at the start of a new round.
StartCoroutine(NewWaveNotification());

Set the new kill threshold required to trigger the next wave. Math.RoundToInt is specifically used to obtain a whole number due to the calculation being a division, rather than a multiplication... because you can't really spawn 50% of an enemy.
KillThreshold = (KillThreshold + ((CurrentWave * Z) + (CurrentWave * N) + (Mathf.RoundToInt(CurrentWave / H))));

Show a countdown timer to the player "wave starts in 10 seconds..."
StartCoroutine(NewWaveCountdownTimer());

Start the internal countdown that triggers a new wave. (This calls a function from one of the custom event managers used to manage various gameplay aspects).
StartCoroutine(NewWaveCountdown());

This is set to false after the new wave kicks in. This then allows a new wave to be triggered again when the new kill threshold is met.
SwitchedToNextLevel = false;


[ 3. Clamping Spawn Rates ]
Consider wave 300. I'm not actually sure if the player could get this far without dying... but let's say the did. That's a lot of enemies to spawn. Having all enemies instantiated at the start of a wave would be ludicrous for obvious reasons. For a while, it was tempting to copy COD: Black Ops Zombies system.

Originally, in maps like Kino Der Toten and others, Treyarch limited the total number of zombies that would chase you, to the point where no more zombies would be spawned if the threshold was met (I think it was 27 zombies). There are a few reasons to do this;

1) spawning 100 zombies at once is unfair. Skill becomes meaningless and dying like this would ruin the game as you have no chance to fight back.
2) at some point, depending on how complex the models and attached components are, it starts to impact on game performance.

Although limiting the total number of active enemies to a static number is not something I wanted to do, so here's the solution.

Spawn Intervals are custom parameters included in the custom spawn system, and used to determine how far apart (in seconds) to spawn enemies at various spawn points semi-dynamically.

Similar to the kill threshold, there is an "Active Agent Threshold" that, when triggered, will increase the duration between enemies being spawned, rather than pausing the spawn system itself. The full effects of this system are somewhat untested, as getting to round 100+ is... difficult. However, it works quite nicely for the moment. The interesting thing about this mini-system is that the spawn rate will be scaled back to normal when under the threshold. This gives a more dynamic feel without making it "too" easy for players, or making it so impossible it's not fun anymore.

Here's a more visual example. (under-threshold spawn VS over-threshold spawn)


[ 4. System Integration ]
There are a few systems that work together in order to make everything work, these include (but are not limited to):

Global Event Manager
The GEM basically acts like a huge dictionary. This is used to register and deregister events using Unity's Event System. This handles global calls that are deemed to impact on critical aspects of gameplay (weapons, AI, spawning etc).

Spawn System 
The spawn system is separate from the wave manager, with the wave manager having authority over it. (The wave manager tells the spawn system what to do, rather than it being the other way around). This was kept separate for scalability and so I don't have to sift through a ton of code each time when working on the different system components.

Wave Manager 
The wave manager is (probably) the 2nd most important system component. It primarily takes information/data from multiple sources (player manager, game manager, spawn system) and uses it to trigger events via the global event manager (or a function in another script that itself activates an event within the GEM).

Game Manager 
The game manager is where "how many enemies have been killed so far?" is stored. This is because related data is also stored or manipulated here so it makes management easier. For example, stuff like current points, "is the player dead" and some of the menu functions are stored here (such as pausing and viewing the in-game shop).

Player Manager 
The player manager passes information to the spawn system about certain, secret stuff I can't really describe without ruining the gameplay.

So, to summarise, is it possible to get to wave 9,897,678 and beyond? yes (technically) but I don't think anyone could actually achieve such a feat. Either way, it's truly an "endless" spawn system.

Get Wave Z

Leave a comment

Log in with itch.io to leave a comment.