Anyway to Optimize this code?

I’m creating a Tower Defense game, and thus need a Wave Spawner script, I’m still inexperienced in Unity, and c#. I made this script, which allows me to control the flow of the wave 100% however I recognize that this potentially isn’t the best way to do things, I tried my best to comment it to help anyone willing to help me, understand the code better, I’d really appreciate any type of input. :slight_smile:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class EnemyStructure
{
    //Enemy clusters
    [HideInInspector] public string Name;
    [Header("Required")]
    public GameObject enemyType;
    public int amount = 1;
    public float timeToNextEnemy;
    [Header("MultiEnemy")]
    public float multiEnemySpacing;
    public bool ignoreSpacingTime;
}
[System.Serializable]
public class Waves
{
    //Waves holding the enemy clusters
    [HideInInspector] public string Name;
    public EnemyStructure[] enemiesInWave;
}
public class WaveSpawner : MonoBehaviour
{
    [Header("References")]
    [SerializeField] Transform enemyHolder;

    [Header("Properties")]
    [SerializeField] float waveCooldownTime;
    [SerializeField] bool autoStartWaves;

    [Header("Waves")]
    [SerializeField] int currentWave;
    [SerializeField] bool spawning;
    [SerializeField] Waves[] totalWaves;

    List<bool> waitQueue = new List<bool>();
    int waitQueueIndex;

    float waveCountdown;
    Transform spawnPoint;
    private void OnValidate()
    {
        LableArrays();
    }
    void LableArrays()
    {
        //Loops through all Waves and Enemies In Waves, and lables them in the Inspector
        for (int i = 0; i < totalWaves.Length; i++)
        {
            totalWaves[i].Name = "Wave " + (i + 1);
            for (int x = 0; x < totalWaves[i].enemiesInWave.Length; x++)
            {
                totalWaves[i].enemiesInWave[x].Name = "Enemy(ies) " + (x + 1);
            }
        }
    }
    void Start()
    {
        //Start function, what do you expect? We are setting variables
        spawning = false;
        waveCountdown = waveCooldownTime;
        currentWave = 0;
        spawnPoint = Waypoints.waypointsStatic[0];
        waitQueueIndex = -1;
    }
 
    void Update()
    {
        //If no enemies exist in the map, and the spawner is done spawning
        if(!spawning && enemyHolder.childCount == 0)
        {
            //If waves start automatically, countdown, then start wave
            if (autoStartWaves)
            {
                waveCountdown -= Time.deltaTime;
                if (waveCountdown <= 0)
                {
                    TryStartNextWave();
                }
            }
        }
        //Start next wave if we press "P"
        if (Input.GetKeyUp(KeyCode.P))
        {
            TryStartNextWave();
        }
    }
    IEnumerator SpawnWave()
    {
        //Setting variables we need
        int waveIndex = currentWave;
        spawning = true;
        waveCountdown = waveCooldownTime;
        //Here as well
        bool clusterWait = false;
        waitQueueIndex++;
        int queueNumber = waitQueueIndex;

        waitQueue.Add(clusterWait);
        //Loop through as many enemies/enemy clusters there are in the wave
        for (int i = 0; i < totalWaves[waveIndex-1].enemiesInWave.Length; i++)
        {
            //List of bools to act as local variables for whether or not to
            //wait for enemy clusters to spawn
            clusterWait = !totalWaves[waveIndex - 1].enemiesInWave[i].ignoreSpacingTime;
            waitQueue[queueNumber] = clusterWait;

            //Spawn in the enemy(ies) in the cluster
            StartCoroutine(EnemyCluster(waveIndex, i, clusterWait, queueNumber));

            //Wait if we want all enemies to spawn in the current cluster before the next cluster
            while (waitQueue[queueNumber])
            {
                yield return null;
            }

            //Wait for the time to next enemy cluster
            float timeToNextEnemyCluster = totalWaves[waveIndex - 1].enemiesInWave[i].timeToNextEnemy;
            if (timeToNextEnemyCluster > 0)
            {
                yield return new WaitForSeconds(timeToNextEnemyCluster);
            }
        }
        //Tell game we're done spawning in enemies for this wave
        if(currentWave == waveIndex)
        {
            spawning = false;
        }
    }
    IEnumerator EnemyCluster(int waveIndex, int spawnSpot, bool resetWait, int queueNumber)
    {
        //Loop through all enemies to spawn in the enemy cluster
        for (int x = 0; x < totalWaves[waveIndex - 1].enemiesInWave[spawnSpot].amount; x++)
        {
            //Spawn in an enemy
            SpawnEnemy(totalWaves[waveIndex - 1].enemiesInWave[spawnSpot].enemyType);

            //If enemy spacing exists, wait
            float spacingTime = totalWaves[waveIndex - 1].enemiesInWave[spawnSpot].multiEnemySpacing;
            if (spacingTime > 0)
            {
                yield return new WaitForSeconds(spacingTime);
            }
        }
        //If we decided to wait for all enemies in cluster to spawn before the next one, reset the queue
        if (resetWait)
        {
            waitQueue[queueNumber] = false;
        }
    }
    void SpawnEnemy(GameObject enemy)
    {
        //Creates enemy at spawnpoint
        GameObject spawnedEnemy = Instantiate(enemy, spawnPoint.position, Quaternion.identity);
        //Set enemies parent to the enemy holder for tracking
        spawnedEnemy.transform.SetParent(enemyHolder);
    }

