I am facing issues related to scripts execution order and this point is out of my mind though I am an experienced, Unity developer. So I expect a little bit of explanation regarding this.
This is my MenuContoller script code:
public class MainMenuController : MonoBehaviour
{
[SerializeField] Text bestScoreText;
[SerializeField] Toggle soundToggle;
private void OnEnable()
{
Init();
}
private void Init()
{
if (GameManager.Instance == null)
Debug.Log("null game manager");
GameManager.Instance.PlayerLives = 0;
bestScoreText.text = DataStorage.RetrieveBestScore().ToString("D5");
SoundManager.Instance.IsSoundEnabled = DataStorage.RetrieveSoundStatus() == GameConstants.STAT_ON ? true : false;
soundToggle.isOn = !SoundManager.Instance.IsSoundEnabled;
}
}
This is my GameManager script code:
public class GameManager : MonoBehaviour
{
private static GameManager instance;
//
private int levelIndex;
private int gameScore;
private int playerLives;
void Awake()
{
instance = this;
}
public static GameManager Instance
{
get
{
return instance;
}
}
}
I am getting NullReferenceException during execution:
Now I canât able to understand - how the OnEnable method gets executed before other scriptâs Awake method?
Because of this reason, I am getting a null reference exception. As per my understanding, all scripts Awake methods get executed first then after OnEanble call for all scripts of the project.
Please explain to me this point so my side confusion gets solved.
Awake is only called before OnEnable within the context of a single script. If you have multiple scripts with these methods, both Awake and OnEnable can get called for one script, before both get called for another one.
Start is always called after Awake and OnEnable for all scripts, though (source).
So as per you saying - all scripts are execute their life cycle methods in order without considering execution order of the script - they just follow their own path.
Iâve talked about this a few times on the forumâŚ
My first ever post about it way back in 2014:
And my most recent in 2018:
Iâll basically repeat myself here though to save you having to navigate there:
Awake on all scripts is called before Start is called on all scripts. As is documented:
OnEnable is independent of that timing. Here we can get an idea of OnEnableâs timing:
So in regards to when a scene is first loaded. OnEnable is called when the object is first enabled. This happens when the instance of the MonoBehaviour is created, and just after Awake. So as a result your singleton isnât necessarily initialized (has OnEnable called) when MainMenuController has its OnEnable called.
You should either use âStartâ.
Annoying since if you need OnEnable you have to write a bunch of annoying logic.
OR
Use script execution order.
Annoying because setting up execution order is cumbersome. And isnât easily readable in code since itâs all through the editor.
OR
I personally used a method where I created my own script called SPComponent. And on it I have a method called âOnStartOrEnabledâ:
Annoying part is that it requires setting this all up⌠main reason I only did set this all up is because I personally run into it all the time.
With this I call OnStartOrEnable either in âStartâ or in âOnEnableâ if and only if Start was already called. Thusly giving you a OnEnable like method that behaves more like Start.
âŚ
NOTE - you may notice if you go to those links that in my 2018 post my source code link doesnât have OnStartOrEnable. Thatâs because in my 3.0 library Iâve moved to a mixin system (Iâm playing around with mixin ideas to save putting logic on SPComponent in cases its not needed). So now my OnStartOrEnable is implemented with this mixin:
You could change the script execution order in the settings, have the GameManager script run before everything else, and use Awake in your first script, that way it is guaranteed that the GameManagerâs Awake has already run.
The object may or may not exist yet, and even if it did, itâs not enabled yet.
âŚ
Antistoneâs suggestion would/could work.
With one exception⌠the state of the singleton could get overwritten when the singleton is initialized. Depending on timing it may not have deserialized yet, in which case when OP sets âplayerLivesâ to 0, that might get reset to whatever the serialized value is. That value may happen to be 0 as well, making this bug unnoticeable at first until you maybe change it in some scene randomly, since that 0 is just incidental.
Note serialization/deserialization doesnât necessarily happen on the main thread either. So this value could be changed at any moment before its Awake is called. And since the timing of this isnât documented, it could change in the future, or on the target build. Which you could run tests for to determine the behaviour⌠but I personally donât like relying on undocumented behaviour. Itâs bit me in the butt many times before.
âŚ
Personally if I have a singleton. I create them in the moment rather than allowing them to exist in the scene. Essentially I do like SisusCo⌠but instead of FindObjectOfType, if itâs null I create an instance. Thusly guaranteeing an initialized instance with its Awake/OnEnable called immediately (since CreateInstance calls these).
The only exception to this is if my singleton is scene specific rather than living the entire project. Which well⌠I generally avoid this (the exceptions being Unityâs built in singleton like input stuff⌠which grate on my nerves).
As a general rule of thumb, I use Awake() for all internal initialisation, and Start() for anything that depends on other objects in the Scene.
For anyone stumbling upon this in the future, one other thing to consider is that the script execution order only applies after your object is active and enabled. So if youâve got inactive stuff in your scene, or if you create stuff via code inside your scene, that will impact when their initialisation methods are called.
Does that need to be a MonoBehaviour? Nothing in there references the Unity scene, so you could just have it as a plain old C# class. You even can reference the Unity scene, so long as you donât need to use the Inspector to configure anything and remember to null out the references when you change scenes (otherwise the referenced objects canât be cleaned up).
OnEnable, which was used in the original code to assign the instance, will also only run for created and enabled objects, so I donât see any issue here.
FindObjectOfType is more reliable than assigning the instance in OnEnable, because it works even if other classes try to get the instance before the OnEnable function has fired. So it can always be called from the Awake, OnEnable or Start functions of any other components, and it will just work.
Of course creating the instance automatically if it doesnât exist yet is also a perfectly valid option. Both methods have their own pros and cons.
For example, if you auto-create the instance, thereâs a risk that when unloading a scene, exiting play mode or quitting the application, you will accidentally end up recreating the singleton again and again, as multiple systems refer to the manager, and the program keeps trying to unload all objects in the scene. If you use FindObjectOfType you are somewhat more likely to notice such an issue when you run into NullReferenceExceptions, and can then implement a workaround.
Another risk when auto-creating your singleton is that you might not even notice if your singleton gets unintentionally destroyed by something like a scene loading process, and you lose state, as a new singleton simply pops into existence to take itâs place without you even noticing. For this reason using FindObjectOfType might be the better option when using singletons that are supposed to persist through scene loads and are not stateless.
I agree that this is a good pretty general good rule of thumb to follow, that can reduce the number of NullReferenceException you will encounter during development. However I think that ideally you would also always make sure that internal initialization takes place even if external systems call any public methods or properties on your components before that components Awake method has fired. This is especially important in my opinion when we are talking about a manager script commonly utilized by many other classes, because it only takes one other script making the mistake of referring to the manager at the âwrongâ / unexpected time for things to break.
In cases where thatâs relevant, sure⌠but Iâm struggling to think of any cases where thatâs been a concern for me. I donât think itâs come up more than maybe half a dozen times in the decade Iâve been using Unity. In such cases, something similar to what the OP is doing seems reasonable - make another method that does the initialization, then call that from any relevant entry point if it hasnât already been called.
I have run into the issue myself a couple of times over the years. Not that many times, I suppose. Each time that I can recall, it happened when the two classes in question were written by different programmers.
I think these things get more important the more people are working on the same project, and the more complex the project is. Itâs a similar dynamic as with things like unit tests / assertations: theyâre not that important, until your project size grows so large that they are.
Also I suppose that on-demand instance fetching / creation becomes more important when youâre working with editor code, which I happen to be doing a lot
Yes, OnEnable was used in the original code, and FindObjectOfType would be similar⌠thatâs the problem.
Itâs a timing issue.
OP has a timing issue where theyâre accessing the singleton during its OnEnable, and the Awake/OnEnable of the singleton has yet to occur.
FindObjectOfType wouldnât resolve OPs problem since it suffers from the same timing issue.
Analogously OP tried picking up water before it turned to ice with his right hand, and you suggested to use his left hand. Both have the same problem⌠itâs not ice yet (the singleton hasnât been initialized yet).
@lordofduct Perhaps youâre not understanding exactly what Iâm suggesting?
Consider this simple singleton implementation:
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager instance;
public static GameManager Instance
{
get
{
if(instance == null)
{
instance = Object.FindObjectOfType<GameManager>();
}
return instance;
}
}
}
And this simple test:
using UnityEngine;
public class TestGameManager : MonoBehaviour
{
private void Awake()
{
Debug.Log("GameManager found during Awake: " + (GameManager.Instance != null));
}
private void OnEnable()
{
Debug.Log("GameManager found during OnEnable: " + (GameManager.Instance != null));
}
private void Start()
{
Debug.Log("GameManager found during Start: " + (GameManager.Instance != null));
}
}
As long as both components exist somewhere in the same scene when it is loaded, GameManager.Instance will never return null during any of these test. Not even if you manually set TestGameManager to always execute before GameManager using the Script Execution Order settings.
Because youâre not relying on the instance being set during some Unity event function, script execution order no longer matters at all.
The only example that I can think of where this would fail is in some sort of weird multi-scene loading scenario where TestGameManagerâs scene is loaded before GameManagerâs scene. But if youâre building that sort of multi-scene loading system, making it impossible for this to happen should be the first thing you do anyways.
My point was that FindObjectOfType only returns enabled/active objects, per the documentation. And due to timing, it may or may not be instantiated/enabled yet, depends on how the scene loads. Which is OPâs original problem.
This might possibly work for you in your test, especially in the editor, if the timing is right. Unfortunately in my 9 years of experience using Unity⌠timing being one way in the editor doesnât always reflect the timing in a targeted build.
Case in point⌠I use aron granbergâs A* pathfinding library in a couple of my released games. His library uses FindObjectOfType for the very purpose youâre describing. Unfortunately I have this really annoying bug out in the wild in one of my titles where scenes have no AI pathfinding because the ordering/timing can be all wrong when built to those target release platforms. Super annoying because all my testing in editor works great! Itâs only in the wild that it occurs⌠I specifically had to hunt down machines with specific hardware specs to even recreate it in a release build.
(on a side note, that same FindObjectOfType also crashes on some machines⌠I reported it, but never got any response from Unity likely cause itâs not easily reproduceable. I avoid it like the plague now)
If you are relying on Unity to deserialize data into your class, and one way or another the function that wants to reference this class is running before that deserialization occurs, then Iâm pretty sure you are screwed no matter how the reference is obtained. To avoid this, you would presumably either need to make sure you never try to reference the class before whatever point at which deserialization is guaranteed to have occurred (which you say is undocumented, so youâd have to guess what that point is) or you would need to avoid any component ever relying on data in any other component that has been set through Unityâs inspector (which strikes me as rather impractical).
I suppose that is technically a risk, but I suspect that a version of Unity that allows some objects to Awake before other objects have been deserialized would break a huge number of existing Unity applications, that makes it pretty unlikely Unity would ever do it. Indirect references like someComponent.someSubComponent are so common and so innocuous that itâs hard to believe any significant fraction of Unity users have completely avoided ever using them from OnEnable or Awake. (Nor is that the limit of the problems; it would also be unsafe to call any functions on any component that relies in any way on any of its serialized data.)
Additionally, note that classes referencing scriptable objects or prefabs would also need some way of knowing when it is safe to assume that they have been deserialized, and since they never Awake, that couldnât be the rule. You could drive yourself crazy trying to prepare for every possible future system Unity could ever use for this, but it really seems to me that Unityâs only sensible option is guaranteeing that all objects deserialize before any object Awakes.
I mean yeah⌠Unity in their own documentation even says donât rely on the data in any other component during Awake. It says you should only setup relationships during Awake, but access data on those objects in Start or later.
Mind you when Instantiate is called, it pushes it through the entire creation/awake/onenable process for you so that it can immediately be accessed the line after calling Instantiate.
And OPâs original problem all stems from that ordering/timing. OnEnable happens just after Awake, rather than just before Start, relative to other components. It gets this weird grey area of behaviour where the first time its called it could potentially be before Awake has been called on any other object. Where as if itâs called later on (because you toggled its enabled property), then it is safe to access all other objects. Itâs an annoying duality to is operation.
So as a result its bad to access the state/data of another object during OnEnable, similar to Awake. Because you might be accessing another object that has yet to be initialized. Set up relationships⌠sure, but donât access.
This is why I use my OnStartOrEnable, which removes this duality.
âŚ
Back at your option of the constructor. It doesnât necessarily resolve that duality. Sure, in most cases the deserialization has likely occurred (some build targets depending version âmightâ behave differently, but those are edge cases). But it limits OP in that they canât use Awake in the GameManager singleton anymore if another object attempts to modify its state BEFORE its Awake is even called.
The big take away I wanna stress is⌠be aware of WHY the problem exists so OP can work around it. And as a result I prefer methodologies that take those into consideration so you donât run into other gotchas. Like yes, the constructor will resolve the null reference exception in 99.999999% of cases (since yes, technically the serialization issue probably wonât hurt them). But now in a month say they do something in Awake of their singleton like set playerLives, theyâre going to get unexcepted behaviour since they didnât really resolve the root of the issue. They just patched around it.
Looking at the documentation, it does seem to suggest that it will have been initialized (deserialized I guess you can say) during Awake. Though Iâve had odd behaviour personally on target builds over years where that is unreliable.
I instead rely on this specific part about setting up references to other object, but donât pass information (data) back and forth until Start.
And like I stated in this most recent post. Iâm more concerned about the all the intialization along the process. Both from Unity and by the user.