Is There A Way To Do This...

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.

Just maintain a list of alive enemies

public class Enemy
{
    public void Die()
    {
        spawner.RemoveEnemy(this);
    }
}

pubic class Spawner
{
    private List<Enemy> enemies;

    public RemoveEnemy(Enemy enemy)
    {
        enemies.Remove(enemy);
        StartCoroutine(Respawn());
    }

    IEnumerator Respawn()
    {
        yield return new WaitForSeconds(180);
        enemies.Add(Instantiate(......).GetComponent<Enemy>());
    }
}

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.

public class EnemyManager : MonoBehaviour
{
    private int maxEnemyCount = 10;
    private float respawnDelay = 2; // 2 seconds
    public static List<Enemy> TotalEnemies; // Includes dead ones
    public static List<Enemy> DeadEnemies;

    private void Update()
    {
        while(TotalEnemies.Count < maxEnemyCount)
        {
            // Create enemies and add to list
        }
    }
}

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.

public class Enemy : MonoBehaviour
{
    public bool isAlive;
    public float timeOfDeath;
}

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.

private void SetDead()
{
    this.isAlive = false;
    this.timeOfDeath = Time.time;
  
    EnemyManager.DeadEnemies.Add(this);

    // Other code to disable object
}

From within your manager class, check the time on all enemies that need to re-spawn and that’s it

for(var i = 0; i < DeadEnemies.Count; i++)
{
     // Get enemy
     Enemy enemy = DeadEnemies[i];

     // Check how long since the enemy died
     var time = Time.time - enemy.timeOfDeath;

     // check if enemy needs to respawn
     if(time > respawnDelay)
     {
          enemy.isAlive = true;
          // Remove from list
          DeadEnemies.RemoveAt(i);

          // Do code to enable object, respawn...

          // update counter
          i--;
     }
}

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 inside the SpawnManager class stored the information. However, after the enemy GameObject is destroyed, and the spawnDelay is up, I get the NullReference Exception.

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 :slight_smile:

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:

public class EnemyManager : MonoBehaviour
{
    // Track enemy info instead
    public static List<EnemyInfo> DeadEnemies = new List<EnemyInfo>();
}

public class Enemy : MonoBehaviour
{
    public EnemyInfo Info;

    private void KillMe()
    {
        // Update info
        Info.IsAlive = false;
        Info.TimeOfDead = Time.time;

        // keep info only, but destroy object
        EnemyManager.DeadEnemies.Add(Info);

        Destroy(this);
    }
}

public struct EnemyInfo
{
    public bool IsAlive;
    public float TimeOfDead;
}

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

@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.

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. :smile: