Reputation: 6419
Please read the entire question, and run the example before posting an answer.
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:
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):
I have also tried to use AssetDAtabase.LoadAssetAtPath
(at least while in the editor), which has not made a difference.
You can download a UnityPackage here, which contains the following:
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
Set the Wrapper asset's Value
Field to the ExampleObject prefab
Note that because on occasion unity does correctly cache the asset,
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:
Object.Instantiate
" - Does not change outcome, and modifying the raw object returned by Resources.Load
is desirable in some cases.[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:
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:
This, I really don't understand.
Upvotes: 5
Views: 3233
Reputation: 4249
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:
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 serializedWrapper
'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
Upvotes: 3