    public void TryStartNextWave()
    {
        //Increment wave number and start next wave
        if (currentWave < totalWaves.Length)
        {
            currentWave++;
            StartCoroutine(SpawnWave());
        }
        else
        {
            Win();
        }
    }

    void Win()
    {
        print("Player Won!");
    }
}

Before you change this code why don’t you make a separate code to check whether or not you want the functionality this code provides. For example you have 100% control over the flow wave and you are suggesting you do not want that level of control? Like balloons tower defence they can come clustered or linearly and queue.

if by optimal you want a smaller code structure I would assume most basic it could get is like this —

MakeWave()- add to list number of enemies
(if cluster less than or equal to zero)
EjectUnit(int whichindex) probably 0
cluster = How_long_a_tick_is
Wave Remove at whichindex

Send him on his path or plug him into his path iterate path.

if you had something as simple as that you would be fine. Ensure that you do not want the functionality you may no longer have before decimating the code.

also
The Inumerators may be your problem. But maybe somebody else can look at this.

Check out Step #2 below… it is critical to understanding any code, including your own.

Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

How to do tutorials properly, two (2) simple steps to success:

Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That’s how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.
Fortunately this is the easiest part to get right: Be a robot. Don’t make any mistakes.
BE PERFECT IN EVERYTHING YOU DO HERE!!

If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.

Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there’s an error, you will NEVER be the first guy to find it.

Beyond that, Step 3, 4, 5 and 6 become easy because you already understand!

You have a issue line 165.

        if (currentWave < totalWaves.Length)
        {
            currentWave++;
            StartCoroutine(SpawnWave());
        }

with this code you will reach currentWave == totalWaves.Length and then totalWaves[currentWaves] does not exist.

if (currentWave < (totalWaves.Length-1))

should do the trick.

I see that you have fixed it by using waveIndex - 1 everytime but in my opinion this is unnecessary and source for bugs.

Your waveIndex should start at 0 and finish at totalWaves.Length - 1, otherwise I’m not sure what your code does when waveIndex == 0.

Nope, my code works, if there are 6 waves, totalWaves.Length will be == 6, therefore if waveIndex == 6 then we will not call a new wave and instead win.

Just glancing, in function enemyCluster you’re using looking at totalWaves[waveIndex - 1].enemiesInWave[spawnSpot]. At the top of the function you may as well grab that: EnemyStructure ee = totalWaves[waveIndex - 1].enemiesInWave[spawnSpot]; then just use ee everywhere. As well as shorter, it makes it more obvious that you’re using the same thing in those 3 places – and also reduces the chance for error if done this way for new code. Or, this is a more programmery idea, don’t even pass it waveIndex and spawnSpot – pass it the EnemyStructure instead.

Then a minor thing, in spawnWave you’re got if (timeToNextEnemyCluster > 0) protecting a WaitForSeconds. I wouldn’t bother, it can be confusing since check for zero feels like it can’t be zero or bad things happen.

1 Like

I didn’t follow a tutorial for this, I made this code myself, I think you may have understood what I meant, as I forgot a comma.