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. Dismiss Notice

[SOLVED] Issue with my enemy spawn system

Discussion in 'Scripting' started by PlazmaInteractive, Oct 22, 2016.

  1. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    Sup guys, I've got an issue with the enemy spawn system in my game. Before I explain the problem, I'll explain what I'm trying to achieve first.

    1. The player starts off at Level 1, which contains a list of waves they have to complete. The list of waves is an array which is located inside each level in a list, another array.
    2. Once the player finishes all the waves required to complete a level, the game pauses and displays a Shop Panel, where the player can buy or upgrade themselves. They then press a Continue button where they will start the next level, Level 2.
    3. Level 2 starts, containing a different list of waves compared to Level 1. When the player finishes that level, the Shop Panel appears again and they'll start Level 3 by pressing a Continue button. The cycle continues until they beat the entire game.

    So far, I've gotten the spawning system to work. It's based off Brackey's version with a bit of tweaking for my purpose. The problem I'm having is to make sure the next level in the game starts spawning enemy correctly when the player presses the Continue button. In my current situation, whenever I press the Continue button, the Shop Panel disappears and the game resumes as normal but enemies aren't spawning.

    EnemySpawnSystem script:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class EnemySpawnSystem : MonoBehaviour
    5. {
    6.     [System.Serializable]
    7.     public class Wave
    8.     {
    9.         public string name;
    10.         public GameObject enemy;
    11.         public float delay;
    12.         public int count;
    13.         public float rate;
    14.         public bool isInvade;
    15.     }
    16.  
    17.     [System.Serializable]
    18.     public class LevelBehaviour
    19.     {
    20.         public string name;
    21.         public Wave[] wave;
    22.     }
    23.  
    24.     public bool levelCompleted;
    25.     public bool waveCompleted;
    26.     public Transform invadeSpawnPoint;
    27.     public GameObject panelGUI;
    28.     public LevelBehaviour[] levels;
    29.     public GameObject waveStartText;
    30.  
    31.     private DataManager data;
    32.     private float endDelay = 3f;
    33.     private bool isSpawning;
    34.  
    35.     void Start()
    36.     {
    37.         data = GameObject.FindWithTag("PlayManager").GetComponent<DataManager>();
    38.     }
    39.  
    40.     void Update()
    41.     {
    42.         if (waveCompleted)
    43.         {
    44.             if (!EnemyAlive())
    45.             {
    46.                 // Delay before level ended
    47.                 endDelay -= Time.deltaTime;
    48.                 if (endDelay <= 0f)
    49.                 {
    50.                     // Level completed and updates current level
    51.                     levelCompleted = true;
    52.                     data.currentLevel++;
    53.  
    54.                     // Show the shop panel
    55.                     Time.timeScale = 0f;
    56.                     panelGUI.SetActive(true);
    57.                     panelGUI.transform.GetChild(1).gameObject.SetActive(true);
    58.                 }
    59.             }
    60.             else
    61.                 return;
    62.         }
    63.  
    64.         if (!isSpawning)
    65.         {
    66.             // Starts spawning enemies
    67.             StartCoroutine(SpawnLoop());
    68.             isSpawning = true;
    69.         }
    70.     }
    71.  
    72.     IEnumerator SpawnLoop()
    73.     {
    74.         if (!levelCompleted)
    75.         {
    76.             // If level is not complete, start spawning waves
    77.             foreach (var level in levels)
    78.             {
    79.                 if (!waveCompleted)
    80.                 {
    81.                     // If wave is not complete, continues spawning more waves
    82.                     foreach (var wave in level.wave)
    83.                     {
    84.                         // Delays wave first before spawning
    85.                         if (wave.delay > 0)
    86.                         {
    87.                             waveStartText.SetActive(true);
    88.                             yield return new WaitForSeconds(wave.delay);
    89.                             waveStartText.SetActive(false);
    90.                         }
    91.  
    92.                         if (wave.enemy != null && wave.count > 0)
    93.                         {
    94.                             for (int i = 0; i < wave.count; i++)
    95.                             {
    96.                                 // If enemy is Invade, spawn in invade spawn point
    97.                                 if (wave.isInvade)
    98.                                     Instantiate(wave.enemy, invadeSpawnPoint.position + new Vector3(Random.Range(-2, 2), Random.Range(-2, 2), 0f), Quaternion.identity);
    99.                                 // Else spawns enemy as normal
    100.                                 else
    101.                                 {
    102.                                     Instantiate(wave.enemy, new Vector2(Random.Range(-6, 6), Random.Range(-6, 6)), Quaternion.identity);
    103.                                     yield return new WaitForSeconds(wave.rate);
    104.                                 }
    105.                             }
    106.                         }
    107.                     }
    108.                     // Spawning waves for the level is completed
    109.                     waveCompleted = true;
    110.                 }
    111.             }
    112.         }
    113.     }
    114.  
    115.     bool EnemyAlive()
    116.     {
    117.         // If there's no enemy left, return false
    118.         if (!FindObjectOfType<EnemyStatsController>())
    119.             return false;
    120.  
    121.         // Returns true if there is enemy alive
    122.         return true;
    123.     }
    124. }
    I think the problem here is that it continues iterating through the foreach level loop and checks each level. It checks Level 1 and checks Level 2 too. So when I press the Continue button, the foreach loop has already done iterating through and that's the reason why enemies won't spawn. So basically what I'm looking for is to somehow pause between each iteration when waveCompleted is true, then continue from where it left off when I press the Continue button.

    How would one do this? If anyone can guide me to the correct solution, that'll be awesome.
     
  2. Thorskin

    Thorskin

    Joined:
    Oct 10, 2016
    Posts:
    13
    Update is still running while you are inside the shop.
    Line 52 will increment the level once for each frame.
     
  3. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    The simplest way is to add a bool that your in the shop, and have the panels OnClick set that bool to false:
    Code (CSharp):
    1. bool inShop;
    2.  
    3. void Start()
    4. {
    5.         inShop = false;
    6.         data=GameObject.FindWithTag("PlayManager").GetComponent<DataManager>();
    7. }
    8.  
    9. // Make the OnClick Continue button call this function
    10. public void LeftShop()
    11. {
    12.         inShop = false;
    13. }
    14.  
    15. void Update()
    16. {
    17.            if (inShop)
    18.               return;
    19.            // ... lots of other code
    20.      
    21.            // ... more code
    22.    
    23.            panelGUI.SetActive(true);
    24.            panelGUI.transform.GetChild(1).gameObject.SetActive(true);
    25.             inShop = true;
     
  4. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    This didn't work. Again, I think the problem here is that the foreach loop in line 77 iterates through every level available, so by the time I resume the game using the Continue button, there'll be no level left to iterate which would explain the problem of 'Level 2 not spawning any enemies' when in fact there's nothing left to iterate. Here's a bit more explanation.

    Level 1 is iterated through at the beginning. As waveCompleted is set to false, it will spawn the waves contained in that level. All waves is done spawning in Level 1, so it sets waveCompleted as true. As it'll take time for the player to kill of all enemies, meanwhile, the EnemySpawnScript will continue iterating to Level 2 from line 77. It checks that waveCompleted is set to true, thus it won't spawn any enemies. The player finally kills of all enemies, and the Shop Panel is enabled. The player clicks the Continue button and realizes that no enemies is spawning. This is because the script has already iterated through Level 2, so that's why it won't spawn any enemies.

    So the solution I'm looking for is, how do I pause the foreach loop when waveCompleted is set to true? This way, the loop will be stuck at Level 1, and once the player clicks the Continue button, that button will set waveCompleted to false and the loop will continue iterating to Level 2.

    Screenshot for those needing more details.
    Capture.PNG
     
  5. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Just change your SpawnLoop so it doesn't loop through all the levels. Instead pass it a level and have it spawn through all the waves in that level. Basically rip out the outer loop of SpawnLoop. Then have the Update function keep track of the level. You can have isSpawning set to false also in LeftShop function. Then you update function will start the coroutine again, you just have to pass it the current level
     
  6. PlazmaInteractive

    PlazmaInteractive

    Joined:
    Aug 8, 2016
    Posts:
    114
    I didn't really follow your guide but I did find a solution that's in a way, based off your guide. So I took advantage of data.currentLevel to determine which level would be spawning waves next. Then I added a new bool, isCurrentLevel, inside my LevelBehaviour class then in Update, I set isCurrentLevel to true to levels[data.currentLevel - 1]. Inside SpawnLoop, I insert an if(level.isCurrentLevel) statement before checking if(!waveCompleted) so that when the script loops through each level, it will spawn the waves that has isCurrentLevel set to true.

    Here's the fixed script for those needing it and a screenshot of the Inspector:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class EnemySpawnSystem : MonoBehaviour
    5. {
    6.     [System.Serializable]
    7.     public class Wave
    8.     {
    9.         public string name;
    10.         public GameObject enemy;
    11.         public float delay;
    12.         public int count;
    13.         public float rate;
    14.         public bool isInvade;
    15.     }
    16.  
    17.     [System.Serializable]
    18.     public class LevelBehaviour
    19.     {
    20.         public string name;
    21.         public bool isCurrentLevel;
    22.         public Wave[] wave;
    23.     }
    24.  
    25.     public bool levelCompleted;
    26.     public bool waveCompleted;
    27.     public bool isSpawning;
    28.     public Transform invadeSpawnPoint;
    29.     public GameObject panelGUI;
    30.     public LevelBehaviour[] levels;
    31.     public GameObject waveStartText;
    32.  
    33.     private DataManager data;
    34.     private float endDelay;
    35.     private int oldLevel;
    36.  
    37.     void Start()
    38.     {
    39.         data = GameObject.FindWithTag("PlayManager").GetComponent<DataManager>();
    40.         oldLevel = data.currentLevel;
    41.     }
    42.  
    43.     void Update()
    44.     {
    45.         // Set isCurrentLevel to correct level based off data.isCurrentLevel
    46.         levels[data.currentLevel - 1].isCurrentLevel = true;
    47.  
    48.         if (waveCompleted)
    49.         {
    50.             if (!EnemyAlive())
    51.             {
    52.                 // Delay before level ended
    53.                 endDelay -= Time.deltaTime;
    54.                 if (endDelay <= 0f)
    55.                 {
    56.                     // Level completed
    57.                     levelCompleted = true;
    58.                     // Make sure current level increments once only
    59.                     if(data.currentLevel == oldLevel)
    60.                         data.currentLevel++;
    61.  
    62.                     foreach (var level in levels)
    63.                         level.isCurrentLevel = false;
    64.  
    65.                     // Show the shop panel and pauses the game
    66.                     Time.timeScale = 0f;
    67.                     panelGUI.SetActive(true);
    68.                     panelGUI.transform.GetChild(1).gameObject.SetActive(true);
    69.                 }
    70.             }
    71.             else
    72.                 return;
    73.         }
    74.  
    75.         if (!isSpawning)
    76.         {
    77.             // Starts spawning enemies
    78.             StartCoroutine(SpawnLoop());
    79.             isSpawning = true;
    80.         }
    81.     }
    82.  
    83.     IEnumerator SpawnLoop()
    84.     {
    85.         if (!levelCompleted)
    86.         {
    87.             // If level is not complete, start spawning waves
    88.             foreach (var level in levels)
    89.             {
    90.                 if (level.isCurrentLevel)
    91.                 {
    92.                     if (!waveCompleted)
    93.                     {
    94.                         // If wave is not complete, continues spawning more waves
    95.                         foreach (var wave in level.wave)
    96.                         {
    97.                             // Delays wave first before spawning
    98.                             if (wave.delay > 0)
    99.                             {
    100.                                 waveStartText.SetActive(true);
    101.                                 yield return new WaitForSeconds(wave.delay);
    102.                                 waveStartText.SetActive(false);
    103.                             }
    104.  
    105.                             if (wave.enemy != null && wave.count > 0)
    106.                             {
    107.                                 for (int i = 0; i < wave.count; i++)
    108.                                 {
    109.                                     // If enemy is Invade, spawn in invade spawn point
    110.                                     if (wave.isInvade)
    111.                                         Instantiate(wave.enemy, invadeSpawnPoint.position + new Vector3(Random.Range(-2, 2), Random.Range(-2, 2), 0f), Quaternion.identity);
    112.                                     // Else spawns enemy as normal
    113.                                     else
    114.                                     {
    115.                                         Instantiate(wave.enemy, new Vector2(Random.Range(-6, 6), Random.Range(-6, 6)), Quaternion.identity);
    116.                                         yield return new WaitForSeconds(wave.rate);
    117.                                     }
    118.                                 }
    119.                             }
    120.                         }
    121.                         // Spawning waves for the level is completed
    122.                         waveCompleted = true;
    123.                         // Reset end delay after wave is completed
    124.                         endDelay = 3f;
    125.                     }
    126.                 }
    127.             }
    128.         }
    129.     }
    130.  
    131.     bool EnemyAlive()
    132.     {
    133.         // If there's no enemy left, return false
    134.         if (!FindObjectOfType<EnemyStatsController>())
    135.             return false;
    136.  
    137.         // Returns true if there is enemy alive
    138.         return true;
    139.     }
    140.  
    141.     public void UpdateOldLevel()
    142.     {
    143.         // Update old level for continue button
    144.         oldLevel = data.currentLevel;
    145.     }
    146. }
    Capture.PNG