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.

Bug For loop used to spawn enemy objects not spawning correct amount when called from start

Discussion in 'Scripting' started by dusthound, Nov 28, 2022.

  1. dusthound

    dusthound

    Joined:
    Sep 18, 2018
    Posts:
    97
    Hi,

    I am working on a script that spawns waves of enemies (aka Targets) for a gallery shooter project and cannot get the script to consistently spawn the correct amount of enemies. I have built a class called WaveData that stores all the information about the wave of enemies (Quantity of enemies to spawn, Type of enemy to spawn, spawn position, interval to wait between spawning enemies, interval to wait between waves). The act of spawning the targets is handled using a For loop inside a coroutine (Titled SpawnTargets, this is also found in the WaveData class) The SpawnTargets coroutine is called from within either the start or update function inside my main class (called WaveSpawnController). This is where we get to the heart of the problem, when calling the SpawnTargets coroutine from within the start method, it does not spawn enough targets (it consistently falls one short). However, when it is called from within the Update function it spawns the correct amount of targets.

    btw: I am handling the act of object spawning using a pooling system, not Instantiate. I'll have that script linked below as well

    thanks!!











    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class WaveSpawnController1 : MonoBehaviour
    7. {
    8.     #region Variables
    9.     public ObjectPoolingControl PoolingSystem;
    10.  
    11.     public float YellowTargetSpeed, OrangeTargetSpeed, RedTargetSpeed;
    12.     public List<WaveData> Waves;
    13.     [SerializeField] int CurrentWave = 0;
    14.     #endregion
    15.  
    16.     #region CustomDataTypes
    17.     [Serializable]
    18.     public class WaveData
    19.     {
    20.         //Target To spawn
    21.         public string TargetType;
    22.  
    23.         //Amount of targets in the wave
    24.         public int Quantity;
    25.  
    26.         //Start position and destination position
    27.         public Vector3 StartPos, EndPos;
    28.  
    29.  
    30.         //Next wave to spawn
    31.         public WaveData NextWave;
    32.  
    33.         //Time to wait until spawning next object
    34.         public float SpawnInterval;
    35.        
    36.  
    37.         //Defines the wait period between waves and the time in seconds to start the next wave
    38.         public float TimeBetweenWaves;
    39.         [HideInInspector]public float TimeStartNextWave;
    40.  
    41.         //Reference to the pooling system
    42.         [HideInInspector]public ObjectPoolingControl poolingSystem;
    43.  
    44.         public IEnumerator SpawnTargets()
    45.         {
    46.             Debug.Log("Spawn sequence started");
    47.  
    48.             //Calculates the time in seconds at which the next wave can be started
    49.             TimeStartNextWave = Time.fixedTime + TimeBetweenWaves;
    50.  
    51.            
    52.  
    53.             //Spawns the targets
    54.             for (int i = 0; i < Quantity; i++)
    55.             {
    56.                 //Activates the next target using the pooling system
    57.                 poolingSystem.ActivateObject(TargetType, StartPos, Quaternion.identity);
    58.  
    59.                 //Waits
    60.                 yield return new WaitForSeconds(SpawnInterval);
    61.             }
    62.  
    63.            
    64.         }
    65.     }
    66.  
    67.     [Serializable]
    68.     public class TargetData
    69.     {
    70.         public string TargetType;
    71.         public int Quantity;
    72.         public Vector3 StartPos, EndPos;
    73.  
    74.  
    75.     }
    76.  
    77.  
    78.     #endregion
    79.  
    80.  
    81.  
    82.     // Start is called before the first frame update
    83.     void Awake()
    84.     {
    85.         foreach(WaveData wave in Waves)
    86.         {
    87.             wave.poolingSystem = PoolingSystem;
    88.         }
    89.     }
    90.  
    91.     private void Start()
    92.     {
    93.         StartCoroutine(Waves[0].SpawnTargets());
    94.         //Debug.Log(Waves.Count);
    95.     }
    96.  
    97.     // Update is called once per frame
    98.     void Update()
    99.     {
    100.        
    101.         if(CurrentWave < Waves.Count - 1 && Time.fixedTime > Waves[CurrentWave].TimeStartNextWave)
    102.         {
    103.             Debug.Log("Starting next wave");
    104.  
    105.             StartCoroutine(Waves[CurrentWave + 1].SpawnTargets());
    106.  
    107.             CurrentWave++;
    108.         }
    109.  
    110.        
    111.        
    112.     }
    113.  
    114. }
    115.  



    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System;
    5.  
    6. public class ObjectPoolingControl : MonoBehaviour
    7. {
    8.     //List of objects that must be created, the type of object and quantity is defined in the inspector
    9.     public ObjectToCreate[] ObjectsToCreate;
    10.  
    11.     //A list of every object in the pooling system, subdivided by the type of object
    12.     [SerializeField]private List<ObjectPool> TotalObjectPool = new List<ObjectPool>();
    13.  
    14.     //Custom class used to define the objects for the pooling system to create
    15.     #region ObjectToCreateClass
    16.     [Serializable]
    17.     public class ObjectToCreate
    18.     {
    19.         public string Name;
    20.         public GameObject GameObject;
    21.         public int Quantity;
    22.     }
    23.     #endregion
    24.  
    25.     //Custom class used to categorize and store all objects in the pooling system
    26.     #region ObjectPoolClass
    27.     [Serializable]
    28.     public class ObjectPool
    29.     {
    30.         //Title of the pool, matches the name of the object that will be contained within (ie: pool titled Bullet contains bullets)
    31.         public string Title;
    32.  
    33.         //List of all objects that are currently active in the scene, unavailable for use
    34.         public List<GameObject> ActiveObjects;
    35.  
    36.         //List of all inactive objects in the scene, currently available for use
    37.         public List<GameObject> InactiveObjects;
    38.  
    39.         //Default constructor
    40.         public ObjectPool()
    41.         {
    42.  
    43.         }
    44.        
    45.         //Advanced constructor
    46.         public ObjectPool(string title)
    47.         {
    48.             //Sets the title to the value passed through the parameter
    49.             Title = title;
    50.             //Creates a new list of active objects
    51.             ActiveObjects = new List<GameObject>();
    52.             //Creates a new list of inactive objects
    53.             InactiveObjects = new List<GameObject>();
    54.         }
    55.        
    56.     }
    57.  
    58.  
    59.  
    60.     #endregion
    61.  
    62.  
    63.     // Start is called before the first frame update
    64.     void Start()
    65.     {
    66.         //Goes through each of the objects that need to be created and creates the quantity set in the inspector
    67.         foreach (ObjectToCreate objectToCreate in ObjectsToCreate)
    68.         {
    69.             //Creates a new ObjectPool object to keep references to each of the created objects
    70.             ObjectPool newObjectPool = new ObjectPool(objectToCreate.Name);
    71.  
    72.             //Adds the new object pool to the master list of objects
    73.             TotalObjectPool.Add(newObjectPool);
    74.  
    75.            
    76.             //Instantiates each object in the ObjectsToCreate list to the quantity defined in each entry
    77.             for(int objectsCreated = 0; objectsCreated < objectToCreate.Quantity; objectsCreated++)
    78.             {
    79.                 //Creates the next object needed for the pool
    80.                 GameObject createdObject = CreateObject(objectToCreate, newObjectPool);
    81.             }
    82.         }
    83.     }
    84.  
    85.     // Update is called once per frame
    86.     void Update()
    87.     {
    88.        
    89.        
    90.     }
    91.  
    92.  
    93.  
    94.  
    95.     //Standard ActivateObject method
    96.     //Finds the object pool matching the title provided, activates the first object in the list and sets the transform
    97.     public void ActivateObject(string objectPoolTitle, Vector3 StartPosition, Quaternion StartRotation)
    98.     {
    99.         //Variables needed for logic
    100.         ObjectPool PoolToPullFrom;
    101.         GameObject ObjectToActivate;
    102.  
    103.  
    104.         //Find the object pool with matching title
    105.         foreach(ObjectPool objectPool in TotalObjectPool)
    106.         {
    107.             if (objectPool.Title == objectPoolTitle)
    108.             {
    109.                 //Stores a temporary reference to the object pool
    110.                 PoolToPullFrom = objectPool;
    111.  
    112.                 //Stores a temporary reference to the object being activated
    113.                 ObjectToActivate = objectPool.InactiveObjects[0];
    114.  
    115.                 //Removes object from inactive list
    116.                 PoolToPullFrom.InactiveObjects.RemoveAt(0);
    117.  
    118.                 //Activates the object
    119.                 ObjectToActivate.SetActive(true);
    120.  
    121.                 //Sets the transform values
    122.                 ObjectToActivate.transform.position = StartPosition;
    123.                 ObjectToActivate.transform.rotation = StartRotation;
    124.  
    125.                 //Adds object to active list
    126.                 PoolToPullFrom.ActiveObjects.Add(ObjectToActivate);
    127.  
    128.                 //Clears data in object temporary reference
    129.                 ObjectToActivate = null;
    130.             }
    131.         }
    132.     }
    133.  
    134.  
    135.  
    136.     public void DeactivateObject(string objectPoolTitle, GameObject objectToDeactivate)
    137.     {
    138.         //Temporary variables
    139.         ObjectPool PoolToPullFrom;
    140.         GameObject ObjectToDeactivate;
    141.  
    142.  
    143.         foreach(ObjectPool objectPool in TotalObjectPool)
    144.         {
    145.             if(objectPool.Title == objectPoolTitle)
    146.             {
    147.                 PoolToPullFrom = objectPool;
    148.  
    149.                 foreach(GameObject gameObject in PoolToPullFrom.ActiveObjects)
    150.                 {
    151.                     if(objectToDeactivate == gameObject)
    152.                     {
    153.                         ObjectToDeactivate = gameObject;
    154.  
    155.                         objectPool.ActiveObjects.Remove(ObjectToDeactivate);
    156.  
    157.                         ObjectToDeactivate.SetActive(false);
    158.  
    159.                         objectPool.InactiveObjects.Add(ObjectToDeactivate);
    160.  
    161.                         break;
    162.                     }
    163.                 }
    164.  
    165.  
    166.  
    167.  
    168.             }
    169.         }
    170.        
    171.     }
    172.  
    173.  
    174.  
    175.  
    176.  
    177.     #region CreateObject methods
    178.     //Creates a GameObject based on the information in the ObjectsToCreate list //Crappy overload
    179.     public GameObject CreateObject(ObjectToCreate objectToCreate, Vector3 startPosition, Quaternion startRotation)
    180.     {
    181.         GameObject CreatedObject = Instantiate(objectToCreate.GameObject, startPosition, startRotation);
    182.  
    183.         CreatedObject.SetActive(false);
    184.  
    185.         return CreatedObject;
    186.     }
    187.  
    188.  
    189.     //Creates a new object based on the list of objects to create, adds the created object to the object pool
    190.     public GameObject CreateObject(ObjectToCreate objectToCreate, ObjectPool objectPool)
    191.     {
    192.         //Instantiates the object and keeps a temporary reference to it
    193.         GameObject CreatedObject = Instantiate(objectToCreate.GameObject);
    194.  
    195.         //Sets the new object to inactive
    196.         CreatedObject.SetActive(false);
    197.  
    198.         //Adds the object to the corresponding pool
    199.         objectPool.InactiveObjects.Add(CreatedObject);
    200.  
    201.         //Returns the new object
    202.         return CreatedObject;
    203.     }
    204.     #endregion
    205. }
    206.  
    207.  
    208.  
    209.  
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    31,140
    Sounds like a boog! Slap some debug.log() in there and find out what's happening.

    I would also normalize your code so that you do NOT start a coroutine in Start() but instead do it always when the conditions are correct, and use CurrentWave as zero, 1, 2, 3, etc.

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    When in doubt, print it out!(tm)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.

    ALSO, just so you know:

    The costs and issues associated with object pooling / pools:

    https://forum.unity.com/threads/object-pooling.1329729/#post-8405055

    https://forum.unity.com/threads/object-pooling-in-a-tower-defense-game.1076897/#post-6945089