making a left enemies counter,count the number of enemies

Hi, I am working on a FPS game where enemies spawn in waves, and i want to make a counter for the number of enemies left to kill in the scene, and i do it by creating a var then finding the GameObjects with tag “Enemy” and adding it to the list or (var), but the problem with that is the length of the list wont update if i kill one of them, or is there a better way of doing the counter.

Here is the enemy script:

using UnityEngine;
using UnityEngine.AI;
using System.Collections;

public class EnemyController : MonoBehaviour
{

Transform target;
NavMeshAgent agent;

void Start()
{
    target = PlayerManager.instance.Player.transform;
    agent = GetComponent<NavMeshAgent>();
    
}

void Update()
{
    targetPlayer();
}

void targetPlayer()
{
        agent.SetDestination(target.position);
}

and i know that this wave spawner script is way too long but i dont know what parts are the important for you, sooo…

here it is:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System;
using System.Collections.Generic;

public class WaveSpawner : MonoBehaviour {

//public GameObject spawningNextWavePanel;
//public Text spawningNextWaveText;

public GameObject waveCompletedPanel;
public Text waveCompletedText;

public enum SpawnState { SPAWNING, WAITING, COUNTING };

[System.Serializable]
public class Wave
{
	public string name;
	public Transform enemy;
	public int count;
	public float rate;
    
}

public Wave[] waves;
private int nextWave = 0;
public int NextWave
{
    get { return nextWave + 1; }
}


public Transform[] spawnPoints;

public float timeBetweenWaves = 5f;
public float waveCountdown;
public float WaveCountdown
{
	get { return waveCountdown; }
}

private float searchCountdown = 1f;

private SpawnState state = SpawnState.COUNTING;
public SpawnState State
{
	get { return state; }
}


void Start()
{
    if (spawnPoints.Length == 0)
	{
		Debug.LogError("No spawn points referenced.");
	}

	waveCountdown = timeBetweenWaves;
}

void Update()
{
    //var enemies = GameObject.FindGameObjectsWithTag("Enemy");
    //var enemiesCount = enemies.Length;
    //Debug.Log(enemiesCount);

    if (state == SpawnState.WAITING)
	{
		if (!EnemyIsAlive())
		{
            WaveCompleted(waves[NextWave - 1]);

        }
		else
		{
			return;
		}
	}

	if (waveCountdown <= 0)
	{
		if (state != SpawnState.SPAWNING)
		{
			StartCoroutine( SpawnWave ( waves[nextWave] ) );
		}
	}
	else
	{
		waveCountdown -= Time.deltaTime;
	}
    
}



void WaveCompleted(Wave myWave)
{
    Debug.Log("Wave Completed!");

    waveCompletedPanel.SetActive(true);

    waveCompletedText.text = myWave.name + " Completed";

    StartCoroutine("waitForText");

    state = SpawnState.COUNTING;
    waveCountdown = timeBetweenWaves;

    if (nextWave + 1 > waves.Length - 1)
    {
        nextWave = 0;
        Debug.Log("ALL WAVES COMPLETE! Looping...");

    }
    else
    {
        nextWave++;
    }
}
IEnumerator waitForText()
{
    yield return new WaitForSeconds(2);
    waveCompletedPanel.SetActive(false);
}

private string GetName()
{
    return name;
}

bool EnemyIsAlive()
{
	searchCountdown -= Time.deltaTime;
	if (searchCountdown <= 0f)
	{
		searchCountdown = 1f;
		if (GameObject.FindGameObjectWithTag("Enemy") == null)
		{
			return false;
		}
	}
	return true;
}

IEnumerator SpawnWave(Wave _wave)
{
    //spawningNextWavePanel.SetActive(true);
    //spawningNextWaveText = "Spawning " + _wave.name + " in " + waveCountdown;
    Debug.Log("Spawning Wave: " + _wave.name);
	state = SpawnState.SPAWNING;

	for (int i = 0; i < _wave.count; i++)
	{
		SpawnEnemy(_wave.enemy);
		yield return new WaitForSeconds( 1f/_wave.rate );
	}

	state = SpawnState.WAITING;

	yield break;
}

void SpawnEnemy(Transform _enemy)
{
	Debug.Log("Spawning Enemy: " + _enemy.name);

	Transform _sp = spawnPoints[UnityEngine.Random.Range (0, spawnPoints.Length) ];
	Instantiate(_enemy, _sp.position, _sp.rotation);
}

}

hope you can help since i tried to modify it my self and failed

OnDestroyEventSender.cs

using UnityEngine;

public class OnDestroyEventSender : MonoBehaviour
{
    public event System.Action<OnDestroyEventSender> OnDestroyed;

