Reputation: 1402
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
Reputation: 15951
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.
instance
as null and falls through to the else
statement and sets the instance to itselfinstance
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
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
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
Reputation: 3108
This is happening because of Script Execution Order
.
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