Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Is There A Way To Do This...

Discussion in 'Scripting' started by DRRosen3, Nov 20, 2014.

  1. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    683
    What I'm trying to accomplish is an enemy spawner of sorts. However, unlike the ones I seem to come across, I don't want it to respawn the enemy based on a timer. What I want is to have x amount of enemies in the scene at a time. Then, if the enemy dies, or is captured I want to remove that enemy from the scene, and then wait x amount of time before respawning a new enemy. However, keep in mind I never want to exceed x amount of enemies at any given time. And if 1 enemy dies at let's say 3:10 and then another enemy is captured at 3:12. If the respawn timer for each enemy is 3 minutes, the first enemy should respawn at 3:13 and the second shouldn't respawn until 3:15. Any suggestions or direction? I posted this here because obviously I don't want to use the FindGameObjectsWithTag() because it'll cause too much CPU usage.
     
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Just maintain a list of alive enemies

    Code (csharp):
    1.  
    2. public class Enemy
    3. {
    4.     public void Die()
    5.     {
    6.         spawner.RemoveEnemy(this);
    7.     }
    8. }
    9.  
    10. pubic class Spawner
    11. {
    12.     private List<Enemy> enemies;
    13.  
    14.     public RemoveEnemy(Enemy enemy)
    15.     {
    16.         enemies.Remove(enemy);
    17.         StartCoroutine(Respawn());
    18.     }
    19.  
    20.     IEnumerator Respawn()
    21.     {
    22.         yield return new WaitForSeconds(180);
    23.         enemies.Add(Instantiate(......).GetComponent<Enemy>());
    24.     }
    25. }
    26.  
     
  3. RSG

    RSG

    Joined:
    Feb 20, 2013
    Posts:
    93
    I recommend using a manager class to handle when your enemies are created and destroyed. Also, to help with performance, use an object pool so that you don’t have to constantly create and destroy game objects. In the manager class I would add the following properties:

    • Max enemy count
    • Re-spawn delay
    • Enemy list to track all enemies
    • List to track enemies that needs to re-spawn
    Inside of the manager class you can also track how many enemies you create.

    Code (CSharp):
    1. public class EnemyManager : MonoBehaviour
    2. {
    3.     private int maxEnemyCount = 10;
    4.     private float respawnDelay = 2; // 2 seconds
    5.     public static List<Enemy> TotalEnemies; // Includes dead ones
    6.     public static List<Enemy> DeadEnemies;
    7.  
    8.     private void Update()
    9.     {
    10.         while(TotalEnemies.Count < maxEnemyCount)
    11.         {
    12.             // Create enemies and add to list
    13.         }
    14.     }
    15. }
    Also use a class to model each of your enemies. In addition, add a property to determine if an enemy is dead and a property to determine when it died, that way you can track when the enemy needs to re-spawn.

    Code (CSharp):
    1. public class Enemy : MonoBehaviour
    2. {
    3.     public bool isAlive;
    4.     public float timeOfDeath;
    5. }
    Now for the fun part, when you kill an enemy mark it as dead but also track the time of dead and add it to the list of dead enemies.

    Code (CSharp):
    1. private void SetDead()
    2. {
    3.     this.isAlive = false;
    4.     this.timeOfDeath = Time.time;
    5.  
    6.     EnemyManager.DeadEnemies.Add(this);
    7.  
    8.     // Other code to disable object
    9. }
    From within your manager class, check the time on all enemies that need to re-spawn and that’s it

    Code (CSharp):
    1.  
    2. for(var i = 0; i < DeadEnemies.Count; i++)
    3. {
    4.      // Get enemy
    5.      Enemy enemy = DeadEnemies[i];
    6.  
    7.      // Check how long since the enemy died
    8.      var time = Time.time - enemy.timeOfDeath;
    9.  
    10.      // check if enemy needs to respawn
    11.      if(time > respawnDelay)
    12.      {
    13.           enemy.isAlive = true;
    14.           // Remove from list
    15.           DeadEnemies.RemoveAt(i);
    16.  
    17.           // Do code to enable object, respawn...
    18.  
    19.           // update counter
    20.           i--;
    21.      }
    22. }
    23.  
     
  4. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    683
    Thanks a lot @KelsoMRK and @RSG . Both great ideas. I think for the complexity of my project RSG's idea would work better simply because I need to tell the GameObject that is the Enemy to die from a third script (which I already have up and running properly).

    Only snag I'm hitting now RSG is a NullReferenceException. in the SetDead() function I have it set to Destroy() the GameObject that the Enemy class is attached too. I thought this would be fine, since the List<T> inside the SpawnManager class stored the information. However, after the enemy GameObject is destroyed, and the spawnDelay is up, I get the NullReference Exception.
     
  5. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I would personally rather avoid constantly polling the number of alive enemies and the respawn timers of all the dead ones but to each their own. Mine was also nowhere near a complete example :)
     
  6. RSG

    RSG

    Joined:
    Feb 20, 2013
    Posts:
    93
    When you destroy a game object, unity destroys the object as well as all attached mono behaviors. That’s why you are getting a null reference in your enemies list.


    If you want to destroy a game object but also want to keep the enemy information, then you could use a regular class or a struct instead to record your data:

    Code (CSharp):
    1. public class EnemyManager : MonoBehaviour
    2. {
    3.     // Track enemy info instead
    4.     public static List<EnemyInfo> DeadEnemies = new List<EnemyInfo>();
    5. }
    6.  
    7. public class Enemy : MonoBehaviour
    8. {
    9.     public EnemyInfo Info;
    10.  
    11.     private void KillMe()
    12.     {
    13.         // Update info
    14.         Info.IsAlive = false;
    15.         Info.TimeOfDead = Time.time;
    16.  
    17.         // keep info only, but destroy object
    18.         EnemyManager.DeadEnemies.Add(Info);
    19.  
    20.         Destroy(this);
    21.     }
    22. }
    23.  
    24. public struct EnemyInfo
    25. {
    26.     public bool IsAlive;
    27.     public float TimeOfDead;
    28. }

    The reason that previously I suggesting using an object pool, was because creating and destroying game objects can be expensive. Instead you could keep the object and simply disable it, but depending on the type of game that you are making this may not be an issue. Hope that helps
     
  7. DRRosen3

    DRRosen3

    Joined:
    Jan 30, 2014
    Posts:
    683
    @KelsoMRK Oh believe you me I get where you were going with your example. I just couldn't picture how to make it work in MY project. Still kudos and thanks for helping!

    @RSG I wasn't aware that creating/destroying objects was so taxing. Hmmm. I'll play around with your second suggestion and see what I come up with. For now, to make your first suggestion work, I've just changed it from Destroy()'ing the object to disabling it instead. It's working so far, but I may come across something (or add something later) that forces me to consider your latest suggestion.
     
  8. RSG

    RSG

    Joined:
    Feb 20, 2013
    Posts:
    93
    There are definitely better ways of doing this; you could use coroutines, events, stacks, etc. I guess I was focusing more on the idea of how to approach this with some basic examples just to get going. :D