Unity : TD Wave Spawning & Editing Scripts

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 /// <summary>
2 /// Object for each rounds WaveInfo
3 /// </summary>
4 [System.Serializable]
5 public class WaveObj
6 {
7    public string cName; //what is this enemies name
8    public GameObject cEnemy; //what enemy in this wave
9    public GameObject cSpawn; //where does this enemy spawn?
10    public int cEnemyCount; //how many enemies in this wave
11  public float cCadence; //what is the spawn delay between enemies in this wave
12  public float cDelay; //delay until next wave
13 
14 /// <summary>
15 /// Object for holding a waves info
16 /// </summary>
17 /// <param name="WaveCount">Total waves</param>
18 /// <param name="cEnemy">Current Enemy</param>
19 /// <param name="cEnemyCount">How many of current enemy will spawn in this wave</param>
20 /// <param name="cCadence">What is the delay between spawns</param>
21 /// <param name="cDelay">What is the delay after this wave before the next begins</param>
22 public WaveObj(string cName, GameObject cEnemy, GameObject cSpawn, int cEnemyCount, float cCadence, float cDelay)
23  {
24        this.cName = cName;
25        this.cEnemy = cEnemy;
26        this.cSpawn = cSpawn;
27        this.cEnemyCount = cEnemyCount;
28        this.cCadence = cCadence;
29        this.cDelay = cDelay;
30    }
31    public WaveObj()
32    { }
33  }

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 /// <summary>
2 /// Enumerator of enemy waves, info filled via unity IDE with custom Editor script WaveDesigner
3 /// </summary>
4 public class WaveEnumerator : MonoBehaviour, IEnumerator
5 {
6    public List<WaveObj> WaveInfo;
7    public int numOfWaves;
8    private int spot;
9 
10    public WaveEnumerator()
11    {
12        spot = -1;
13    }
14 
15    //Required IEnumerator method, return our enumerator
16    public IEnumerator GetEnumerator()
17    {
18        return (IEnumerator)this;
19    }
20 
21    //Required IEnumerator method, line up next object and return true if exists
22    public bool MoveNext()
23    {
24        spot++;
25        return (spot < WaveInfo.Count);
26 
27    }
28    ////Required IEnumerator method, reset the enumerator
29    public void Reset()
30    {
31        spot = -1;
32    }
33 
34    ////Required IEnumerator method, return current object
35    public object Current
36    {
37        get { return WaveInfo[spot]; }
38        set { } //empty as changes are not allowed
39    }
40 }

Once the level is loaded I kick-off a coroutine to handle spawning, and passing the appropriate references to our spawned enemies

      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.

[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.