Problem with Loading Scenes

I’m having an issue with figuring out the code for how to load the next scene once all the enemies in the level are dead. I tried to use the same techniques taught in the Brick Breaker section, but I just can’t seem to get my head around it.

I’m thinking I need a way to count the enemies, and have a decrementing variable when each is destroyed, but I’m not sure how to write it or where exactly to put it. Any help is appreciated!

Right now I can only get to the “Game Over” screen when the player dies, but nothing happens once all the enemies have spawned/been destroyed in Level 1.

I would suggest some kind of abstract “Game Status”, “Level Data”, “Level Manager”, script that keeps track of things important to the level itself, such as what kind of tiles to use, what kind of enemies to load, what the lose and victory conditions are, etc.

This would be a good place to put a static integer for your enemiesRemaining count or your enemiesDestroyed count, when it reaches a certain number, you can trigger the end of the level which can do things like play an animation, start a sound effect, show some UI, etc., in addition to loading the next scene.

I have an Enemy.cs, a Level.cs, and an EnemySpawner.cs. If different levels will have different numbers of enemies, which script would it be best to have the static integer in? I’m trying to use tags to keep track of the enemies, but I’m having a hard time getting it working since the enemies spawn in waves and aren’t already on the screen (like in Brick Breaker).

I’ll attach the scripts below (sorry if I’m using the wrong format for this):

Enemy.cs

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

public class Enemy : MonoBehaviour
{
    [Header("Enemy Stats")]
    [SerializeField] float enemyHealth = 100;
    float shotCounter;
    [SerializeField] float minTimeBetweenShots = 0.2f;
    [SerializeField] float maxTimeBetweenShots = 3f;
    [SerializeField] int scoreValue = 150;

    [Header("Laser")]
    [SerializeField] GameObject projectile;
    [SerializeField] float projectileSpeed = 10f;

    [Header("VFX")]
    [SerializeField] GameObject deathVFX;
    [SerializeField] float durationOfExplosion = 1f;

    [Header ("SFX")]
    [SerializeField] AudioClip enemyDeathSFX;
    [SerializeField] [Range(0, 1)] float enemyDeathSFXVolume = 0.5f;
    [SerializeField] AudioClip enemyLaserSound;
    [SerializeField] [Range(0, 1)] float enemyLaserVolume = .02f;

    // Start is called before the first frame update
    void Start()
    {
        shotCounter = Random.Range(minTimeBetweenShots, maxTimeBetweenShots);
    }

    // Update is called once per frame
    void Update()
    {
        CountDownAndShoot();
    }

    private void CountDownAndShoot()
    {
        shotCounter -= Time.deltaTime;
        if(shotCounter <= 0f)
        {
            Fire();
            shotCounter = Random.Range(minTimeBetweenShots, maxTimeBetweenShots);
        }
    }

    private void Fire()
    {
        GameObject enemyLaser = Instantiate(projectile, transform.position, Quaternion.identity) as GameObject;
        enemyLaser.GetComponent<Rigidbody2D>().velocity = new Vector2(0, -projectileSpeed);
        AudioSource.PlayClipAtPoint(enemyLaserSound, Camera.main.transform.position, enemyLaserVolume);
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        DamageDealer damageDealer = other.gameObject.GetComponent<DamageDealer>();
        if (!damageDealer) { return; }
        ProcessHit(damageDealer);
    }

    private void ProcessHit(DamageDealer damageDealer)
    {
        enemyHealth -= damageDealer.GetDamage();
        damageDealer.Hit();
        if (enemyHealth <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        FindObjectOfType<GameSession>().AddToScore(scoreValue);
        Destroy(gameObject);
        GameObject explosion = Instantiate(deathVFX, transform.position, transform.rotation);
        Destroy(explosion, durationOfExplosion);
        AudioSource.PlayClipAtPoint(enemyDeathSFX, Camera.main.transform.position, enemyDeathSFXVolume);
    }
}

Level.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Level : MonoBehaviour
{
    [SerializeField] float delayInSeconds = 2f;

    public void LoadStartMenu()
    {
        //Load via index
        SceneManager.LoadScene(0);
    }

    public void LoadGame()
    {
        //Load via string reference
        SceneManager.LoadScene(1);
        FindObjectOfType<GameSession>().ResetGame();
    }

    public void LoadGameOver()
    {
        StartCoroutine(LoadDelay());
    }

    IEnumerator LoadDelay()
    {
        yield return new WaitForSeconds(delayInSeconds);
        SceneManager.LoadScene("Game Over");
    }

    public void QuitGame()
    {
        Application.Quit();
    }
}

EnemySpawner.cs

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

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] List<WaveConfig> waveConfigs;
    [SerializeField] int startingWave = 0;
    [SerializeField] bool looping = false;


    // Start is called before the first frame update
    IEnumerator Start()
    {
        do
        {
            yield return StartCoroutine(SpawnAllWaves());
        }
        while (looping);
    }
    private IEnumerator SpawnAllWaves()
    {
        for(int waveIndex = startingWave; waveIndex < waveConfigs.Count; waveIndex++)
        {
            var currentWave = waveConfigs[waveIndex];
            yield return StartCoroutine(SpawnAllEnemiesInWave(currentWave));
        }
    }

    private IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig)
    {
        for(int enemyCount = 0; enemyCount < waveConfig.GetNumberOfEnemies(); enemyCount++)
        {
            var newEnemy = Instantiate(waveConfig.GetEnemyPrefab(), waveConfig.GetWaypoints()[0].transform.position, Quaternion.identity);
            newEnemy.GetComponent<EnemyPathing>().SetWaveConfig(waveConfig);
            yield return new WaitForSeconds(waveConfig.GetTimeBetweenSpawns());
        }
        
    }

}

