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

时间:2017-06-18 12:14:49

标签: c# unity3d unity5 unity3d-editor

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

概述

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

我正在使用ScriptableObject加载Resources.Load资产,并且ScriptableObject具有对另一资产资源的公开引用,我们假设一个GameObject Prefab。从这一点开始,我将ScriptableObject称为' Wrapper',因为在这个简化的例子中,它是它所服务的唯一目的。

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

Screencap showing that the prefab is not loaded on the first run, but is on the second

根据我的理解,这是一个执行问题的顺序,其中有问题的Prefab资源在静态构建期间尚未加载,并且在后续运行中它仍然被缓存。

我假设在加载具有对另一个资产的序列化引用的资产时,默认情况下将自动加载嵌套资产,无论这是否在静态init期间。然而,这似乎并非如此。

证明Wrapper资产确实在其序列化数据中正确引用了预制件(使用Asset Serialization set to Force Text): Proof that prefab reference is correctly serialized

我也尝试使用AssetDAtabase.LoadAssetAtPath(至少在编辑器中),这没有什么区别。

示例项目

您可以下载UnityPackage here,其中包含以下内容:

enter image description 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
      }
    }
    
  • 创建一个空的&#34; ExampleObject&#34;层次结构中的GameObject,然后将其另存为预制件Assets/Resources/ExampleObject.prefab

  • Assets/Resources/Wrapper.asset

    创建ExampleWrapper的资产实例
    • 由于Unity 5不提供用于生成ScriptableObjects的UI,因此请创建own menu item或使用an automated solution。这个问题假设您已经熟悉ScriptableObjects以拥有自己喜欢的方法。
  • 将Wrapper资产的Value字段设置为ExampleObject预制件 enter image description here

  • 请注意,因为有时Unity会正确缓存资产,

原理

此处的示例是有意简化的,但它基于使用ScriptableObjects存储/共享自定义系统配置数据的实际项目。

不要回复以下内容:

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

寻找:

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

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

enter image description here

  • 在启动期间,静态构造函数被调用一次,Wrapper被加载,并且嵌套的预制件 实际上已正确加载。
  • 然后(仍然在初始启动时,因为它发生在我可以输入任何操作之前)它再次静态构造,而这次加载Wrapper时,嵌套的预制件不会加载。 / LI>

这个,我真的不明白。

1 个答案:

答案 0 :(得分:3)

对你的问题存在误解。

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

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

  • 调用Loader构造函数,并正确传递Wrapper引用。
  • Debug.Log(Wrapper.Value)返回null,因为脚本化对象的字段尚未序列化
  • Wrapper的字段已序列化,现在正在显示Wrapper.Value正确显示ExampleObject

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

关于您的修改,显然它是“按设计”发生的,正如本期中明确说明的那样:https://issuetracker.unity3d.com/issues/unityeditor-dot-initializeonload-calls-the-constructor-twice-when-the-editor-opens