尝试创建嵌套的 ScriptableObject:"AddAssetToSameFile failed because the other asset is not persistent"

Trying to create nested ScriptableObject: "AddAssetToSameFile failed because the other asset is not persistent"

目标: 从项目视图创建嵌套的可编写脚本的 object。

预期:当从项目视图创建容器可编写脚本object的实例时, child 脚本化 object 已创建并附加到容器资产。容器还应保留 child.

的引用

实际: 当我尝试将 child 附加到容器资产时,它失败了。我使用 AssetDatabase.AddObjectToAsset 但出现以下错误消息:

观察:容器创建成功。未创建 child 资产。一旦创建资产,检查器就会显示 child 引用,但在输入容器名称时会显示 Type mismatch

child object 不是持久化的。我不知道 persistent 在这种情况下是什么意思。我想这可能是我不明白这个问题的原因。

以下是我要实现的简化版本的代码。重现了同样的错误。

容器class

[CreateAssetMenu]
public class Container : ScriptableObject
{
    [SerializeField] private Child child;
        
    private void Reset()
    {
        // Create new child
        child = ScriptableObject.CreateInstance<Child>();

        // Attach child to the container
        AssetDatabase.AddObjectToAsset(child, this); // This line throws exception!

        // Save changes
        AssetDatabase.SaveAssets();
    }
}

Child class

public class Child : ScriptableObject
{
    [SerializeField] public string myString;
}

问题是,在您输入名称之前,新创建的 scriptableObject 尚未持久化。如果您点击 Escape 则它永远不会被创建 ;)

您可以做的是延迟子创建,直到实际创建资产。为了检查这一点,您可以使用 AssetDatabase.Contains

注意:我建议不仅要依赖 Reset,还要使用 OnValidateAwake,以便在有人通过检查员。在那种情况下,我会简单地检查此资产中是否已存在子项,以免重新创建它。

另请注意:UnityEditor 在构建中被完全删除!

=> 如果这意味着用于 Unity 编辑器本身之外的运行时应用程序,请确保将与 UnityEditor 相关的所有内容包装在 pre-processor 标签中

#if UNITY_EDITOR
any code related to UnityEditor namespace
#endif

所以我会做一些事情,例如

using System;
using System.Linq;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[CreateAssetMenu]
public class Container : ScriptableObject
{
    [SerializeField]
    private Child child;

    #if UNITY_EDITOR
    private void Awake()
    {
        Init();
    }

    private void OnValidate()
    {
        Init();
    }

    private void Reset()
    {
        Init();
    }

    private void OnDestroy()
    {
        EditorApplication.update -= DelayedInit;
    }

    private void Init()
    {
        // If child is already set -> nothing to do
        if (child)
        {
            return;
        }

        // If this asset already exists initialize immediately
        if (AssetDatabase.Contains(this))
        {
            DelayedInit();
        }
        // otherwise attach a callback to the editor update to re-check repeatedly until it exists
        // this means it is currently being created an the name has not been confirmed yet
        else
        {
            EditorApplication.update -= DelayedInit;
            EditorApplication.update += DelayedInit;
        }
    }

    private void DelayedInit()
    {
        // if this asset dos still not exist do nothing
        // this means it is currently being created and the name not confirmed yet
        if (!AssetDatabase.Contains(this))
        {
            return;
        }

        // as soon as the asset exists remove the callback as we don't need it anymore
        EditorApplication.update -= DelayedInit;

        // first try to find existing child within all assets contained in this asset
        var assets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(this));
        // you could as well use a loop but this Linq query is a shortcut for finding the first sub asset
        // of type "Child" or "null" if there was none
        child = assets.FirstOrDefault(a => a.GetType() == typeof(Child)) as Child;

        // did we find a child ?
        if (!child)
        {
            // If not create a new child
            child = CreateInstance<Child>();
            // just for convenience I'd always give assets a meaningful name
            child.name = name + "_Child";
            
            // Attach child to the container
            AssetDatabase.AddObjectToAsset(child, this);
        }
        
        // Mark this asset as dirty so it is correctly saved in case we just changed the "child" field
        // without using the "AddObjectToAsset" (which afaik does this automatically)
        EditorUtility.SetDirty(this);

        // Save all changes
        AssetDatabase.SaveAssets();
    }
    #endif
}