Hi rjacks6770,

Welcome to our community, and good job on challenging yourself. :slight_smile:

Have you already tried to add Debug.Logs to your code to see what is going on during runtime? For example, where is the number of spawned enemies in your scene?

Maybe a different approach would be better than the one we used in Block Breaker. As you wrote, unlike in the Block Breaker game, not all enemies are there at the beginning of the game. However, since the EnemySpawner spawns enemies, it knows exactly how many enemies there were spawned if you add them to a List. And before you spawn the next wave, you check the content of the list. Only if all elements are null, you spawn the next wave. That’s certainly just one way how to solve the problem.

Hi Nina!

I think I’ve somewhat figured out how to make the list. I put it in my EnemySpawner.cs since that’s where they are instantiated. The new EnemySpawner.cs looks like this:

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

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] List<WaveConfig> waveConfigs;
    [SerializeField] int startingWave = 0;
    [SerializeField] bool looping = false;

    [SerializeField] public List<GameObject> enemiesList = new List<GameObject>();

    // Start is called before the first frame update
    IEnumerator Start()
    {
        do
        {
            yield return StartCoroutine(SpawnAllWaves());
        }
        while (looping);
    }
    private IEnumerator SpawnAllWaves()
    {
        for(int waveIndex = startingWave; waveIndex < waveConfigs.Count; waveIndex++)
        {
            var currentWave = waveConfigs[waveIndex];
            yield return StartCoroutine(SpawnAllEnemiesInWave(currentWave));
        } 
    }

    public IEnumerator SpawnAllEnemiesInWave(WaveConfig waveConfig)
    {
        for(int enemyCount = 0; enemyCount < waveConfig.GetNumberOfEnemies(); enemyCount++)
        {
            var newEnemy = Instantiate(waveConfig.GetEnemyPrefab(), waveConfig.GetWaypoints()[0].transform.position, Quaternion.identity);
            enemiesList.Add(newEnemy);
            newEnemy.GetComponent<EnemyPathing>().SetWaveConfig(waveConfig);
            yield return new WaitForSeconds(waveConfig.GetTimeBetweenSpawns());
        }
    }
}

I also added an EnemyCounter.cs script to access the list and print the items in the console:

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

public class EnemyCounter : MonoBehaviour
{
    EnemySpawner enemies;

    // Start is called before the first frame update
    IEnumerator Start()
    {
        enemies = GetComponent<EnemySpawner>();
        yield return new WaitForEndOfFrame();
        foreach(GameObject g in enemies.enemiesList)
        {
            Debug.Log(g);
        }
    }

I’m having two issues I can’t figure out now:

  1. The enemies are adding to the list in EnemySpawner.cs, but when they are destroyed they show as “Missing Game Object”

  2. For each enemy spawned, the console shows:
    “NullReferenceException: Object reference not set to an instance of an object
    EnemyCounter+d__1.MoveNext () (at Assets/Scripts/EnemyCounter.cs:14)
    UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <127e81e1cb3441cc97d26b1910daae77>:0)
    UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)”

I was planning to remove the items from the list in the EnemyCounter.cs or the Enemy.cs (with Die()), but I didn’t want to continue until this issue is resolved.

Check for null before calling any methods on the enemies in the List. If the enemy is destroyed, try to figure out what value the “slot” in the List got. It might either be null or GameObject. The latter would be a bit of a problem, and in that case, you’ll indeed have to explicitely remove the item from the list.