    private void OnDestroy()
    {
        if ( OnDestroyed != null )
            OnDestroyed( this );
    }
}

Wave.cs

using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public class Wave
{
    /// <summary>
    /// Event invoked when the wave has been cleared
    /// </summary>
    public event System.Action<Wave> OnCleared;

    /// <summary>
    /// Event invoked when an enemy of the wave has been spawned
    /// </summary>
    public event System.Action<Wave, Transform> OnEnemySpawned;

    /// <summary>
    /// Event invoked when an enemy of the wave has been killed
    /// </summary>
    public event System.Action<Wave, Transform> OnEnemyKilled;

    [SerializeField]
    private string name;

    [SerializeField]
    private Transform enemyPrefab;

    [SerializeField, Range(1, 100)]
    private int count;

    [SerializeField, Range(0.01f, 1f)]
    private float rate;

    private List<OnDestroyEventSender> enemies;

    public bool IsCleared
    {
        get { return enemies != null && enemies.Count == 0; }
    }

    public string Name
    {
        get { return name; }
    }

    public int Count
    {
        get { return count; }
    }

    public float Rate
    {
        get { return rate; }
    }

    public int RemainingEnemiesToInstantiate
    {
        get; private set;
    }

    public int RemainingEnemiesAlive
    {
        get { return enemies.Count; }
    }

    /// <summary>
    /// Resets the wave
    /// </summary>
    public void Reset()
    {
        RemainingEnemiesToInstantiate = count;
    }

    /// <summary>
    /// Instantiate an enemy at the given position and rotation
    /// </summary>
    public void InstantiateEnemy( Vector3 position, Quaternion rotation )
    {
        if ( RemainingEnemiesToInstantiate == 0 )
            throw new System.InvalidOperationException( "No remaining enemy to instantiate. Have you called `Reset`?" );

        if ( enemies == null ) enemies = new List<OnDestroyEventSender>( count );

        Transform enemy = Object.Instantiate( enemyPrefab, position, rotation );
        OnDestroyEventSender onDestroyEventDispatcher = enemy.gameObject.AddComponent<OnDestroyEventSender>();
        onDestroyEventDispatcher.OnDestroyed += OnEnemyDestroyed;
        enemies.Add( onDestroyEventDispatcher );
        RemainingEnemiesToInstantiate--;

        if ( OnEnemySpawned != null )
            OnEnemySpawned( this, enemy );
    }

    /// <summary>
    /// Called when an enemy has been destroyed
    /// </summary>
    private void OnEnemyDestroyed( OnDestroyEventSender destroyedEnemy )
    {
        destroyedEnemy.OnDestroyed -= OnEnemyDestroyed;
        enemies.Remove( destroyedEnemy );

        if ( OnEnemyKilled != null )
            OnEnemyKilled( this, destroyedEnemy.transform );

        if ( RemainingEnemiesToInstantiate == 0 && enemies.Count == 0 && OnCleared != null )
            OnCleared( this );
    }
}

WaveSpawner.cs

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class WaveSpawner : MonoBehaviour
{
    [Header("===== Waves ===== ")]
    public GameObject waveCompletedPanel;
    public Text waveCompletedText;
    public Text waveEnemiesCountText;
    public Wave[] waves;
    public float timeBetweenWaves = 5f;
    public bool loopWaves = false;

    private int currentWaveIndex = -1;
    private float waveCountdown;
    private bool spawningWave;

    [Header("===== Spawn points ===== ")]

    public Transform[] spawnPoints;

    /// <summary>
    /// Gets the current wave
    /// </summary>
    private Wave CurrentWave
    {
        get
        {
            return currentWaveIndex >= 0 && currentWaveIndex < waves.Length
                ? waves[currentWaveIndex]
                : null;
        }
    }

    /// <summary>
    /// Indicates whether the last wave has been cleared (if waves don't loop)
    /// </summary>
    private bool ClearedLastWave
    {
        get { return !loopWaves && currentWaveIndex == waves.Length - 1 && CurrentWave.IsCleared; }
    }

    void Start()
    {
        if ( spawnPoints.Length == 0 )
            Debug.LogError( "No spawn points referenced." );

        waveCountdown = timeBetweenWaves;
    }

    void Update()
    {
        waveCountdown -= Time.deltaTime;
        if ( spawningWave )
        {
            if ( waveCountdown <= 0 && CurrentWave != null && CurrentWave.RemainingEnemiesToInstantiate > 0 )
            {
                Transform spawnPoint = spawnPoints[UnityEngine.Random.Range (0, spawnPoints.Length) ];
                CurrentWave.InstantiateEnemy( spawnPoint.position, spawnPoint.rotation );
                waveCountdown = 1 / CurrentWave.Rate;
            }
        }
        else if ( waveCountdown <= 0 )
        {
            StartNextWave();
        }
    }

    /// <summary>
    /// Starts the next wave
    /// </summary>
    private void StartNextWave()
    {
        Debug.Log( "Starting next wave!" );
        spawningWave = true;
        if ( CurrentWave != null )
        {
            CurrentWave.OnCleared      -= OnWaveCleared;
            CurrentWave.OnEnemySpawned -= OnEnemySpawned;
            CurrentWave.OnEnemyKilled  -= OnEnemyKilled;
        }
        if ( !ClearedLastWave )
        {
            currentWaveIndex = ( currentWaveIndex + 1 ) % waves.Length;
            CurrentWave.Reset();
            CurrentWave.OnCleared      += OnWaveCleared;
            CurrentWave.OnEnemySpawned += OnEnemySpawned;
            CurrentWave.OnEnemyKilled  += OnEnemyKilled;
        }
    }

    /// <summary>
    /// Called when the given wave has been cleared (all the instantiated enemies have been destroyed)
    /// </summary>
    private void OnWaveCleared( Wave wave )
    {
        if ( ClearedLastWave )
        {
            Debug.Log( "You have cleared the last wave, congratulations!" );
        }
        else
        {
            Debug.Log( "Wave Completed! Next one starting in " + timeBetweenWaves + " seconds" );
        }

        waveCountdown = timeBetweenWaves;
        spawningWave = false;

        waveCompletedPanel.SetActive( true );
        waveCompletedText.text = wave.Name + " Completed";
        StartCoroutine( HideWavePanel( 2 ) );
    }

    private void OnEnemySpawned( Wave wave, Transform spawnedEnemy )
    {
        waveEnemiesCountText.text = string.Format( "{0} / {1}", wave.RemainingEnemiesAlive, wave.Count );
    }

    private void OnEnemyKilled( Wave wave, Transform killedEnemy )
    {
        waveEnemiesCountText.text = string.Format( "{0} / {1}", wave.RemainingEnemiesAlive, wave.Count );
    }

    /// <summary>
    /// Hides the wave panel after the given delay
    /// </summary>
    private IEnumerator HideWavePanel( float delay )
    {
        yield return new WaitForSeconds( delay );
        waveCompletedPanel.SetActive( false );
    }
}


Original answer

// Enemy script

public event System.Action OnDestroyed;

private void OnDestroy()
{
    if( OnDestroyed != null )
         OnDestroy();
}

// Spawner script

// Top of the file
using System.Collections.Generic;

// ....

private List<Enemy> enemies = new List<Enemy>();

private void SpawnWave()
{
    // ...
    Enemy enemy = Instantiate( enemyPrefab ).GetComponent<Enemy>() ;
    enemies.Add( enemy ) ;
    enemy.OnDestroyed += () => OnEnemyDestroyed( enemy );
}

private void OnEnemyDestroyed( Enemy enemy )
{
    enemies.Remove( enemy ) ;
    Debug.Log( "An enemy has been destroyed. " + enemies.Count + " left");
}

you need to update your list every time you kill an enemy. just run the forEach loop after each time you kill the enemy.

How about just having a public static int called “enemyCount” or something? You could put that on your Spawner script or a game manager script. Then when you instantiate enemies just up that variable (like “Spawner.enemyCount++” for example), and in your onDestroy function you add the line “Spawner.enemyCount–” This way each enemy is responsible for being accounted for themselves.
This saves you the cpu overhead over finding gameobjects by tag or having to do forEach loops or anything of the sort. Also having a single integer is more memory friendly than tracking a dynamic list.