【问题标题】:Why are nested resources references in a ScriptableObject asset not loaded?为什么未加载 ScriptableObject 资产中的嵌套资源引用?
【发布时间】:2017-06-18 12:14:49
【问题描述】:

请阅读整个问题,并在发布答案之前运行示例。


概述

在静态编辑器脚本中加载 嵌套 资产时,我在 Unity 5.6.1 中遇到了一些不一致的行为(因此在标有 [InitializeOnLoad] 的类的静态构造函数中)。

我正在加载带有Resources.LoadScriptableObject 资产,并且ScriptableObject 具有对另一个资产资源的公共引用,让我们假设一个GameObject Prefab。从这一点开始,我将 ScriptableObject 称为“包装器”,因为在这个简化的示例中,这是它的唯一用途。

虽然Resources.Load 正确返回了 Wrapper,但嵌套的 Prefab 引用通常在第一次运行期间尚未加载,但在第二次运行后加载:

据我了解,这是一个执行顺序问题,其中有问题的 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
      }
    }
    
  • 在 Hierarchy 中创建一个空的“ExampleObject”游戏对象,然后将其保存为 Prefab,地址为 Assets/Resources/ExampleObject.prefab

  • Assets/Resources/Wrapper.asset创建一个ExampleWrapper的资产实例

    • 由于 Unity 5 不提供用于生成 ScriptableObjects 的 UI,因此请创建您的 own menu item,或使用 an automated solution。此问题假设您对 ScriptableObjects 足够熟悉,可以拥有自己喜欢的方法。
  • 将 Wrapper 资产的 Value 字段设置为 ExampleObject 预制件

  • 请注意,由于有时 unity 确实会正确缓存资产,


基本原理

这里的示例是故意简化的,但它是基于使用ScriptableObjects 为自定义系统存储/共享配置数据的真实项目。

不要回复以下内容:

  • “仅使用 Object.Instantiate - 不会改变结果,在某些情况下需要修改 Resources.Load 返回的原始对象。
  • “跳过包装器并直接引用预制件/手动加载它” - 虽然这绕过了加载问题,但它也错过了问题的重点。添加抽象级别可以使系统之间的资源共享更易于维护。此外,此问题不仅限于预制件(此处仅用于简单示例)。更现实的示例将包含多个嵌套对象,例如 Sprite、Materials、其他 ScriptableObjects 等。
  • “在静态构建期间不要加载” - Unity 支持静态系统(为什么还要提供[InitializeOnLoad]?),并且使用基于 ScriptableObjects 的资产来存储此类系统的配置信息是非常实际的用途-案子。在完全重新构建系统之前,我想看看其他潜在的替代方案。

正在寻找什么:

  • 当我在诸如此类的静态上下文中加载它时,我是否可以强制 Unity 预加载在包装器中序列化的资产,而不必通过其路径手动加载其内容?
  • 换句话说,我不想只运行Resources.Load&lt;GameObject&gt;("ExampleObject"),因为这首先会否定封装它的全部意义。我可以修改 ExampleWrapper 类,但任何潜在的解决方案都需要足够自动化,以便将预制件添加到检查器中的字段的工作流程就足够了。

编辑:还应该注意的是,奇怪的是,当我关闭项目并再次打开它时,我看到以下内容:

  • 在启动过程中,静态构造函数被调用一次,Wrapper被加载,嵌套的prefab实际加载正确。
  • 然后(仍在初始启动中,因为它发生在我可以输入任何操作之前)它再次静态构造,这一次加载 Wrapper 时,嵌套的预制件没有加载。

这个,我真的不明白。

【问题讨论】:

  • 首先,您第一次说Resources.Load 不起作用然后您说您甚至不想使用Resources.Load...您想听到什么作为答案?那你想用什么?
  • 我想在包装器上使用一次Resource.Load,而不是在每个包装的资源上手动调用Resource.Load,因为这破坏了将它们包装在单个包装器中的意义。请参阅 Galandil 的回答,了解我正在寻找的有用信息。

标签: c# unity3d unity5 unity3d-editor


【解决方案1】:

您的问题存在误解。

引用传递给Loader类,场景初始化完成后可以通过记录Wrapper.Value来检查。

很可能,问题出在(正如您所指出的)执行/序列化顺序中,显然它发生了这样的事情:

  • Loader 构造函数被调用,Wrapper 引用被正确传递。
  • Debug.Log(Wrapper.Value) 返回 null 因为脚本对象的字段还没有被序列化
  • Wrapper 的字段已序列化,现在记录 Wrapper.Value 可以正确显示 ExampleObject

因此,除非您计划在初始化期间对 Wrapper 字段进行“特殊”处理,否则您的代码中确实没有问题:我尝试在 OnEnableExampleWrapper 期间运行 Debug.Log(Loader.Wrapper.Value) ,我得到了正确的值。

关于您的编辑,显然它是“按设计”发生的,如本期所述:https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens

【讨论】:

  • 嗯,有道理。尚未加载任何序列化值,因此即使添加 public int Blah 并更改它也会在静态 ctr 中打印时产生默认值。那么这是否意味着就执行顺序而言,它是:Static Init -> Apply Serialized Data -> Reset -> 等等...?
  • 是的,确实我在ExampleWrapper 中使用int 进行了测试,它在开始时总是记录0。关于初始化期间的执行顺序,你的猜测和我的一样好,即我们无法确定在 Unity 的引擎盖下会发生什么以及何时发生。但可能static 构造函数确实是执行的第一件事(单原生优先于 Unity 管理?真的没有线索)。
  • 有道理,感谢您的反馈!自从您回答后,我发现的另一个相关资源是:blogs.unity3d.com/2016/06/06/… 这确实表明 Unity -really- 不希望用户在构造函数中加载资源。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-06
  • 1970-01-01
  • 2023-03-27
相关资源
最近更新 更多