为什么未加载 ScriptableObject 资产中的嵌套资源引用?
Why are nested resources references in a ScriptableObject asset not loaded?
请在发布答案之前阅读整个问题和运行示例。
概述
我 运行 在静态编辑器脚本中加载 嵌套 资产时,我 运行 在 Unity 5.6.1 中出现一些不一致的行为(因此在 class 标记为 [InitializeOnLoad]
).
我正在加载一个 ScriptableObject
Asset with Resources.Load
,并且 ScriptableObject 有一个对另一个资产资源的 public 引用,让我们假设一个 GameObject Prefab。从这一点开始,我将 ScriptableObject 称为 'Wrapper',因为在这个简化的示例中,这是它服务的唯一目的。
虽然 Resources.Load
正确地 returns 包装器,但嵌套的 Prefab 引用通常在第一个 运行 期间尚未加载,但在第二个 运行 之后加载:
据我了解,这是一个执行顺序问题,其中有问题的预制资源在静态构建期间尚未加载,并且在随后的 运行 中它仍然被缓存。
我假设当加载一个资产并引用另一个资产时,嵌套资产将默认自动加载,无论这是否在静态初始化期间。然而,这里似乎并非如此。
证明 Wrapper 资产确实在其序列化数据中正确引用了 Prefab(使用 Asset Serialization set to Force Text):
我也尝试过使用 AssetDAtabase.LoadAssetAtPath
(至少在编辑器中是这样),但没有什么不同。
示例项目
您可以下载一个UnityPackage here,其中包含以下内容:
或重现如下:
脚本:
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
}
}
在层次结构中创建一个空的 "ExampleObject" GameObject,然后将其保存为预制件 Assets/Resources/ExampleObject.prefab
创建 ExampleWrapper 的资产实例并在 Assets/Resources/Wrapper.asset
- 由于 Unity 5 不提供 UI 来生成 ScriptableObject,因此请创建您的 own menu item, or use an automated solution。此问题假定您足够熟悉 ScriptableObjects 以拥有自己的首选方法。
将 Wrapper 资产的 Value
字段设置为 ExampleObject 预制件
请注意,因为有时 unity 会正确缓存资产,
理由
此处的示例有意简化,但它基于使用 ScriptableObjects
用于自定义系统的 storing/sharing 配置数据的真实项目。
请不要回复以下内容:
- "Just Use
Object.Instantiate
" - 不改变结果,在某些情况下修改 Resources.Load
返回的原始对象是可取的。
- "Skip the wrapper and just reference the prefab directly/Load it manually" - 虽然这绕过了加载问题,但它也忽略了问题的重点。添加抽象级别可以使系统之间的资源共享更易于维护。此外,此问题不仅限于预制件(此处仅用于简单示例)。更真实的示例将包含多个嵌套对象,例如 Sprites、Materials、其他 ScriptableObjects 等。
- "Don't load during static construction" - Unity 支持静态系统(为什么还要提供
[InitializeOnLoad]
?)并使用基于 ScriptableObjects 的资产来存储此类的配置信息系统是一个非常真实的用例。在完全重新构建系统之前,我想看看其他可能的替代方案。
我正在寻找什么:
- 当我在这样的静态上下文中加载它时,是否可以强制 Unity 预加载在包装器中序列化的资产,而不必通过其路径手动加载其内容?
- 换句话说,我不想只是 运行
Resources.Load<GameObject>("ExampleObject")
,因为这首先会否定封装它的全部意义。我可以修改 ExampleWrapper
class,但任何潜在的解决方案都需要足够自动化,以便在检查器中将预制件添加到现场的工作流将是所有需要的。
编辑: 还应该注意的是,奇怪的是,当我关闭项目并再次打开它时,我看到以下内容:
- 启动时静态构造函数被调用一次,Wrapper被加载,嵌套prefab实际加载正确
- 然后(仍在初始启动中,因为它发生在我可以输入任何操作之前)它再次静态构造,这次加载 Wrapper 时,嵌套预制件 不是 已加载。
这个,我真的不懂
你的问题存在误解。
引用是传递给Loader
class,场景初始化完成后可以通过loggingWrapper.Value
查看。
很可能,问题出在(如您所指出的)execution/serialization 顺序中,显然是这样发生的:
- 调用了
Loader
构造函数,正确传递了Wrapper
引用。
Debug.Log(Wrapper.Value)
returns null
因为脚本对象的字段还没有被序列化
Wrapper
的字段已序列化,现在记录 Wrapper.Value
正确显示 ExampleObject
.
因此,除非您计划 "special" 在初始化期间对 Wrapper
字段进行处理,否则您的代码确实没有问题:我尝试 运行 Debug.Log(Loader.Wrapper.Value)
在 ExampleWrapper
的 OnEnable
期间,我得到了正确的值。
关于您的编辑,显然它发生了 "by Design",正如本期中明确指出的那样:https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens
请在发布答案之前阅读整个问题和运行示例。
概述
我 运行 在静态编辑器脚本中加载 嵌套 资产时,我 运行 在 Unity 5.6.1 中出现一些不一致的行为(因此在 class 标记为 [InitializeOnLoad]
).
我正在加载一个 ScriptableObject
Asset with Resources.Load
,并且 ScriptableObject 有一个对另一个资产资源的 public 引用,让我们假设一个 GameObject Prefab。从这一点开始,我将 ScriptableObject 称为 'Wrapper',因为在这个简化的示例中,这是它服务的唯一目的。
虽然 Resources.Load
正确地 returns 包装器,但嵌套的 Prefab 引用通常在第一个 运行 期间尚未加载,但在第二个 运行 之后加载:
据我了解,这是一个执行顺序问题,其中有问题的预制资源在静态构建期间尚未加载,并且在随后的 运行 中它仍然被缓存。
我假设当加载一个资产并引用另一个资产时,嵌套资产将默认自动加载,无论这是否在静态初始化期间。然而,这里似乎并非如此。
证明 Wrapper 资产确实在其序列化数据中正确引用了 Prefab(使用 Asset Serialization set to Force Text):
我也尝试过使用 AssetDAtabase.LoadAssetAtPath
(至少在编辑器中是这样),但没有什么不同。
示例项目
您可以下载一个UnityPackage here,其中包含以下内容:
或重现如下:
脚本:
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 } }
在层次结构中创建一个空的 "ExampleObject" GameObject,然后将其保存为预制件
Assets/Resources/ExampleObject.prefab
创建 ExampleWrapper 的资产实例并在
Assets/Resources/Wrapper.asset
- 由于 Unity 5 不提供 UI 来生成 ScriptableObject,因此请创建您的 own menu item, or use an automated solution。此问题假定您足够熟悉 ScriptableObjects 以拥有自己的首选方法。
将 Wrapper 资产的
Value
字段设置为 ExampleObject 预制件请注意,因为有时 unity 会正确缓存资产,
理由
此处的示例有意简化,但它基于使用 ScriptableObjects
用于自定义系统的 storing/sharing 配置数据的真实项目。
请不要回复以下内容:
- "Just Use
Object.Instantiate
" - 不改变结果,在某些情况下修改Resources.Load
返回的原始对象是可取的。 - "Skip the wrapper and just reference the prefab directly/Load it manually" - 虽然这绕过了加载问题,但它也忽略了问题的重点。添加抽象级别可以使系统之间的资源共享更易于维护。此外,此问题不仅限于预制件(此处仅用于简单示例)。更真实的示例将包含多个嵌套对象,例如 Sprites、Materials、其他 ScriptableObjects 等。
- "Don't load during static construction" - Unity 支持静态系统(为什么还要提供
[InitializeOnLoad]
?)并使用基于 ScriptableObjects 的资产来存储此类的配置信息系统是一个非常真实的用例。在完全重新构建系统之前,我想看看其他可能的替代方案。
我正在寻找什么:
- 当我在这样的静态上下文中加载它时,是否可以强制 Unity 预加载在包装器中序列化的资产,而不必通过其路径手动加载其内容?
- 换句话说,我不想只是 运行
Resources.Load<GameObject>("ExampleObject")
,因为这首先会否定封装它的全部意义。我可以修改ExampleWrapper
class,但任何潜在的解决方案都需要足够自动化,以便在检查器中将预制件添加到现场的工作流将是所有需要的。
编辑: 还应该注意的是,奇怪的是,当我关闭项目并再次打开它时,我看到以下内容:
- 启动时静态构造函数被调用一次,Wrapper被加载,嵌套prefab实际加载正确
- 然后(仍在初始启动中,因为它发生在我可以输入任何操作之前)它再次静态构造,这次加载 Wrapper 时,嵌套预制件 不是 已加载。
这个,我真的不懂
你的问题存在误解。
引用是传递给Loader
class,场景初始化完成后可以通过loggingWrapper.Value
查看。
很可能,问题出在(如您所指出的)execution/serialization 顺序中,显然是这样发生的:
- 调用了
Loader
构造函数,正确传递了Wrapper
引用。 Debug.Log(Wrapper.Value)
returnsnull
因为脚本对象的字段还没有被序列化Wrapper
的字段已序列化,现在记录Wrapper.Value
正确显示ExampleObject
.
因此,除非您计划 "special" 在初始化期间对 Wrapper
字段进行处理,否则您的代码确实没有问题:我尝试 运行 Debug.Log(Loader.Wrapper.Value)
在 ExampleWrapper
的 OnEnable
期间,我得到了正确的值。
关于您的编辑,显然它发生了 "by Design",正如本期中明确指出的那样:https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens