I have a singleton class (let’s call it A) that has some initialization code in Awake().
I have a separate class (We’ll call it B), that wants to use the singleton A in OnEnable, so every time the B is enabled (B could be toggled throughout the game) I want it to use this Singleton.
The problem is that OnEnable is called alongside Awake for every object, and having code in OnEnable does not guarantee that ALL other objects’ Awake methods have been called.
From my understanding, unity only guarantees that (for a particular Monobehaviour) it calls functions in the order of Awake, OnEnable, Start.
If I have Object1, and Object2 with their own functions, they’ll be called like the following:
Code in OnEnable cannot assume ALL other Awake calls have been made, only that THIS object’s Awake call has.
How do I get around this?
I’ve tried the following:
public class Object1 {
private bool HasInitialized = false;
public void OnEnable() {
if(HasInitialized) {
Singleton.Method();
}
}
public void Start() {
if(!HasInitialized) {
Singleton.Method();
}
HasInitialized = true;
}
}
But it seems very clunky and cumbersome to write on EVERY class that want’s to use a singleton.
Is there a better way to go about this?
hi,
the Awake, OnEnable and Update functions of different scripts are called in the order the scripts are loaded (which is arbitrary). However, it is possible to modify this order using the Script Execution Order settings (menu: Edit > Project Settings > Script Execution Order).
otherwise if the execution time is long and you need to wait to all tasks are finished . (sometime depends on other factors (exemple network or any other job)), i can use Couroutine on Start function to wait until my other object was successfully initialized or refreshed (example retrieved database data )
SOLUTION 1 :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class A : MonoBehaviour {
public bool _awakened = false;
public bool _enabled=false;
public bool _Started= false;
private void Awake()
{
//do jobs
Debug.Log( "object A is Awakened" );
_awakened = true;
}
private void OnEnable()
{
//do jobs
Debug.Log( "object A is Enabled" );
_enabled = true;
}
private IEnumerator Start()
{
// wait some time for simulated job
yield return new WaitForSeconds( 10 );
//do jobs
_Started = true;
}
}
and i have a class B that use class A but need A to be ready before using it :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class B : MonoBehaviour {
public A ReferenceObjectA;
// Use this for initialization
IEnumerator Start () {
yield return new WaitUntil( () => ReferenceObjectA._enabled && ReferenceObjectA._awakened && ReferenceObjectA._Started );
Debug.Log( "ReferenceObjectA is awakened and enabled and started !!" );
// do my logic game
}
}
Edit (SOLUTION 2): another idea come across my mind, is to use Delegates , or UnityEvent , you can implement the next same logic with both of them [the object him self will call the
other object(s) that he is ready ]
(OnEnabled can be static member depends on your need )
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class A : MonoBehaviour {
// UnityEvent that well invoke when we are ready, it may be a static if you want
public UnityEvent OnEnabled;
private void OnEnable()
{
//invoke all UnityAction attached to this UnityEvent
OnEnabled.Invoke( );
}
}
and then you have a class B that you want her to execute some code when an object A said he is ready.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class B : MonoBehaviour {
public A ReferencedAObject;
// Use this for initialization
void Awake () {
Debug.Log( "B Awake ");
ReferencedAObject.OnEnabled.AddListener( When_A_is_Ready );
}
//this will be called by object A him self
public void When_A_is_Ready()
{
// job that will be excuted when A call OnEnabled
Debug.Log( "A is now ready " );
}
}
I had a similar issue with the generic singleton class I am using, so I decided to add some more info for those who want to know why this implementation is breaking the codes and how to prevent those problems.
First, take a look at the code below(the one causing problems). Notice that we use unity’s Awake call to ensure that only one instance of the class is created.
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T _instance;
public static T Instance
{
get{return instance;}
}
public static bool isInicialized
{
get{return _instance != null;}
}
// Start is called before the first frame update
protected virtual void Awake()
{
if(_instance != null)
{
Debug.LogError("[Singleton] Trying to create a second instance of singleton ", this);
}
else
{
_instance = (T) this;
}
}
protected virtual void OnDestroy()
{
if(_instance == this)
{
_instance = null;
}
}
}
In this implementation, you’ll need to attach the script to a gameObject on the editor by yourself in order to use it.
We can see that it becomes a big problem since we cannot control the order that the scripts Awake’s will be called, and quite often you’ll see that scripts that have onAwake or onEnable references to the singleton will break.
Now let’s see a implementation that doesn’t use Awake.
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T _instance;
public static T Instance
{
get
{
if(_instance == null)
{
var objs = FindObjectsOfType(typeof(T)) as T[];
if (objs.Length > 0)
_instance = objs[0];
if (objs.Length > 1)
{
Debug.LogError("[Singleton] There is more than one instance of " + typeof(T).Name + " in the scene.");
}
if (_instance == null)
{
GameObject obj = new GameObject();
obj.hideFlags = HideFlags.DontSave;
_instance = obj.AddComponent<T>();
}
}
return _instance;
}
}
public static bool isInicialized
{
get{return _instance != null;}
}
protected virtual void OnDestroy()
{
if(_instance == this)
{
_instance = null;
}
}
}
Here we used the getter to ensure that anytime a script tries to access the Instance there will be a reference.
We could even destroy the other instances if found more than one, though Logging error seems enough to me.
For the singleton pattern you shouldn’t be using any of Unity’s built-in functions as an initialiser. The getter of the instance should always ensure there is one and only one ever this one returned.
MSDN Singleton in C#