脚本的执行顺序-唤醒和启用

时间:2019-10-19 15:39:42

标签: c# unity3d

我遇到了与脚本执行顺序有关的问题,尽管我是一位经验丰富的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: enter image description here

现在我不明白-如何在其他脚本的Awake方法之前执行OnEnable方法?

由于这个原因,我得到了一个空引用异常。据我了解,所有脚本Awake方法都首先执行,然后在OnEanble调用项目的所有脚本之后执行。

请向我解释这一点,以便解决我身边的困惑。

3 个答案:

答案 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方法,我在其中编写了要在其他子脚本中执行的所有代码,方法是在游戏管理器中引用该代码。