I wrote two c# scripts to handle wave creation and spawning in a tower defense game. A wave spawning script and an editor script.
Let’s begin with my wave spawning script. I stored each wave’s info (enemy, spawn point, spawn number, spawn delay, etc.) in an object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/// <summary> /// Object for each rounds WaveInfo /// </summary> [System.Serializable] public class WaveObj { public string cName; //what is this enemies name public GameObject cEnemy; //what enemy in this wave public GameObject cSpawn; //where does this enemy spawn? public int cEnemyCount; //how many enemies in this wave public float cCadence; //what is the spawn delay between enemies in this wave public float cDelay; //delay until next wave /// <summary> /// Object for holding a waves info /// </summary> /// <param name="WaveCount">Total waves</param> /// <param name="cEnemy">Current Enemy</param> /// <param name="cEnemyCount">How many of current enemy will spawn in this wave</param> /// <param name="cCadence">What is the delay between spawns</param> /// <param name="cDelay">What is the delay after this wave before the next begins</param> public WaveObj(string cName, GameObject cEnemy, GameObject cSpawn, int cEnemyCount, float cCadence, float cDelay) { this.cName = cName; this.cEnemy = cEnemy; this.cSpawn = cSpawn; this.cEnemyCount = cEnemyCount; this.cCadence = cCadence; this.cDelay = cDelay; } public WaveObj() { } } |
I packaged a collection (list*) of these objects into a class that implements the IEnumerator interface so I could easily enumerate through a level’s waves.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/// <summary> /// Enumerator of enemy waves, info filled via unity IDE with custom Editor script WaveDesigner /// </summary> public class WaveEnumerator : MonoBehaviour, IEnumerator { public List<WaveObj> WaveInfo; public int numOfWaves; private int spot; public WaveEnumerator() { spot = -1; } //Required IEnumerator method, return our enumerator public IEnumerator GetEnumerator() { return (IEnumerator)this; } //Required IEnumerator method, line up next object and return true if exists public bool MoveNext() { spot++; return (spot < WaveInfo.Count); } ////Required IEnumerator method, reset the enumerator public void Reset() { spot = -1; } ////Required IEnumerator method, return current object public object Current { get { return WaveInfo[spot]; } set { } //empty as changes are not allowed } } |
Once the level is loaded I kick-off a coroutine to handle spawning, and passing the appropriate references to our spawned enemies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
StartCoroutine(StartWaves());//start our wave co-routine } IEnumerator StartWaves() { foreach (WaveStruct currentWave in gameObject.GetComponent<WaveEnumerator>()) { //The enemies in each wave loop for (int enemiesInWave = 1; enemiesInWave <= currentWave.cEnemyCount; enemiesInWave++) { while (paused) { yield return new WaitForSeconds(.5f); } //loop continously while paused //instantiate the current enemy object GameObject justSpawned = (GameObject)Instantiate(currentWave.cEnemy, currentWave.cSpawn.transform.position, Quaternion.identity); //Initialize the spawned enemy (let it know ref to our Game Controller script, and the target it needs to walk towards) justSpawned.GetComponent<Enemy_Attributes>().InitSpawn(activeGameController, Target.transform.position); yield return new WaitForSeconds(currentWave.cCadence); } yield return new WaitForSeconds(currentWave.cDelay); //wait how long dicated until the next wave of enemies } //add code to alert game engine that all enemies have been spawned yield return null; } |
Now that I had a script to spawn enemies I needed a way to easily assign values. For this I wrote an Editor script. Remember these must always be placed in a projects Editor folder.
Script below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
[CustomEditor(typeof(WaveEnumerator))] public class WaveDesigner : Editor { public bool[] WaveCount; WaveEnumerator myLevelEnumerator; // Use this for initialization void OnEnable () { myLevelEnumerator = (WaveEnumerator)target; WaveCount = new bool[1]; } public override void OnInspectorGUI() { EditorGUILayout.LabelField("Enter the level's wave info below:"); //how many waves myLevelEnumerator.numOfWaves = EditorGUILayout.IntField("Number of Waves:", myLevelEnumerator.numOfWaves); //lets adjust all our sizes to legal options AdjustListSizes(); for (int i = 0; i < myLevelEnumerator.WaveInfo.Count; i++) { //lets create a foldout for each wave WaveCount[i] = EditorGUILayout.Foldout(WaveCount[i], "Wave #" + i.ToString()); if (WaveCount[i]) { var listRef = myLevelEnumerator.WaveInfo[i]; //too lazy to type this each time //each waves info is below listRef.cName = EditorGUILayout.TextField("Name", listRef.cName); listRef.cEnemy = (GameObject)EditorGUILayout.ObjectField("Prefab Enemy: ", listRef.cEnemy, typeof(GameObject), true); listRef.cSpawn = (GameObject)EditorGUILayout.ObjectField("Spawn Point: ", listRef.cSpawn, typeof(GameObject), true); listRef.cEnemyCount = EditorGUILayout.IntField("Amount to Spawn: ", listRef.cEnemyCount); listRef.cCadence = EditorGUILayout.FloatField("Delay between spawns :", listRef.cCadence); listRef.cDelay = EditorGUILayout.FloatField("Delay until spawning starts", listRef.cDelay); } } } //lets make sure all our lists are the proper sizes private void AdjustListSizes() { //leve must contain at least one wave if (myLevelEnumerator.numOfWaves <= 0) { myLevelEnumerator.numOfWaves = 1; } //is our container bool large enough? if (myLevelEnumerator.numOfWaves != WaveCount.Length) { WaveCount = new bool[myLevelEnumerator.numOfWaves]; } //has a list been constructed? Avoid null error first time script attachment if (myLevelEnumerator.WaveInfo == null) { myLevelEnumerator.WaveInfo = new List<WaveStruct>(); } //is our list not large enough? while (myLevelEnumerator.numOfWaves > myLevelEnumerator.WaveInfo.Count) { myLevelEnumerator.WaveInfo.Add(new WaveStruct()); } //is our list too large? as items have been removed if (myLevelEnumerator.WaveInfo.Count > myLevelEnumerator.numOfWaves) { myLevelEnumerator.WaveInfo.RemoveRange((myLevelEnumerator.numOfWaves - 1), ((myLevelEnumerator.WaveInfo.Count - 1) - (myLevelEnumerator.numOfWaves - 1))); } } } |
Hope this was helpful. Comments are always appreciated.
-Cheers,
John
*For simplicity I used a list instead of an array. I put this list of WaveObj’s (even though it is an enumerable) inside WaveEnumerator so I could handle wave size (count) inside of the Unity IDE.
Thank you for this! I’m researching the subject and this is one of the best posts I found so far.