Part of these game systems:
Wave Spawner
Wave-based enemy spawner with configurable wave definitions, spawn points, delays, and wave completion callbacks.
How to Use
1
Create empty GameObjects as spawn points
2
Attach WaveSpawner to a manager object
3
Define waves: each wave has enemy prefabs and counts
4
Assign spawn points array
5
Hook OnWaveStart/OnWaveComplete to your UI
6
Enemies are tracked automatically (destroy them to advance)
7
OnAllWavesComplete fires when final wave is cleared
Source Code
C#
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Wave-based enemy spawner. Define waves with enemy types,
/// counts, and spawn delays. Tracks alive enemies per wave.
/// </summary>
public class WaveSpawner : MonoBehaviour
{
[System.Serializable]
public class Wave
{
public string waveName = "Wave";
public EnemySpawn[] enemies;
public float delayBetweenSpawns = 0.5f;
}
[System.Serializable]
public class EnemySpawn
{
public GameObject prefab;
public int count = 5;
}
[Header("Waves")]
[SerializeField] private Wave[] waves;
[SerializeField] private float delayBetweenWaves = 5f;
[SerializeField] private bool autoStart = true;
[Header("Spawn Points")]
[SerializeField] private Transform[] spawnPoints;
[Header("Events")]
public UnityEvent<int> OnWaveStart; // wave index
public UnityEvent<int> OnWaveComplete; // wave index
public UnityEvent OnAllWavesComplete;
public UnityEvent<int, int> OnEnemyCountChanged; // alive, total
private int currentWaveIndex;
private List<GameObject> aliveEnemies = new List<GameObject>();
private int totalEnemiesInWave;
private bool isSpawning;
/// <summary>Current wave number (1-based).</summary>
public int CurrentWave => currentWaveIndex + 1;
/// <summary>Total number of waves.</summary>
public int TotalWaves => waves != null ? waves.Length : 0;
/// <summary>Is the spawner currently active?</summary>
public bool IsActive => isSpawning;
/// <summary>Number of enemies still alive.</summary>
public int AliveCount => aliveEnemies.Count;
private void Start()
{
if (autoStart && waves != null && waves.Length > 0)
StartWaves();
}
/// <summary>Begin the wave sequence.</summary>
public void StartWaves()
{
if (spawnPoints == null || spawnPoints.Length == 0)
{
Debug.LogError("[WaveSpawner] No spawn points assigned!");
return;
}
currentWaveIndex = 0;
StartCoroutine(SpawnWave());
}
private IEnumerator SpawnWave()
{
if (currentWaveIndex >= waves.Length)
{
OnAllWavesComplete?.Invoke();
yield break;
}
isSpawning = true;
Wave wave = waves[currentWaveIndex];
OnWaveStart?.Invoke(currentWaveIndex);
aliveEnemies.Clear();
totalEnemiesInWave = 0;
foreach (var enemySpawn in wave.enemies)
totalEnemiesInWave += enemySpawn.count;
int spawnIndex = 0;
foreach (var enemySpawn in wave.enemies)
{
for (int i = 0; i < enemySpawn.count; i++)
{
Transform spawnPoint = spawnPoints[spawnIndex % spawnPoints.Length];
spawnIndex++;
Vector3 offset = new Vector3(Random.Range(-1f, 1f), 0f, Random.Range(-1f, 1f));
GameObject enemy = Instantiate(
enemySpawn.prefab,
spawnPoint.position + offset,
spawnPoint.rotation
);
aliveEnemies.Add(enemy);
OnEnemyCountChanged?.Invoke(aliveEnemies.Count, totalEnemiesInWave);
yield return new WaitForSeconds(wave.delayBetweenSpawns);
}
}
isSpawning = false;
// Wait for all enemies to be destroyed
yield return StartCoroutine(WaitForWaveClear());
OnWaveComplete?.Invoke(currentWaveIndex);
currentWaveIndex++;
if (currentWaveIndex < waves.Length)
{
yield return new WaitForSeconds(delayBetweenWaves);
StartCoroutine(SpawnWave());
}
else
{
OnAllWavesComplete?.Invoke();
}
}
private IEnumerator WaitForWaveClear()
{
while (true)
{
aliveEnemies.RemoveAll(e => e == null);
OnEnemyCountChanged?.Invoke(aliveEnemies.Count, totalEnemiesInWave);
if (aliveEnemies.Count == 0)
yield break;
yield return new WaitForSeconds(0.5f);
}
}
/// <summary>Skip to the next wave immediately.</summary>
public void SkipToNextWave()
{
foreach (var enemy in aliveEnemies)
{
if (enemy != null) Destroy(enemy);
}
aliveEnemies.Clear();
}
}