1

So... i was working at this Wave System for a little game and i wanted the system to wait a specific amount of time before spawning another enemy, so i did this thing:

void ExecuteWaveAction(WaveAction action)
{
    int numberOfSpawns = spawnLocations.Length;
    int currentSpawnToInstantiate = 0;
    float timeLeftToSpawnEnemy = 0f;

    for (int i = 0; i < action.quantityOfEnemysToSpawn; i++) 
    {
        if (timeLeftToSpawnEnemy < action.spawnRate) 
        {
            timeLeftToSpawnEnemy += Time.deltaTime;
            i--;
        }
        else
        {
            GameObject.Instantiate (action.enemyToSpawn, spawnLocations [currentSpawnToInstantiate].position, Quaternion.identity);
            currentSpawnToInstantiate++;

            timeLeftToSpawnEnemy = 0f;

            if (currentSpawnToInstantiate >= numberOfSpawns)
                currentSpawnToInstantiate = 0;
        }
    }
}

if you are asking yourself what a WaveAction is :

public struct WaveAction
{
    public int quantityOfEnemysToSpawn;
    public float spawnRate;
    public GameObject enemyToSpawn;
}

i don't know what is wrong with the code, when i am debugging everything seems to be fine, the script actually waits before spawning, but when i am playing the script spawn all creatures at once.

if someone could help me i would be very grateful, and last of all, if i made any spelling or english mistakes i am sorry, i am not a native english speaker

Nícolas
  • 406
  • 3
  • 8
  • 24
  • 2
    You add Time.deltaTime to timeLeftToSpawnEnemy inside a loop, i.e. your spawn rate is action.quantityOfEnemysToSpawn times faster then you expect. – rs232 Mar 21 '18 at 13:33
  • 1
    Also, by doing i-- after your've changed your timeLeftToSpawnEnemy you effectively make your code loop _in the same frame_ until someone finally spawns. – rs232 Mar 21 '18 at 13:35
  • So, what would be your way of doing it?? because i have no clue – Nícolas Mar 21 '18 at 13:39
  • 2
    Most people would go with a Coroutine. Easier to read, and it's frame independent. – Brandon Miller Mar 21 '18 at 13:42
  • While it is definitely possible to do that with your approach, I would recommend you to try to do what @BrandonMiller says: to use a Coroutine. Coroutines are designed for tasks like this. – rs232 Mar 21 '18 at 13:50

2 Answers2

3

I suggest to use a coroutine for that:

IEnumerator ExecuteWaveAction(WaveAction action) {
    int currentSpawnToInstantiate = 0;
    for (int i = 0; i < action.quantityOfEnemysToSpawn; i++) 
    {
            // spawn object
            GameObject.Instantiate (action.enemyToSpawn, spawnLocations [currentSpawnToInstantiate].position, Quaternion.identity);
            currentSpawnToInstantiate++;
            if (currentSpawnToInstantiate >= numberOfSpawns)
                currentSpawnToInstantiate = 0;

            // waits for 1 sec before continue. you can change the time value
            yield return new WaitForSeconds(1);
        }
    }
1

First let's take a look at Time.deltaTime:

The time in seconds it took to complete the last frame (Read Only).

Use this function to make your game frame rate independent.

In Unity whenever you need to formulate a time related scenario you have to use co-routines or else it's all up to your code logic to measure delta time.

In a simple coroutine we have the logic wrapped in a loop and we have a yield statement which value indicates the amount of time (frames) the coroutine should wait after each pass.

Update and FixedUpdate methods are Unity built-in coroutines which are being executed every Time.deltaTime and Time.fixedDeltaTime seconds respectively.

The logic of your spawning must have a coroutine in order to work since ExecuteWaveAction must use delta time.

int numberOfSpawns = spawnLocations.Length;
int currentSpawnToInstantiate = 0;
float timeLeftToSpawnEnemy = 0f;
WaveAction action = set this in advance;

void Start()
{
    action = ... 
    ExecuteWaveAction(); 
}

void ExecuteWaveAction()
{
    numberOfSpawns = spawnLocations.Length;
    currentSpawnToInstantiate = 0;
    timeLeftToSpawnEnemy = 0f;
    i = 0;    
}

Now look how the looped logic is being separated from the original method and being put in a coroutine:

int i = 0;
void Update()
{
    //if spawning is active then continue...
    if (i < action.quantityOfEnemysToSpawn) 
    {
        if (timeLeftToSpawnEnemy < action.spawnRate) 
        {
            timeLeftToSpawnEnemy += Time.deltaTime;
        }
        else
        {
            i++;

            GameObject.Instantiate (action.enemyToSpawn, spawnLocations [currentSpawnToInstantiate].position, Quaternion.identity);
            currentSpawnToInstantiate++;

            timeLeftToSpawnEnemy = 0f;

            if (currentSpawnToInstantiate >= numberOfSpawns)
                currentSpawnToInstantiate = 0;
        }
    }
}

Note:

You may wonder why can't we use Time.deltaTime in a loop?

Answer is: because Time.deltaTime represents the time length of one frame (which is basically equal to the delay between each two consecutive executions of the same Update method) but a simple loop runs all at once in one frame and thus Time.deltaTime only tells us some other delay which our loop isn't working upon it.

In other words Unity does not wait inside a loop for a frame to end, but it waits after the execution of each coroutine (waiting time = frame length (i.e. Time.deltaTime or Time.fixedDeltaTime or manual coroutine waiting time).

Bizhan
  • 16,157
  • 9
  • 63
  • 101