- Home /
Setting a reference to a Singleton in the editor not possible?
Hello all,
I had a problem with a strange behaviour, just found the reason and want to understand what is going on there.
What I observed in debug mode: My Restard method sets gameHasEnded = false and loads the scene again, but after the reload the value of gameHasEnded was still true.
My setup: A restart button calls the Restart method of my GameManager. This GameManageris a singleton and set to DontDestroyOnLoad. If I call this restart method by the onClick() event of the button (see following screenshot), it does not use the singleton instance of my GameManager.
I found it out with the following debug logs:
Generating a UUID when the GameManager gets created.
private void Awake()
{
uuid = Guid.NewGuid();
someCode();
}
public void Restart()
{
Debug.Log("----------------Restart--------------UUID: " + uuid);
Debug.Log("----------------Restart--------------UUID: " + GameManager.Instance.uuid);
GameManager.Instance.gameHasEnded = false;
gameHasEnded = false;
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
OUTPUT:
----------------Restart--------------UUID: 00000000-0000-0000-0000-000000000000 ----------------Restart--------------UUID: 87f2267c-65cf-497a-8d5b-d155c711209b
So, when Unity calls the Restart() via the onClick it seems to get somehow a "broken" instance or at least not my singleton. The line "GameManager.Instance.gameHasEnded = false;" solved my problem that the value was not changed in my singelton, but this seems to be an odd workaround.
Can you explain me why it behaves like observed? Is it in general not allow to set a reference to a Singleton in the editor?
Answer by AndBje · Apr 10, 2021 at 10:30 PM
There should of course only be one instance of a singleton, hence the name. The problem is that we usually want to add it to the scene's hierarchy and thus a copy of the MonoBehaviour will be created each time the scene is loaded. This can lead to some confusion and the general solution is to check in the Awake() method if there already is a singleton loaded. If there is, we should delete ourselves by calling Destroy().
public class GameManager : MonoBehaviour
{
private static GameManager _instance;
private void Awake()
{
if (_instance != null && _instance != this)
{
// There is already a singleton loaded. Destroy ourself!
Destroy(this.gameObject);
return;
}
// We were first, let this object be the singleton! :)
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
...
If we follow the principle above we shouldn't add a reference to a public function of the instance, for example from a button. First, it will work as normal. However, when the scene is reloaded, Unity will then again create a new instance of the GameManager and connect it to the button. The second instance will shortly after discover that there already exists an instance and will therefore destroy itself. Nothing will then happen when the user clicks on the button.
If we don't destroy the second copy, as in your case (I assume), the button will still work even after a reload, but it will be connected to the second instance and you have two singletons (and then three, and then four... and so on).
I would recommend you always delete any instance after the first one. So, how should we connect the button to the first instance? Well, we don't. We connect it to a static version.
public static void Restart()
{
_instance.gameHasEnded = false;
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
There are more advanced ways of doing singletons in Unity (for example https://blog.mzikmund.com/2019/01/a-modern-singleton-in-unity/) but I didn't want to make the example too complicated.
Thanks @andbje for your reply.
Yes, I indeed use a singleton which destroys any additional (second) instances of this GameManager class in a similar way you showed it.
I assumed that if I set a reference to such a singelton in the editor, that this would also point to my target singleton instance. But I see my mistake in thinking.
However, when the scene is reloaded, Unity will then again create a new instance of the GameManager and connect it to the button. The second instance will shortly after discover that there already exists an instance and will therefore destroy itself.
Yes, okay. The singleton works as it should here. The problem is, that Unity does not use the getter of the singleton class which would return the valid/target instance, right? It creates a new one, which is destroyed then.
So, how should we connect the button to the first instance? Well, we don't. We connect it to a static version.
Ahh, I see. So, in this way I should always get to my single target instance!
Just to avoid dependent problems in the future: Does it have any (negative) side effect to use a static method here?
Yes, you are right. Every time Unity loads your scene it will create all game objects in the hierarchy and connect any events to the newly created objects. It does not understand that one of the game objects already exists (the GameManager). The unnecessary copy will itself, in the Awake() method, notice that it was not the first singleton object and destroy itself.
You could of course continue to use the second copy as long as it always uses the static reference _instance (Instance in your code) - but having several copies of a singleton can never be a good idea. So the only solution if you want to call a singleton from Editor code is to call static functions. I can't see that it should create any problems - rather the opposite.
Then we have the never-ending discussion on whether singletons are good or not... but let's not go there... - everything has its place and I don't $$anonymous$$d singletons as long as they are used with care. It would of course be nice if Unity could discover the singleton and reconnect the already existing instance when the scene is loaded.
Almost forgot to thank you, so: Thanks for your help! :-)