Search code examples
c#unity-game-enginesynchronizationdata-access

How to ensure that MonoBehaviour object has loaded its data before another Actor wants to access it?


I have an Inventory : MonoBehaviour that loads some mock data in Start(), including a "selectedWeapon" variable that is set and can be read.

Let's say I want to access this variable when I set up another MonoBehaviour in the same scene.

Is there a good way to ensure that the variable is set when trying to access it? I aim to do it in Start() or some initial function preferably only once.

My temporary solution is to use the Update() function on the instance that wants to access the "selectedWeapon" from the Inventory, and repeatably tries to set it's own variable as long as it is not set.

/// This is only an example, illustrating my problem.

public class Inventory : MonoBehaviour
{
    [SerializeField]
    private WeaponItem m_SelectedWeapon = null;
    .
    .
    .
    void Start()
    {
        m_SelectedWeapon = MockDatabase.GetSelectedWeapon();
    }
    .
    .
    .
    public WeaponItem GetSelectedWeapon()
    {
        return m_SelectedWeapon;
    }
}

//--------------------------------------------------------------------

public class Actor : MonoBehaviour
{
    private WeaponItem m_SelectedWeapon = null;

    public Inventory inventory;
    .
    .
    .
    void Start()
    {
       // Would like to set up things here
       // but this can let m_SelectedWeapon be null
       // since it may be null in Inventory
       m_SelectedWeapon = inventory.GetSelectedWeapon();
    }

    void Update()
    {
        // If not yet set, load from inventory
        if(m_SelectedWeapon == null)
            m_SelectedWeapon = inventory.GetSelectedWeapon();
    }
    .
    .
    .    
}

The temporary solution feels unsustainable since the checks in Update will certainly grow in my project.


Solution

  • My general shortrule is always

    • Use Awake for everything where you do not depend on others so setting up your own values, for setting up all references between components (but without using their values yet), stuff where you can use static classes.

      Also use this if possible for long taking loading/IO so it is done during the application loads and doesn't appear as lag for the user.

    • Use Start for everything where you need other components be already setup for e.g. using the values of references that have been setup in Awake.

    also see Awake and Start Tutorial


    This has its limits ofcourse so when needed you can start manipulating the Script Execution Order but I avoid that as much as possible.


    If it gets really complex there sometimes isn't really a way around using an event system like e.g.

    public class A : MonoBehaviour
    {
        public event Action OnReady;
        public bool isReady;
    
        private void Awake()
        {
            // do your stuff
    
            isReady = true;
    
            // execute whatever was added as callback
            OnReady?.Invoke();
        }
    }
    

    and than add callbacks where needed like e.g.

    public class B : MonoBehaviour
    {
        // either reference it in the Inspector
        public A a;
    
        private void Awake()
        {
            // or get it somehow on runtime
            a = FindObjectOfType<A>();
    
            // if ready directly move on otherwise add callbacks
            if(a.isReady)
            {
                OnAReady();
            }
            else
            {
                // it is always good to remove the callback even though
                // it wasn't added yet. Makes sure it is always only added once
                a.OnReady -= OnAReady;
                a.OnReady += OnAReady;
            }
        }
    
        private void OnDestroy()
        {
            // always remove callbacks when no longer needed
            a.OnReady -= OnAReady;
        }
    
        private void OnAReady()
        {
            // always remove callbacks when no longer needed
            a.OnReady -= OnAReady;
    
            // use stuff from A
        }
    }
    

    That looks more annyoing and complex but is way more efficient than waiting for some only-do-it-once event in the Update method.