0
\$\begingroup\$

I am making a shooting game, where I have a countdown as I destroy enemies. I want to implement logic when all the game objects are destroyed within the given time, and the countdown reaches zero.

How do I do that?

\$\endgroup\$
-2
\$\begingroup\$

You need to find a way of identifying all objects of interest. Do they have a particular script on them? You simply count them

if(GameObject.FindObjectOfType<EnemyScript>()==null)
{
    // Everything destroyed
}
else
{
    // Something survived
}
\$\endgroup\$
4
  • 7
    \$\begingroup\$ Note that searching for the objects frequently (eg. every frame) can be quite expensive and slow. It may be better to have the EnemyScript increment a shared counter on Start() (to say "I am here"), and decrement that counter in OnDestroy() (to say "I was shot"). Then you always know the number of active EnemyScripts without searching for them every time (handy if you want to display this number in UI, etc). You can have the decrement method check for zero and fire off the "everything destroyed" event in response. \$\endgroup\$ – DMGregory Nov 1 '16 at 21:49
  • \$\begingroup\$ It can be slow. However I would suggest going with this method and then when you get to a game you are happy with that is the time to profile & optimise. Other solutions are incredibly over engineered and if it turns out the game mode doesn't work as thought it's a few lines to delete. Removing the larger solutions given is going to be far harder. Plus they'll take longer to implement when getting to the game loop is the key here. \$\endgroup\$ – PompeyPaul Nov 3 '16 at 9:16
  • 1
    \$\begingroup\$ I would consider that a false choice. We can solve this problem in a way that's both simple/fast to author and efficient to run - I've added an answer to demonstrate. It's the same concept described above and in the other answers, just with a drop-in example to show how little code and maintenance is required. \$\endgroup\$ – DMGregory Nov 3 '16 at 13:35
  • \$\begingroup\$ I think you should carefully study this. Especially point 9 howtomakeanrpg.com/a/not-your-problem.html \$\endgroup\$ – PompeyPaul Nov 9 '16 at 23:00
3
\$\begingroup\$

As mentioned, you could use UnityEngine.GameObject.FindObjectOfType<>() to find all available instances of a particular script, ideally the script that defines these game objects as enemies, in your game. This works, but it involves a lot of redundant work. You do not need to check if there is still an enemy alive in every frame. Instead, have your enemies notify us when they are destroyed.


Creating an Objective Manager

Ideally, your main logic would be aware of the enemies, and the enemies would notify the main logic, when they are destroyed. I usually implement this behavior through a singleton objective manager class.

public class ObjectiveManager   
{
    public static ObjectiveManager instance;

    void Awake()
    {
        if(instance == null)
        {
            instance = this;
        }
        else if(instance != this)
        {
            if(gameObject == null)
            {
                Destroy(this);
            }
            else
            {
                Destroy(gameObject);
            }
        }
    }
}

By being created in this way, we can always access our ObjectiveManager as an instance, even when we do not have a direct reference. That is because we have a static reference to our single instance: ObjectiveManager.instance. As an alternative to using a singleton and having a static reference to the objective manager, you could provide each enemy with a reference to ObjectiveManager.


Creating a list of enemies..

.. By searching all game objects

You have two fairly easy ways to find all the active enemies in your scene: by tag and by script. They both work fairly similar to eachother;

  • FindObjectsOfType<Enemy>() will find all active instances of Enemy, and will retrieve a list of Object types.
  • FindObjectsWithTag("Enemy") will find any active instance of a game object tagged with the string "Enemy". This allows you to specify specific Enemy instances to add to your count, and will retrieve a list of GameObject types.

Once you have decided how you wish to create your list, doing so is fairly simple. Note that you should only implement the code relating to the means in wish you wish to create your list.

public class ObjectiveManager
{
    public static ObjectiveManager instance;

    public int enemyCount;

