C# 为什么未加载ScriptableObject资源中的嵌套资源引用?

C# 为什么未加载ScriptableObject资源中的嵌套资源引用?,c#,unity3d,unity5,unity3d-editor,C#,Unity3d,Unity5,Unity3d Editor,请阅读整个问题,并在发布答案之前运行示例 概述 在Unity 5.6.1中,当在静态编辑器脚本中加载嵌套资产时(在标记为的类的静态构造函数中也是如此),我遇到了一些不一致的行为 我正在用加载一个资产,ScriptableObject有一个对另一个资产资源的公共引用,让我们假设一个GameObject预置。从这一点上,我将ScriptableObject称为“包装器”,因为在这个简化的示例中,这是它的唯一用途 当Resources.Load正确返回包装器时,嵌套的预置引用通常在第一次运行期间尚未

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


概述 在Unity 5.6.1中,当在静态编辑器脚本中加载嵌套资产时(在标记为的类的静态构造函数中也是如此),我遇到了一些不一致的行为

我正在用加载一个资产,ScriptableObject有一个对另一个资产资源的公共引用,让我们假设一个GameObject预置。从这一点上,我将ScriptableObject称为“包装器”,因为在这个简化的示例中,这是它的唯一用途

Resources.Load
正确返回包装器时,嵌套的预置引用通常在第一次运行期间尚未加载,但在第二次运行之后加载:

据我所知,这是一个执行顺序问题,在静态构建过程中,相关的预制资源尚未加载,在后续运行中,它仍处于缓存状态

我假设在加载一个带有对另一个资产的序列化引用的资产时,默认情况下会自动加载嵌套资产,而不管这是否在静态初始化期间。然而,这里的情况似乎并非如此

包装器资产确实在其序列化数据中正确引用预置的证明(使用):

我也尝试过使用(至少在编辑器中是这样),但没有什么不同


示例项目 您可以下载,其中包含以下内容:

或复制如下:

  • 脚本:

    ExampleWrapper.cs:

    using UnityEngine;
    public class ExampleWrapper : ScriptableObject
    {
      public GameObject Value;
    }
    
    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
      }
    }
    
    StaticLoader.cs:

    using UnityEngine;
    public class ExampleWrapper : ScriptableObject
    {
      public GameObject Value;
    }
    
    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
      }
    }
    
    使用UnityEngine;
    #如果统一编辑器
    使用UnityEditor;
    [初始化加载]
    #恩迪夫
    公共类加载器
    {
    静态加载程序()
    {
    var Wrapper=Resources.Load(“Wrapper”);
    Debug.Log(Wrapper);//打印包装器ScriptableObject
    Debug.Log(Wrapper.Value);//打印包装好的游戏对象
    }
    }
    
  • 在层次结构中创建一个空的“ExampleObject”游戏对象,然后将其保存为
    Assets/Resources/ExampleObject.Prefact中的预置

  • Assets/Resources/Wrapper.asset

    • 由于Unity 5不提供用于生成ScriptableObjects的UI,因此请创建或使用。这个问题假设您对ScriptableObjects足够熟悉,有自己的首选方法
  • 将包装器资产的
    字段设置为ExampleObject预置

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


根本原因 这里的示例经过刻意简化,但它基于使用
ScriptableObjects
存储/共享自定义系统配置数据的实际项目

不要回答以下问题:

  • “仅使用”-不会改变结果,在某些情况下,修改
    资源返回的原始对象。加载
    是可取的
  • “跳过包装器,直接引用预置/手动加载”-虽然这绕过了加载问题,但也忽略了问题的重点。添加抽象级别可以使系统之间的资源共享更易于维护。此外,这个问题并不局限于预制件(此处仅用于简单示例)。更真实的示例将包含多个嵌套对象,例如精灵、材质、其他ScriptableObject等
  • “静态构造期间不加载”-Unity支持静态系统(为什么还要提供
    [InitializeOnLoad]
    ?),使用基于ScriptableObjects的资产存储此类系统的配置信息是一个非常真实的用例。在完全重新设计系统之前,我想看看其他可能的替代方案
我要寻找的是:

  • 在这样的静态上下文中加载时,我是否可以强制Unity预加载包装器中序列化的资产,而不必手动按路径加载其内容
  • 换句话说,我不想只运行
    Resources.Load(“ExampleObject”)
    ,因为这将否定封装它的全部意义。我可以修改
    ExampleWrapper
    类,但是任何可能的解决方案都需要足够的自动化,以便将预置添加到inspector中的字段的工作流就是所需的全部

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

  • 在启动过程中,静态构造函数被调用一次,包装器被加载,嵌套的预置实际上被正确加载
  • 然后(仍然在初始启动中,因为它发生在我可以输入任何操作之前)它会再次静态构造,而这次当包装器被加载时,嵌套的预置不会被加载

这一点,我真的不明白。

对你的问题有一种误解

引用被传递到
加载程序
类,在场景初始化完成后,您可以通过记录
Wrapper.Value
来检查它

最有可能的问题是(正如您所指出的)执行/序列化顺序,显然它是这样发生的:

  • 调用
    Loader
    构造函数,并正确传递
    Wrapper
    引用
  • Debug.Log(Wrapper.Value)
    返回
    null
    ,因为可脚本对象的字段尚未序列化
  • Wrapper
    的字段被序列化,现在记录
    Wrapper.Value
    正确显示
    ExampleObject
所以,除非你打算做些“特别”的事情