0
votes

I have 2 buttons in my options menu, "return to main menu" and "mute music" Each of these buttons has a script attached to it, and calls the OnPress() method of the script when it is pressed. I also have a main Level object/script that handles all the scene loading and stuff. So the script of the main menu button does FindObjectOfType() in its Start and then calls level.LoadStartScene() in its OnPress(). The script for the mute button does the same thing but calls level.ToggleMuteMusic(). So this worked perfectly before, but then I made the level a singleton with the following code:

public void Awake() {
    InitializeSingleton();
}

private void InitializeSingleton() {
    if (FindObjectsOfType(GetType()).Length > 1) {
        Destroy(gameObject);
    } else {
        DontDestroyOnLoad(gameObject);
    }
}

So now the main menu button works perfectly, but the mute button gives an error; I think this is because in Start() it finds the old level object, and then the one with DontDestroyOnLoad comes in and deletes the old one, but then why does the main menu button work???

Mute Button Code:

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

public class MuteButton : MonoBehaviour {

[SerializeField] string mutedText = "Unmute Music";
[SerializeField] string unmutedText = "Mute Music";

private Level level;
private TextMeshProUGUI myText;

public void Start() {
    level = FindObjectOfType<Level>();
    myText = GetComponent<TextMeshProUGUI>();
}

public void OnPress() {
    if (level == null) {
        Debug.Log("log 1");
    }
    level.ToggleMuteMusic();
    bool muteMusic = level.GetMuteMusic();
    if (muteMusic == true) {
        myText.SetText(mutedText);
    } else if (muteMusic == false) {
        myText.SetText(unmutedText);
    }
}

}

Menu Button Code:

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

public class MenuButton : MonoBehaviour {

Level level;

public void Start() {
    level = FindObjectOfType<Level>();
}

public void OnPress() {
    level.LoadStartScene();
}
}

Full 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. UnityEngine.GameObject.GetComponent[T] () (at C:/buildslave/unity/build/Runtime/Export/Scripting/GameObject.bindings.cs:28) Level.ToggleMuteMusic () (at Assets/Scripts/Level.cs:74) MuteButton.OnPress () (at Assets/Scripts/MuteButton.cs:23)

Thanks for your time :)

1
This implementation is not a singleton pattern. Singletons ensure that there is 1 and only 1 instance of a class. You've simply told the system to not destroy this object when a new scene is loaded. There is nothing in your code to stop the creation of another instance. So, what is most likely happening is that a second instance of the class is being created and it destroys itself in InitializeSingleton by (FindObjectsOfType(GetType()).Length > 1) being 'true'. Why do you need a singleton?Topher
As an additional note, a good implementation of a singleton pattern can be found here: wiki.unity3d.com/index.php/Singleton . I've used this and it works well.Topher

1 Answers

0
votes

It can be a bit difficult to determine the exact order that things are executing to cause this issue, but the fact that you're losing a reference in Start() that was handled in Awake() is odd. I've had issues using FindObjectOfType<> in Start() in the past, so maybe changing up how you're handling the singleton would be best.

What I'd recommend is making your Level static so you can reference it easier, since you're already implementing a form of Singleton.

Here's an example of how you could rewrite the top of your Level.cs file:

public static Level instance;

private void Awake()
{
    if (instance != null && instance != this)
    {
        Destroy(this.gameObject);
        return;
    }
    else
    {
        instance = this;
    }
    DontDestroyOnLoad(this.gameObject);
}

This causes it to create the Level class once, then destroy all subsequent versions of it (like when you reload the scene it's placed in).

Now, to reference it in your other scripts, you never have to use FindObjectOfType<Level>() again! You can statically reference Level.instance, like so:

//New way to call ToggleMuteMusic()
Level.instance.ToggleMuteMusic();

Hopefully this helps!