Niclas
Niclas

Reputation: 1402

Shorthand for singleton instance, giving NullException in some classes

I'm working in Unity and have a singleton called GameManager, which contains an object called GameSettings, which I want to reach from other classes:

public class GameManager : MonoBehaviour {
    public static GameManager instance;
    public GameSettings gameSettings; 

    void Awake() {
        if(instance != null)
            GameObject.Destroy(instance);
        else
            instance = this;
        DontDestroyOnLoad(this);
    }
}

I can reach it from another class using GameManager.instance.gameSettings.offlineMode for example. But I wanted an easier way of writing this, to make the code clearer, so in the other class I did:

public class Health : Photon.MonoBehaviour {
    private GameManager gm;

    void Awake(){
        // Get components
        anim = transform.GetChild(0).GetComponent<Animator>();
        unitController = GetComponent<UnitController>();
        gm = GameManager.instance;
    }
}

Then I can use gm.gameSettings.offlineMode. This has worked well in most classes, but in one class it doesn't work, and gives me NullReferenceException: Object reference not set to an instance of an object.

Why is this happening, and only in one class? What should I check for, and is it a bad idea to "shorthand" a singleton instance like this?

Upvotes: 0

Views: 1193

Answers (4)

Script execution order was at fault here, but I see another issue with your code:

    if(instance != null)
        GameObject.Destroy(instance);
    else
        instance = this;

Lets say you get two instances at some point.

  • The first copy sees instance as null and falls through to the else statement and sets the instance to itself
  • The second copy sees instance as being set to the first copy and... destroys it (then exits, leaving instance equal to null!).

Note that "second copy" here could be the first copy executing Awake() a second time for any reason.

Upvotes: 1

Falcon
Falcon

Reputation: 121

Probably it is about execution order. Some script trying access object before this object is actually created. Notice that you don't have control on execution order of Awake functions unless you won't set it up in project settings. There are many ways to solve it. You can assign your reference in Start function which is always called after Awake so you will be sure that object you want get access to is already created. Another way is to change execution order in project settings (worst solution) or use lazy loading. Lazy loading is very convinient when it comes to singletons, but it is not the best practise. I would rather recommend you to avoid singletons and initialize all services in one central point. Passing dependencies through constructor is much more clear than singleton pattern. Also you can use some dependency injection framework like zenject.

Upvotes: 1

CHE6yp
CHE6yp

Reputation: 86

My guess is that your Health class executes before GameManager class. You can try to play with execution order, or just move the line of code where you assign the instance to the Start() method.

Upvotes: 1

Fredrik Widerberg
Fredrik Widerberg

Reputation: 3108

This is happening because of Script Execution Order.

https://docs.unity3d.com/Manual/class-MonoManager.html

Go into

Edit -> Project Settings -> Script Execution Order

Press the +, select your GameManager and drag it up "before" Default Time, or change the value to something below 0. Now, your GameManagers Awake() will execute before the other scripts Awake()

Another way is to always assign the singleton in Awake(), but when you grab the reference in other classes, you do that in Start()

Upvotes: 3

Related Questions