    void Awake()
    {
        /* logic for setting up the instance reference */

        Object[] enemyList = GameObject.FindObjectsOfType<Enemy>();
        GameObject[] enemyList = GameObject.FindObjectsWithTag("Enemy");

        // Since the first element in an array is 0, we will 
        // increment the initial count by 1 to correct the real count.
        enemyCount = (enemyList.Length() + 1);
    }

.. By having your Enemy inform ObjectiveManager on Awake()

With a singleton objective manager, it is even easier to have the Enemy class inform ObjectiveManager that it needs to be added to the count. This is especially handy if you wish to add more Enemy game objects mid-game.

First, you would add a public method to ObjectiveManager to handle incrementing the count.

public class ObjectiveManager
{
    public static ObjectiveManager instance;
    public int enemyCount;

    public void AddEnemy()
    {
        enemyCount++;
    }
}

Then, on your enemy, add logic to the Awake() method to alert the ObjectiveManager.

public class Enemy
{
    void Awake()
    {
        ObjectiveManager.instance.AddEnemy();
    }
}

Alerting ObjectiveManager when an enemy is destroyed

From there, you can use the Enemy objects OnDestroy() method to inform ObjectiveManager that it has to decrement enemyCount by 1. Assuming we have correctly counted the total enemies at the start, we know that when we kill the last enemy, enemyCount == 0.

public class Enemy
{
    void OnDestroy()
    {
        ObjectiveManager.instance.EnemyDestroyed();
    }
}

public class ObjectiveManager
{
    public static ObjectiveManager instance;
    public int enemyCount;

    public void EnemyDestroyed()
    {
        enemyCount --;

        if(enemyCount <= 0)
        {
            AllEnemiesDestroyed();
        }    
    }

    public void AllEnemiesDestroyed()
    {
        // all enemies destroyed. Continue appropriate logic from here.
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ This is incredibly over engineered and assumes that the current game concept is what ships. It's adding a large amount of code to a source base which adds maintenance overhead. I would agree with this solution if the question was "How do I optimise my game to remove GameObject.FindObjectOfType<>()" however for getting a game stood up this solution is way over engineered. \$\endgroup\$ – PompeyPaul Nov 3 '16 at 9:19
  • 1
    \$\begingroup\$ @PompeyPaul, you would say its over engineered, I would say its under-engineered, ultimately it took 5 minutes to write, and if other users want to copy and paste it across to use as a placeholder, it should take substantially less. Ultimately, we strive for quality and depth, You are entitle to your opinion, but the votes do speak for themselves. \$\endgroup\$ – Gnemlock Nov 3 '16 at 21:42
2
\$\begingroup\$

There was a complaint that any alternatives to the currently accepted "Find" answer are "over-engineered," but this is not necessarily the case. We can get both good performance and simple code:

public class CountedObject : MonoBehaviour {

    public static int count { get; private set; }

    public static event System.Action AllObjectsDestroyed;

    void Start () {
        // Count every object as it's initialized.
        count++;
    }

    void OnDestroy () {
        // Decrement the count when objects are destroyed,
        // and optionally fire an event when the last one is gone.
        if (--count == 0 && AllObjectsDestroyed != null)
            AllObjectsDestroyed();
    }
}

Now scripts that are interested in how many objects are left can find out in two ways:

  • By reading CountedObject.count whenever it needs
  • By subscribing to the event CountedObject.AllObjectsDestroyed += OnAllDestroyed;

The cost for this is quite modest:

  • Very few lines of code
  • Constant time to initialize
  • Constant time to read the current count

    (for the Find method, this cost is linear in the number of objects in the scene)

  • Constant time to destroy
  • Constant time per subscriber to notify when the last object is destroyed, and this cost is incurred only when the final destruction happens

    (for the Find method, this cost is linear in the number of objects in the scene every update tick that we check it)

This is not as full-featured and robust as an object manager that can track multiple separate object counts or expose more inspector control, but it gets a greater degree of both flexibility and performance than the accepted answer.

We should of course be wary of premature optimization leading us to excessive complexity, but that doesn't mean we should use code we already know is inefficient when there are much better-performing options available with similar code simplicity. In this day of mobile gaming, even if an inefficiency doesn't hit your framerate, it's still sapping the player's battery unnecessarily when there could be a simple way to let them play longer. ;)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.