我遇到了与脚本执行顺序有关的问题,尽管我是一位经验丰富的Unity开发人员,但我无法想到这一点。因此,我想对此进行解释。
这是我的MenuContoller脚本代码:
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;
}
}
这是我的GameManager脚本代码:
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;
}
}
}
我在执行过程中收到NullReferenceException:
现在我不明白-如何在其他脚本的Awake方法之前执行OnEnable方法?
由于这个原因,我得到了一个空引用异常。据我了解,所有脚本Awake方法都首先执行,然后在OnEanble调用项目的所有脚本之后执行。
请向我解释这一点,以便解决我身边的困惑。
答案 0 :(得分:0)
您没有正确实现Singleton模式,应该考虑竞争条件,尤其是当涉及到unity的事件函数时。 正确的单例行为,如果不存在则创建一个实例,还允许您生成具有预定义字段的预定义单例,也更容易实现其他单例而无需重复代码(样板):
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
}
现在轻松创建GameManager单例:
public class GameManager : SingletonPattern<GameManager>
{
}
现在,如果您随时访问GameManager.Instance
,它将在尚未创建实例的情况下创建该实例,从而避免了维护unity事件函数中竞争条件的麻烦。
如果您有一个GameManager
或具有要在编辑器中预设的属性的任何单例,而该属性在播放模式下创建的实例将不具备此属性,则请创建实例预制并将其放置在名为 Singleton 在名为 Resources 的文件夹下,因为系统会首先检查是否存在针对单例的预定义预制件并生成它,然后退回以创建新的游戏对象并附加脚本
答案 1 :(得分:0)
在显示的代码中,您尝试获取名为public ArrayList<Integer> getAllGroupNumbers() {
ArrayList<Integer> a = new ArrayList<Integer>();
for(int i = 0; i < list.size(); i++)
{
if(list.get(i).getGroupNumber() != ...)
}
}
的实例,但随后下面的脚本名为GameManager
。
您应该做的是在场景中放置一个SoundManager
脚本的游戏对象。
然后在SoundManager
中引用该GameObject。例如,序列化该字段:
MainMenuController
然后访问如下所示的SoundManager脚本功能:
[SerializedField] GameObject soundManagerObj;
问题编辑后:
要将GameManager转换为单例(使用this作为参考):
soundManagerObj.GetComponent<SoundManager>().IsSoundEnabled();
第二点要考虑的是,不要将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)
}
}
中的大写字母与Instace
一起使用。相反:
Init()
答案 2 :(得分:0)
您的问题是:为什么在其他脚本中OnEnable()在Awake()之前执行,对吗?
如果是的话,那么我认为我的回答会对您有所帮助,
我已经多次面对这个问题,并且我认为它是由于场景中的许多脚本以及同一“游戏对象”上的许多脚本而发生的,因此有时,这些脚本会像某些时候那样在不同的帧中运行它需要2或3帧才能结束执行,这就是为什么在某些游戏对象中Awake方法和OnEnable方法在第1帧中运行,而在其他gameObjects中Awake方法在第2或3帧中运行。
为解决此问题,通常我从子GameObjects中的所有脚本中删除所有OnEnable方法,并将其与父gameObjects中的一个或多个主脚本结合起来,例如游戏管理器脚本具有OnEnable方法,我在其中编写了要在其他子脚本中执行的所有代码,方法是在游戏管理器中引用该代码。