在回答之前,请务必阅读整个问题并运行示例。
概述
我在Unity 5.6.1中遇到了一些不一致的行为,当在静态Editor脚本中(因此在标记有[InitializeOnLoad]
的类的静态构造函数中)加载嵌套资产时。
我正在使用Resources.Load
加载一个带有公共引用另一个资源的ScriptableObject
资源,假设是一个GameObject预制件。从这一点上来说,在这个简化的示例中,我将引用ScriptableObject作为'Wrapper'。
虽然Resources.Load
正确返回Wrapper,但是第一次运行时经常还没有加载嵌套的Prefab引用,但是在第二次运行后它就被加载了:
据我所知,这是一个执行顺序问题,在静态构造时尚未加载所涉及的Prefab资源,在后续运行中仍然被缓存。
我认为,当加载一个序列化引用另一个资产的资产时,默认情况下,嵌套的资产会自动加载,而不管这是否是在静态初始化期间进行的。然而,在这里似乎并非如此。
证明Wrapper资产确实正确地引用了Prefab在其序列化数据中(Asset Serialization设置为Force Text):
我还尝试使用AssetDatabase.LoadAssetAtPath
(至少在编辑器中),但没有任何区别。
示例项目
您可以从此处下载UnityPackage,其中包含以下内容:
或者按以下方式复制:
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 prefabNote that because on occasion unity does correctly cache the asset,
背景
这个例子是故意简化的,但是它基于使用ScriptableObjects
存储/共享自定义系统配置数据的真实项目。
请不要回复以下内容:
- "只需使用
Object.Instantiate
" - 不会改变结果,并且在某些情况下修改Resources.Load
返回的原始对象是可取的。 - "跳过包装器,直接引用prefab/手动加载" - 虽然这样可以避免加载问题,但也错过了问题的重点。增加抽象层可以使系统之间共享资源更易于维护。此外,这个问题并不仅限于Prefabs(在这里只是为了简单的例子)。更现实的例子将包含几个嵌套对象,如Sprites、Materials、其他ScriptableObjects等。
- "不要在静态构造期间加载" - Unity支持静态系统(否则为什么提供
[InitializeOnLoad]
?),并且使用基于ScriptableObjects的资产来存储这些系统的配置信息是一个非常真实的用例。在完全重新架构系统之前,我想看看其他可能的替代方案。
我想要的是:
- 是否可以强制Unity在这种静态上下文中预加载包装器中序列化的资产,而不必通过路径手动加载其内容?
- 换句话说,我不想只运行
Resources.Load<GameObject>("ExampleObject")
,因为那样会抵消封装的整个意义。我可以修改ExampleWrapper
类,但任何潜在的解决方案都需要自动化到添加prefab到检查器字段的工作流程就足够了。
编辑: 还应该注意到,当我关闭项目并重新打开时,我会看到以下内容:
- 在启动过程中,静态构造函数被调用一次,Wrapper被加载,并且嵌套的prefab实际上被正确加载了。
- 然后(仍然在初始启动期间,因为它发生在我输入任何操作之前)它再次静态构造,这次当Wrapper被加载时,嵌套的prefab没有被加载。
这个,我真的不理解。
Resources.Load
第一次不起作用,然后你又说你甚至不想使用Resources.Load
...你希望听到什么样的答案?那你想要使用什么呢? - ProgrammerResource.Load
,而不是在每个封装资源上手动调用Resource.Load
,因为这样做会削弱将它们封装在单个包装器中的优点。请参考Galandil的答案,那里有我正在寻找的内容。 - Johannes