Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Scriptable Objects as Global Objects & Initialization

Discussion in 'Scripting' started by moose0847, Jul 2, 2021.

  1. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    33
    In my project, I've been using scriptable objects as global objects for things that I need to reference often such as the player, the game instance, UI manager, etc. Up until today the "OnGlobalEnable" function seemed to always fire prior to any objects in the scene having their "Start" event invoked, which I assumed was something built into the engine itself. However after doing a bit of research, I'm thinking that this might have been simply the result of a favorable race condition based on some other factors I wasn't controlling for elsewhere (thread here for reference).

    Generally speaking my use case for these global objects is as follows
    1. I create an asset in my project that I can reference in other scripts. This eliminates the need to search the scene or otherwise perform similar logic throughout the code.
    2. Each of the global objects I create in this manner, in it's "OnEnable" function will create whatever gameobjects it needs to function. For example, my "Player" global object creates a "Cursor" object that can handle interacting with objects in any scene.

    I'm currently finding this pattern quite useful, as it doesn't require me to constantly update initialization logic, and generally leverages the unity editor to solve problems that would have required little bits of code to be scatted throughout the project. Does anyone know of any thing in unity that could be referenced to get around this issue without needing to create a bunch of custom initialization logic?

    For reference, here is my GlobalObject class which all such objects derive from.

    Code (CSharp):
    1.     public abstract class GlobalObject : ScriptableObject
    2.     {
    3.         #region Initialization & Cleanup
    4.  
    5. #if UNITY_EDITOR
    6.         private void OnEnable()
    7.         {
    8.             EditorApplication.playModeStateChanged += OnPlayStateChange;
    9.         }
    10.  
    11.         private void OnDisable()
    12.         {
    13.             EditorApplication.playModeStateChanged -= OnPlayStateChange;
    14.         }
    15.  
    16.         private void OnPlayStateChange(PlayModeStateChange state)
    17.         {
    18.             if (state == PlayModeStateChange.EnteredPlayMode)
    19.             {
    20.                 OnGlobalEnable();
    21.             }
    22.             else if (state == PlayModeStateChange.ExitingPlayMode)
    23.             {
    24.                 OnGlobalDisable();
    25.             }
    26.         }
    27. #else
    28.         private void OnEnable()
    29.         {
    30.             OnGlobalEnable();
    31.         }
    32.  
    33.         private void OnDisable()
    34.         {
    35.             OnGlobalDisable();
    36.         }
    37. #endif
    38.  
    39.         #endregion
    40.  
    41.         #region Override Functions
    42.  
    43.         abstract protected void OnGlobalEnable();
    44.         abstract protected void OnGlobalDisable();
    45.  
    46.         #endregion
    47.     }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,749
    It might not apply directly but I was using OnEnable() recently within ScriptableObjects and ended up with a sporadic race condition reported by some users. Once I tracked it down and was able to repro the race condition, I was able to refactor a bit.

    Luckily all my SOs were already listed and managed by a singleton MonoBehaviour manager class so I refactored so that the manager class now has a single static function decorated with a
    [RuntimeInitializeOnLoadMethod]
    decorator that sets itself up and iterates all the SOs it is in charge of, telling them all to spin up.

    I have editor tooling to keep the master list of SOs updated, and the list of SOs is in another type of SO that the manager class loads. You can actually see the changes I speak of in my Datasacks package, but since I just did it yesterday it is still in the
    feature/next
    branch, NOT in master yet.

    The work was done in commit
    53419861
    but it's as trivial as my text description of it above, and so far it seems rock-solid.

    Datasacks is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/datasacks

    https://github.com/kurtdekker/datasacks

    https://gitlab.com/kurtdekker/datasacks

    https://sourceforge.net/projects/datasacks/
     
  3. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    33
    Thanks for the info! I'm going to give this a try.
     
  4. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    33
    @Kurt-Dekker Thanks for the help!

    I was able to get around having a manager object by leveraging a folder in my project where I can keep all global objects, but the RuntimeInitializeOnLoad method seems to do the trick! For reference, here is my new global object script.


    Code (CSharp):
    1.     public abstract class GlobalObject : ScriptableObject
    2.     {
    3.         #region Initialization & Cleanup
    4.  
    5.         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    6.         private static void GlobalInit()
    7.         {
    8.             AthenaGlobalObject[] globalObjects = Resources.LoadAll<AthenaGlobalObject>("Global");
    9.  
    10.             foreach (AthenaGlobalObject globalObject in globalObjects)
    11.             {
    12.                 if (globalObject != null)
    13.                 {
    14.                     globalObject.OnGlobalEnable();
    15.                 }
    16.             }
    17.         }
    18.  
    19.         private void OnEnable()
    20.         {
    21. #if UNITY_EDITOR
    22.             EditorApplication.playModeStateChanged += OnPlayStateChange;
    23. #endif
    24.         }
    25.  
    26.         private void OnDisable()
    27.         {
    28. #if UNITY_EDITOR
    29.             EditorApplication.playModeStateChanged -= OnPlayStateChange;
    30. #else
    31.             OnGlobalDisable();
    32. #endif
    33.         }
    34.  
    35. #if UNITY_EDITOR
    36.         private void OnPlayStateChange(PlayModeStateChange state)
    37.         {
    38.             if (state == PlayModeStateChange.ExitingPlayMode)
    39.             {
    40.                 OnGlobalDisable();
    41.             }
    42.         }
    43. #endif
    44.  
    45.         #endregion
    46.  
    47.         #region Override Functions
    48.  
    49.         abstract protected void OnGlobalEnable();
    50.         abstract protected void OnGlobalDisable();
    51.  
    52.         #endregion
    53.     }
     
    Kurt-Dekker likes this.