5

Please read the entire question, and run the example before posting an answer.


Overview

I ran into some inconsistent behavior in Unity 5.6.1 when loading nested assets in a static Editor script (so in the static constructor of a class marked with [InitializeOnLoad]).

I am loading a ScriptableObject Asset with Resources.Load, and the ScriptableObject has a public reference to another asset resource, let's suppose a GameObject Prefab. From this point I will refer to the ScriptableObject as the 'Wrapper', since in this simplified example that is the only purpose it serves.

While Resources.Load correctly returns the Wrapper, the nested Prefab reference is often not yet loaded during the first run, but is loaded after the second run:

Screencap showing that the prefab is not loaded on the first run, but is on the second

To my understanding this is an order of execution issue where the Prefab resource in question has not been loaded yet during static construction, and on subsequent runs it remains cached.

I assumed that when loading in an asset with a serialized reference to another asset, that the nested asset would automatically be loaded by default, regardless of whether this is during static init or not. This however does not seem to be the case here.

Proof that the Wrapper asset does indeed correctly reference the Prefab in its serialized data (with Asset Serialization set to Force Text): Proof that prefab reference is correctly serialized

I have also tried to use AssetDAtabase.LoadAssetAtPath (at least while in the editor), which has not made a difference.


Example Project

You can download a UnityPackage here, which contains the following:

enter image description here

Or reproduce it as follows:

  • The Scripts:

    ExampleWrapper.cs:

    using UnityEngine;
    public class ExampleWrapper : ScriptableObject
    {
      public GameObject Value;
    }
    

    StaticLoader.cs:

    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    [InitializeOnLoad]
    #endif
    public class Loader
    {
      static Loader()
      {
        var Wrapper = Resources.Load<ExampleWrapper>("Wrapper");
        Debug.Log(Wrapper);         // Prints the Wrapper ScriptableObject
        Debug.Log(Wrapper.Value);   // Prints the Wrapped GameObject
      }
    }
    
  • Create an empty "ExampleObject" GameObject in the Hierarchy, then save it as a Prefab at Assets/Resources/ExampleObject.prefab

  • Create an asset instance of the ExampleWrapper and at Assets/Resources/Wrapper.asset

    • Since Unity 5 does not provide UI for spawning ScriptableObjects either create your own menu item, or use an automated solution. This question assumes you are familiar enough with ScriptableObjects to have your own preferred method.
  • Set the Wrapper asset's Value Field to the ExampleObject prefab enter image description here

  • Note that because on occasion unity does correctly cache the asset,


Rationale

The example here is deliberately simplified, but it is based on real projects that use ScriptableObjects for storing/sharing configuration data for custom systems.

Please do not reply with the following:

  • "Just Use Object.Instantiate" - Does not change outcome, and modifying the raw object returned by Resources.Load is desirable in some cases.
  • "Skip the wrapper and just reference the prefab directly/Load it manually" - While this bypasses the loading issue, it also misses the point of the question. Adding a level of abstraction can make sharing resources between systems more maintainable. Also this problem is not limited to Prefabs (just used here for easy example). More realistic examples would contain several nested objects, such as Sprites, Materials, other ScriptableObjects, etc.
  • "Don't load during static construction" - Static systems are supported by Unity (why else provide [InitializeOnLoad]?) and using assets based on ScriptableObjects to store config information for such systems is a very real use-case. Before completely re-architecting systems I would like to look at other potential alternatives.

What I am looking for:

  • Is it possible for me to force Unity to pre-load the asset serialized in the wrapper when I load it in a static context such as this, without having to manually load its content by its path?
  • In other words, I don't want to just run Resources.Load<GameObject>("ExampleObject"), since that would negate the whole point of encapsulating it in the first place. I'm fine with modifying the ExampleWrapper class, but any potential solution needs to be automated enough so that the workflow of adding the prefab to the field in the inspector would be all that is needed.

EDIT: It should also be noted that weirdly enough, when I close the project and open it again I see the following:

enter image description here

  • During startup, the static constructor is called once, the Wrapper is loaded, and the nested prefab is actually loaded correctly.
  • Then (still within the initial startup, since it happens before I can input any actions) it statically constructs again, and this time when the Wrapper is loaded, the nested prefab is not loaded.

This, I really don't understand.

2
  • First, you said that Resources.Load is not working the first time then you said you don;t even want to use Resources.Load...What do you want to hear as an answer? What do you want to use then? Commented Jun 18, 2017 at 16:03
  • I want to use Resource.Load once on the wrapper, not manually call Resource.Load on each of its wrapped resources, since that defeats the point of wrapping them in a single wrapper. Please see Galandil's answer for something useful that I was looking for. Commented Jun 18, 2017 at 21:15

1 Answer 1

3

There's a misconception regarding your question.

The reference is passed to the Loader class, and you can check it by logging Wrapper.Value after scene initialization is completed.

Most probably, the problem is (as you pointed out) in the execution/serialization order, apparently it happens something like this:

  • The Loader constructor is called, and Wrapper reference is passed correctly.
  • Debug.Log(Wrapper.Value) returns null because the fields of the scriptable object haven't yet been serialized
  • Wrapper's fields are serialized, now logging Wrapper.Value shows correctly ExampleObject.

So, unless you're planning something "special" to do with the Wrapper fields during initalization, there's really not a problem in your code: I tried to run Debug.Log(Loader.Wrapper.Value) during the OnEnable of ExampleWrapper, and I got the correct value.

Regarding your edit, apparently it happens "by Design", as clearly stated in this issue: https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens

Sign up to request clarification or add additional context in comments.

3 Comments

Ah that makes sense. Any serialized values are not loaded in yet, so even adding a public int Blah and changing it results in a default when printed in the static ctr. So does that mean that in terms of Execution Order it's: Static Init -> Apply Serialized Data -> Reset -> etc...?
Yeah Indeed I tested with an int in ExampleWrapper and it always logged 0 at start. Regarding the exec order during init, your guess is as good as mine, i.e.we can't know for sure what happens and when under the hood of Unity. But probably static constructor are indeed the first thing executed (Mono native takes precedence over Unity managed? No clue, really).
Makes sense, thanks for the feedback! Another relevant resources I've found since your answer was: blogs.unity3d.com/2016/06/06/… which does show that Unity -really- would rather not have users load resources in constructors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.