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.