Порядок выполнения скриптов - Awake и OnEnable - PullRequest
0 голосов
/ 19 октября 2019

Я сталкиваюсь с проблемами, связанными с порядком выполнения сценариев, и этот момент не в моем уме, хотя я являюсь опытным разработчиком 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

Теперь я не могу понять - как метод OnEnable выполняется перед методом Awake другого сценария?

По этой причине,Я получаю исключение нулевой ссылки. Насколько я понимаю, все сценарии Awake-методы выполняются сначала, а затем после вызова OnEanble для всех сценариев проекта.

Пожалуйста, объясните мне этот момент, чтобы разрешить мою путаницу.

Ответы [ 3 ]

0 голосов
/ 19 октября 2019

В коде, который вы показали, вы пытаетесь получить экземпляр чего-то с именем GameManager, но позже приведенный ниже скрипт будет называться SoundManager.

Что вам нужно сделать, это иметь игровой объект в вашемсцена с SoundManager скриптом.

Затем в MainMenuController создайте ссылку на этот GameObject. Например, сериализация этого поля:

[SerializedField] GameObject soundManagerObj;

А затем получить доступ к функциям сценария SoundManager следующим образом:

soundManagerObj.GetComponent<SoundManager>().IsSoundEnabled();

После редактирования вопроса :

Чтобы преобразовать GameManager в синглтон (используйте this в качестве ссылки):

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(),Вместо:

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;
    //...
 }
}
0 голосов
/ 20 октября 2019

ваш вопрос: почему OnEnable () выполняется перед Awake () в других скриптах, верно?

, если да, то я думаю, что мой ответ поможет вам,

Я сталкивался с этимпроблема много раз, и я думаю, что это происходит из-за множества сценариев на сцене и множества сценариев на одном и том же «игровом объекте», поэтому из-за этого иногда эти сценарии запускаются в разных кадрах, например иногда требуется 2 или 3 кадра длязавершите выполнение, поэтому в некоторых игровых объектах метод Awake и метод OnEnable выполняются в кадре 1, а в других gameObjects методы Awake запускаются в кадре 2 или 3.

для решения этой проблемы, обычно я удаляю все методы OnEnableиз всех сценариев в sub-GameObjects и объединить его в один или несколько основных сценариев в родительских gameObjects, например, сценарий диспетчера игры имеет метод OnEnable, я написал внутри него весь код, который хочу выполнить в других подпрограммах, ссылаясь на него внутри игрыменеджер.

0 голосов
/ 19 октября 2019

Вы не реализовали шаблон 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 , поскольку система сначала проверяет, существует ли предварительно определенный префаб для синглтона, и порождает его, а затем возвращается к созданию нового игрового объектаи приложив к нему скрипт.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...