Got stuck when changing levels

Hi there,

I’m trying to continue the Laser Defender project from 2D course and I’m getting in some point that I can’t solve by myself. I’m getting an error related to a button GameObject that seems to be destroyed in some moment at start one level (but I don’t know why or where), and when a StartCoroutine try to trigger the level change it returns a null error.

I’m sharing code involved (may it’s more than what I’m pasting here and the Unity console error, in order to locate problem.

I’ve tried on some ways to modify this unexpected behavior, but I can’t go ahead. Game stays stuck in change level creating and destroying infinitely the new scene, or simply waiting for a button that never arrives.

I’ve checked that all components are linked in inspector, all prefabs, scripts and other stuff inside unity seems to be ok but nothing runs as expected.

Thanks for your help!

PS: Methods and variables names are written in my native language, Catalan, in order to help me understand and follow better what’s happening inside the game, but it’s easy to know what every function does when inspect inside.

Unity error
MissingReferenceException: The object of type ‘GameObject’ has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
Botons.MostrarBotoIniciar () (at Assets/Scripts/Botons.cs:28)
Botons.MostrarBotons () (at Assets/Scripts/Botons.cs:24)
CarregadorEscenes.CarregarProperaEscena () (at Assets/Scripts/CarregadorEscenes.cs:46)
SessioJoc+d__13.MoveNext () (at Assets/Scripts/SessioJoc.cs:56)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at /home/builduser/buildslave/unity/build/Runtime/Export/Scripting/Coroutines.cs:17)

CarregadorEscenes.cs (SceneLoader)

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

public class CarregadorEscenes : MonoBehaviour
{

    [SerializeField] float segonsAbansCanviEscena = 3f;
    [SerializeField] int properaEscena;
    SessioJoc sessioEntrant;
    Botons botoIniciar;

    /* private void Awake() {
        Singleton();
    }

    private void Singleton() {
        int comptadorSessions = FindObjectsOfType(GetType()).Length;
        if (comptadorSessions > 1) {
            gameObject.SetActive(false);
            Destroy(gameObject);
        } else {
            DontDestroyOnLoad(gameObject);
        }
    } */

    public void Start() {
        sessioEntrant = FindObjectOfType<SessioJoc>();
        botoIniciar = FindObjectOfType<Botons>();
    }

    public void CarregarEscenaInicial() {
        sessioEntrant.ResetPuntuacio();
        botoIniciar.MostrarBotons();
        SceneManager.LoadScene(0);
    }

    public void CarregarPantalla1() {
        SceneManager.LoadScene(1);
    }


    public void CarregarProperaEscena() {
        sessioEntrant.ResetEnemics();
        botoIniciar.MostrarBotons();
        SceneManager.LoadScene(0);
    }
    
    public void FinalDePartida() {
        StartCoroutine(CorutinaFinalDePartida());
    }

    private IEnumerator CorutinaFinalDePartida() {
        yield return new WaitForSeconds(segonsAbansCanviEscena);
        CarregarEscenaInicial();
    }

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

Botons.cs (Buttons)

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

public class Botons : MonoBehaviour
{
    Text text;
    GameObject botoIniciar;
    // GameObject botoSortir;

    private void Start() {
        botoIniciar = GameObject.Find("BotoIniciar");
        // botoSortir = GameObject.Find("BotoSortir");
        text = botoIniciar.GetComponentInChildren<Text>();
    }

    public void AmagarBotons() {
        AmagarBotoIniciar();
        AmagarBotoSortir();
    }

    public void MostrarBotons() {
        MostrarBotoIniciar();
    }

    private void MostrarBotoIniciar() {
        botoIniciar.SetActive(true);
    }

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

    public void AmagarBotoSortir() {
        // botoSortir.SetActive(false);
    }

    public void AmagarBotoIniciar() {
        StartCoroutine(CorutinaAmagarBotoIniciar());
    }

    private IEnumerator CorutinaAmagarBotoIniciar() {
        // botoIniciar.GetComponent<Button>().interactable = false;
        yield return new WaitForSeconds(1f);
        for (int i = 3; i > 0; i--) {
            text.text = (i + "!").ToString();
            yield return new WaitForSeconds(0.5f);
        }
        text.text = "Go!";
        yield return new WaitForSeconds(1.5f);
        botoIniciar.SetActive(false);
    }
}

And finally SessioJoc.cs (GameSession)

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

public class SessioJoc : MonoBehaviour
{
    
    [SerializeField] int puntuacioJugador = 0;
    [SerializeField] int puntuacioActual = 0;
    [SerializeField] Text uiPuntuacio;

