Reputation: 4312
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 would like an 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 am not 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.
Upvotes: 0
Views: 1635
Reputation: 19
your question is: why OnEnable() executed before Awake() in other scripts, Right?
if yes, then I think my answer will help you,
I have faced this problem many times, and I think it's happening because of the many scripts in the scene and the many scripts on the same "game object", so because of that sometimes these scripts will run in deferent frames like sometimes it's need 2 or 3 frames to end executing, that's why in some game objects the Awake method and OnEnable method runs in frame 1 and in other gameObjects the Awake methods run in frame 2 or 3.
to solve this problem, usually I remove all OnEnable methods from all scripts in sub-GameObjects and unite it in one or more main scripts in parent gameObjects, e.g. game manager script has an OnEnable method, I wrote inside it all code I want to execute in other sub-scripts by referencing it inside the game manager.
Upvotes: 0
Reputation: 7605
In the code you have shown you try to get an instance of something named GameManager
but later the script below is named SoundManager
.
What you should do is to have a Game Object in your scene with SoundManager
script on it.
Then in MainMenuController
make a reference to that GameObject. For example serializeing that field:
[SerializedField] GameObject soundManagerObj;
And then access the SoundManager script functions like this:
soundManagerObj.GetComponent<SoundManager>().IsSoundEnabled();
After the Question's Edit:
To convert the GameManager into a singleton (use this as a reference):
public class GameManager : MonoBehaviour
{
public static GameManager instance = null;
// Change this to public to access from the other script
public int levelIndex;
public int gameScore;
public int playerLives;
void Awake()
{
if(instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);
// To keep this objectr from one scene to the next one
DontDestryOnLoad(gameObject)
}
}
And second thing to take into account, dont use Instace
with capitals in Init()
. Instead:
public class MainMenuController : MonoBehaviour
{
[SerializeField] Text bestScoreText;
[SerializeField] Toggle soundToggle;
public GameObject gameManager;
private void OnEnable()
{
Init();
}
private void Init()
{
if (GameManager.instance == null){
Debug.Log("null game manager");
Instantiate(gameManager);
}
gameManager.playerLives = 0;
//...
}
}
Upvotes: 1
Reputation: 1334
You didn't implement the Singleton pattern correctly, you should account for race conditions, especially when it comes to unity's event functions. A correct singleton behaviour, creates an instance if one doesn't exist, also allows you to spawn pre-defined singleton with pre-defined fields, also easier to implement other singletons without repeating your code (boilerplate) :
using UnityEngine;
public class SingletonPattern<T> : MonoBehaviour, ISingleton where T : MonoBehaviour
{
#region Static Fields
private static T instance = null;
#endregion
#region Fields
[SerializeField]
protected bool destroyOnLoad = true;
private Transform m_transform = null;
private GameObject m_gameObject = null;
private bool m_isInitialized = false;
#endregion
#region Static Properties
public static bool HasInstance
{
get { return instance != null; }
}
/// <summary>
/// Gets the singleton instance which will be persistent until Application quits.
/// </summary>
/// <value>The instance.</value>
public static T Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<T>();
// We need to create new instance
if (instance == null)
{
var _singletonType = typeof(T);
// First search in resource if prefab exists for this class
string _singletonName = _singletonType.Name;
GameObject _singletonPrefab = Resources.Load("Singleton/" + _singletonName, typeof(GameObject)) as GameObject;
if (_singletonPrefab != null)
{
Debug.Log(string.Format("[SingletonPattern] Creating singeton {0} using prefab",_singletonName));
instance = (Instantiate(_singletonPrefab) as GameObject).GetComponent<T>();
}
else
{
instance = new GameObject().AddComponent<T>();
}
// Update name
instance.name = _singletonName;
}
}
return instance;
}
private set
{
instance = value;
}
}
#endregion
#region Properties
public Transform CachedTransform
{
get
{
if (m_transform == null)
{
m_transform = transform;
}
return m_transform;
}
}
public GameObject CachedGameObject
{
get
{
if (m_gameObject == null)
{
m_gameObject = gameObject;
}
return m_gameObject;
}
}
#endregion
#region MonoCallbacks
protected virtual void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
}
if (!m_isInitialized)
{
Init();
}
}
protected virtual void Start()
{ }
protected virtual void Reset()
{
// Reset properties
m_gameObject = null;
m_transform = null;
m_isInitialized = false;
}
protected virtual void OnEnable()
{ }
protected virtual void OnDisable()
{ }
protected virtual void OnDestroy()
{
}
protected virtual void OnApplicationQuit()
{
if (instance == this)
{
instance = null;
}
}
#endregion
#region Methods
protected virtual void Init()
{
// Set as initialized
m_isInitialized = true;
// Just in case, handling so that only one instance is alive
if (instance == null)
{
instance = this as T;
}
// Destroying the reduntant copy of this class type
else if (instance != this)
{
Destroy(CachedGameObject);
return;
}
// Set it as persistent object
if (!destroyOnLoad)
{
DontDestroyOnLoad(CachedGameObject);
}
}
public void ForceDestroy()
{
// Destory
Destroy(CachedGameObject);
}
#endregion
}
Creating a GameManager singleton is now easy:
public class GameManager : SingletonPattern<GameManager>
{
}
Now if you access GameManager.Instance
at any time, it'll create the instance if it's not created yet, avoiding the headache of maintaining race conditions in unity's event functions.
If you have a GameManager
or any singleton with properties you want to preset in the editor which a created instance in play mode won't have, then create an instance prefab and place it at a folder named Singleton under a folder called Resources, since the system first checks if a pre-defined prefab for the singleton exists and spawns it and then falls back to creating a new gameobject and attaching the script to it.
Upvotes: 1