尝试创建嵌套的 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
但出现以下错误消息:
- UnityException:将资产添加到 object 失败。
- AddAssetToSameFile 失败,因为其他资产不持久
观察:容器创建成功。未创建 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
,还要使用 OnValidate
和 Awake
,以便在有人通过检查员。在那种情况下,我会简单地检查此资产中是否已存在子项,以免重新创建它。
另请注意: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
}
目标: 从项目视图创建嵌套的可编写脚本的 object。
预期:当从项目视图创建容器可编写脚本object的实例时, child 脚本化 object 已创建并附加到容器资产。容器还应保留 child.
的引用实际: 当我尝试将 child 附加到容器资产时,它失败了。我使用 AssetDatabase.AddObjectToAsset
但出现以下错误消息:
- UnityException:将资产添加到 object 失败。
- AddAssetToSameFile 失败,因为其他资产不持久
观察:容器创建成功。未创建 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
,还要使用 OnValidate
和 Awake
,以便在有人通过检查员。在那种情况下,我会简单地检查此资产中是否已存在子项,以免重新创建它。
另请注意: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
}