Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question How do I repeat x times, with y delay between each?

Discussion in 'Editor & General Support' started by Duskbound, Nov 15, 2022.

  1. Duskbound

    Duskbound

    Joined:
    Jul 21, 2018
    Posts:
    4
    What I want to do is really simple, just as the title states.
    I have a "wave" system that is supposed to spawn enemies in waves.
    They work in sections, each section is a class with 3 variables: a GameObject for the enemy prefab (Enemy), an int for the amount of enemies to spawn (Count), and a float for the delay between spawns (Delay). These sections are then stored in a list.
    Now, all I need is to create a loop for each section, looping Count times, with a delay of Delay between each loop.
    But the loops for each section should only run after the previous loop is done, I can't just do a for loop and invoke repeating or start a coroutine.
    There is probably a simple solution but I can't see it, and google has not been of help.

    Here is the WaveData:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. [System.Serializable]
    5. public class SectionTuple
    6. {
    7.     public GameObject Enemy;
    8.     public int Count;
    9.     public float Delay;
    10. }
    11.  
    12. [System.Serializable]
    13. public class WaveData
    14. {
    15.     [Header("Wave Attributes")]
    16.     [Tooltip("(Enemy, Count, Delay)")]
    17.     [SerializeField] public List<SectionTuple> Section;
    18. }
     
    Last edited: Nov 15, 2022
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,019
    do you mean:
    Only spawn next wave - after current enemies have been cleared,
    or spawn next wave always at that delay (after previous wave spawn) ?

    but in any case,
    i'd try putting for loop also inside the main coroutine,
    so you can wait between waves.

    And if need to wait until previous wave is cleared,
    can have a loop there to wait until wave is clear,
    while(waveCleared==false) yield return 0;
     
  3. Duskbound

    Duskbound

    Joined:
    Jul 21, 2018
    Posts:
    4
    I'm talking about a singular wave that is divided into sections, so each section should automatically start once the previous is over. The waves are started with the press of a button, so no need to care about that.

    And I'm unsure what you mean by putting a for loop inside the main coroutine?
    To begin with, I never said anything about having a main coroutine, but making one isn't difficult. What confuses me is the for loop. How exactly would that allow me to wait until one section is done before the next activates? If I create a for loop to spawn a section (using InvokeRepeating I suppose) with 5 enemies, with 1 section delay between each, and then continue to the next section with 10 enemies and .5 seconds between each, wouldn't it spawn them at the same time?

    Maybe I'm being unclear or misunderstanding how for loops work together with coroutines and InvokeRepeating?
     
  4. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,019
    im thinking something like this (this one has fixed values, but could use array/list of course)

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class Waves : MonoBehaviour
    5. {
    6.    // set this value elsewhere (or use enemycount other other methods to check when wave is cleared)
    7.     public bool waveCleared = false;
    8.  
    9.     void Start()
    10.     {
    11.         StartCoroutine(WaveSpawner());
    12.     }
    13.  
    14.     IEnumerator WaveSpawner()
    15.     {
    16.         int waves = 5;
    17.         int enemies = 3;
    18.         int waveDelay = 10;
    19.  
    20.         for (int i = 0; i < waves; i++)
    21.         {
    22.             // wait for delay between waves, although not needed if need to wait for clear already
    23.             Debug.Log("Waiting for next wave");
    24.             yield return new WaitForSeconds(waveDelay);
    25.  
    26.             waveCleared = false;
    27.  
    28.             Debug.Log("Wave " + (i + 1) + " starting");
    29.  
    30.             // spawn wave
    31.             for (int j = 0; j < enemies; j++)
    32.             {
    33.                 // spawn enemies here
    34.             }
    35.  
    36.             Debug.Log("Waiting for wave to be cleared clear");
    37.             // wait until no enemies (could also check count here here)
    38.             while (waveCleared == false)
    39.             {
    40.                 yield return null;
    41.             }
    42.             Debug.Log("Wave " + (i + 1) + " cleared");
    43.         }
    44.  
    45.         Debug.Log("All waves cleared");
    46.  
    47.         yield return null;
    48.     }
    49. }
    50.  
     
  5. Duskbound

    Duskbound

    Joined:
    Jul 21, 2018
    Posts:
    4
    The issue is this part:
    Code (CSharp):
    1. // spawn wave
    2. for (int j = 0; j < enemies; j++)
    3. {
    4. // spawn enemies here
    5. }
    there is supposed to be a delay between each enemy spawn. Spawning all of the enemies at once like this will just stack them on top of each other.
    Also it seems you misunderstood my meaning again. I'm only spawning one wave, which is divided into sections. The sections will not have any delay between them, but the enemies within the section will have a delay between them. The sections do not care if there are still enemies alive.
     
  6. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,019
    ah ok, so you have multiple sections spawning all at the same time
    and inside each section there is that delay for that section enemies?

    something like,
    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class Waves : MonoBehaviour
    5. {
    6.     void Start()
    7.     {
    8.         int sections = 3; // TODO loop your section array/list
    9.         // launch all section spawners as their own separate coroutines
    10.         for (int i = 0; i < sections; i++)
    11.         {
    12.             StartCoroutine(WaveSpawner(i));
    13.         }
    14.     }
    15.  
    16.     IEnumerator WaveSpawner(int section)
    17.     {
    18.         // TODO take this section values here from array or as parameter
    19.         int enemies = 3;
    20.         int delay = 10;
    21.  
    22.         // spawn this section mobs
    23.         for (int j = 0; j < enemies; j++)
    24.         {
    25.             // TODO spawn here
    26.  
    27.            // delay for this section enemy spawns
    28.             yield return new WaitForSeconds(delay);
    29.         }
    30.     }
    31. }
    32.  
     
  7. Duskbound

    Duskbound

    Joined:
    Jul 21, 2018
    Posts:
    4
    Once again not quite, but you've actually helped me figure it out...
    You're correct except that I don't want the sections to come at the same time, but one after the other. When I say no delay, I still want it to finish the previous section before spawning a new one.

    Anyway, thank you so much for the help!

    Here's the code I have to finally get it working:

    Code (CSharp):
    1. private IEnumerator SpawnWave()
    2.     {
    3.         float prevEnemyDelay = 0.0f;
    4.         foreach (SectionTuple section in waveData.Sections) {
    5.             int enemies = section.Count;
    6.             float enemyDelay = section.Delay;
    7.  
    8.             Debug.Log($"Section {waveData.Sections.IndexOf(section)+1}/{waveData.Sections.Count} starting");
    9.             InvokeRepeating(nameof(SpawnEnemy), prevEnemyDelay, enemyDelay);
    10.             prevEnemyDelay = enemyDelay;
    11.  
    12.             Debug.Log("Waiting for next section...");
    13.             yield return new WaitForSeconds(enemyDelay*enemies - enemyDelay + prevEnemyDelay);
    14.             CancelInvoke(nameof(SpawnEnemy));
    15.         }
    16.         yield break;
    17.     }
    Perhaps now you can see what I wanted.
    I store a prevEnemyDelay because otherwise the sections will overlap their first enemy with the previous sections last enemy.
    I then did what you told me to, which is looping through each section.
    I get the amount of enemies to spawn, and the delay between them.
    I log the progress cus why not.
    Then I deviated a little from your advice, by using an InvokeRepeating to spawn the enemies after "prevEnemyDelay", which for the first section will be 0, and for the next section will be the delay from the previous one. It then repeats the spawning with a delay of "enemyDelay".
    Lastly, I wait for the amount of time this section will take, which I had a little issue calculating but I finally figured it out in the end, I just have to calculate the enemyDelay multiplied by the number of enemies, then subtract one because otherwise it always spawns an extra enemy, and add the previousEnemyDelay that I used.
    Finally I can cancel the invoke and the loop will go to the next section.
     
    mgear likes this.