    [SerializeField] bool enemicsIniciats = false;
    [SerializeField] int nombreEnemics;

    [SerializeField] float segonsAbansCanviEscena = 3f;
    [SerializeField] GameObject nouBotoPrefab;

    // components capturats
    Enemic enemics;
    CarregadorEscenes finalEscena;

    private void Awake() {
        Singleton();
    }

    private void Singleton() {
        int comptadorSessions = FindObjectsOfType<SessioJoc>().Length;
        if (comptadorSessions > 1) {
            gameObject.SetActive(false);
            Destroy(gameObject);
        } else {
            DontDestroyOnLoad(gameObject);
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        finalEscena = FindObjectOfType<CarregadorEscenes>();
        if (!finalEscena) { return; }
        uiPuntuacio.text = puntuacioActual.ToString();
    }

    void Update() {
        if (enemicsIniciats) {
            nombreEnemics = FindObjectsOfType<Enemic>().Length;
            if (nombreEnemics <= 0) {
                StartCoroutine(SollicitarProperaEscena());
            }
        }
    }

private IEnumerator SollicitarProperaEscena() {
    yield return new WaitForSeconds(segonsAbansCanviEscena);
    finalEscena.CarregarProperaEscena();
}
    public void IniciarEnemics() {
        enemicsIniciats = true;
    }

    public void Puntuacio(int sumaPunts) {
        puntuacioActual += sumaPunts;
        uiPuntuacio.text = puntuacioActual.ToString();
    }

    public void ResetEnemics() {
        enemicsIniciats = false;
    }

    public void ResetPuntuacio() {
        puntuacioActual = 0;
        Destroy(gameObject);
    }

    public int ObtenirPuntuacio() {
        return puntuacioJugador;
    }
}

Other piece of code that manipulates buttons is inside the enemy spawner:

GeneradorEnemics.cs (EnemySpawner, only pieces that I think are directly involved on this)

public class GeneradorEnemics : MonoBehaviour
{

    [SerializeField] List<ConfigOnada> configsOnades;
    [SerializeField] int onadaInici = 0;
    [SerializeField] bool infinit = false;

    Botons botoIniciar;
    SessioJoc iniciarEnemics;

    // Start is called before the first frame update
    /* IEnumerator Start()
    {
        do {
            yield return StartCoroutine(GenerarTotesLesOnades());    
        } while (infinit);
    } */

    private void Start() {
        botoIniciar = FindObjectOfType<Botons>();
        iniciarEnemics = FindObjectOfType<SessioJoc>();
        IniciarPantalla();
    }

    public void IniciarPantalla() {
        botoIniciar.AmagarBotons();
        StartCoroutine(GenerarTotesLesOnades());
    }

I would pay special attention to any of the objects that are destroyed. I was looking through your SessioJoc class and noticed the Coroutine starts in Update, I think once enemicsIniciats is set to true it calls the Coroutine over and over every frame, is there something else making sure it only gets called once?

1 Like

Hi!

I think these no affect the global situation on buttons trouble. “enemicsIniciats” boolean starts as false to prevent game enter in a endless loop. Then, when the enemySpawner starts to send enemies, after instantiate an enemy also triggers a method (IniciarEnemics()) that changes enemicsIniciats = true. After that, there is at least 1 enemy on screen, so the nested if in Update() don’t trigger the coroutine. Only when player has ended to kill all enemies or the last enemy is destroyed in its last waypoint in its route the condition of the nested if is true (nombreEnemics <= 0), then it triggers the coroutine.

I’ve added a new enemicsIniciats = false, so once coroutine is triggered the if statement of update() is not true and not runs continuously the coroutine, but problem with buttons is there.

    void Update() {
        if (enemicsIniciats) {
            nombreEnemics = FindObjectsOfType<Enemic>().Length;
            if (nombreEnemics <= 0) {
                StartCoroutine(SollicitarProperaEscena());
                enemicsIniciats = false; // once number of enemies is <= 0 and coroutine is triggered, stop finding objects of type Enemy
            }
        }
    }

Anyway, I think I will try to figure out other solution without using buttons.

Thanks for your help!

Wish I could of been more help, I’m sure you’ll find a solution that works good! :+1:

1 Like

Hey! Your idea was so useful and helped me to improve code! I’m thinking on how to avoid this problem, and for now I removed all buttons between screens and everything works as expected (change scenes, singleton conducting music and game stats, etc). I will post here any news on this. So, tranks again!

1 Like

Hey there, I’ve solved buttons problem and I want to share with you a preview of the core game, you can access it here!

They are simple and with no many levels, power-ups or something else, but it’s consistent itself and no crashes xD

Thanks for your feedback if you want to share it!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms