【问题标题】:How to ensure ScriptableObjects are unique when copied in the Unity 3D editor?在 Unity 3D 编辑器中复制时,如何确保 ScriptableObjects 是唯一的?
【发布时间】:2019-03-25 12:24:10
【问题描述】:

在 Unity 3D 中,我有一个 MonoBehaviour,其中包含一个类派生列表,这些派生类都基于一个常见的 ScriptableObject。 该列表在自定义编辑器中填充和处理。这工作完美无缺,但有一个例外:每当我复制/粘贴我的MonoBehaviour,或在 Unity 编辑器中复制包含它的游戏对象时,我的列表只包含实例而不包含唯一的克隆。

这是一些示例代码(注意,这只是剥离了测试代码,我的实际类要复杂得多,需要一个单独的数据类):

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public abstract class MyAbstractBaseClass : ScriptableObject
{
    public abstract void foo();
}

public class MyTestScriptableObject : MyAbstractBaseClass
{
    public string stringMember;
    public override void foo()
    {
    }
}

public class MyTestMonoBehaviour : MonoBehaviour
{
    public List<MyAbstractBaseClass> testList;
}

[CustomEditor(typeof(MyTestMonoBehaviour))]
public class MyTestMonoBehaviourEditor : Editor
{
    const int NUM_LISTENTRIES = 5;

    public override void OnInspectorGUI()
    {
        SerializedProperty testListProp = serializedObject.FindProperty("testList");

        for (int i = 0; i < testListProp.arraySize; i++)
        {
            SerializedObject myTestScriptableObjectSO = new SerializedObject(testListProp.GetArrayElementAtIndex(i).objectReferenceValue);
            SerializedProperty stringMemberProp = myTestScriptableObjectSO.FindProperty("stringMember");
            EditorGUILayout.PropertyField(stringMemberProp);
            myTestScriptableObjectSO.ApplyModifiedProperties();
        }

        if( GUILayout.Button("Generate List"))
        {
            testListProp.arraySize = NUM_LISTENTRIES;
            for( int i=0; i<NUM_LISTENTRIES; i++)
                testListProp.GetArrayElementAtIndex(i).objectReferenceValue = ScriptableObject.CreateInstance<MyTestScriptableObject>();
        }
        serializedObject.ApplyModifiedProperties();
    }
}

就像我说的,除了引用/克隆问题之外,这段代码完美无缺。这意味着,当我更改游戏对象 A 上的字符串 1 时,复制的游戏对象 B 上的字符串 1 也会更改。 我当然可以很容易地用ScriptableObject.Instantiate() 克隆ScriptableObject 引用——我的问题是,我不知道何时克隆。

我的问题是:

  • 当我的MonoBehaviour 在编辑器中被复制(通过 C&P 或复制的游戏对象?)时,是否有任何回调/虚拟方法或类似方法告诉我?
  • 或者,在 C# 或 Unity-wise 中是否有任何类型的引用计数,它可以告诉我对象被引用的频率?由于它只是编辑器代码,因此使用反射的方法也可以。
  • 对于必须唯一的数据类的基础,ScriptableObject 是最佳选择,还是有其他选择?

frankhermes 已经在 cmets 中建议使用简单的序列化数据类。这适用于单个类,但不幸的是不适用于具有基类的类层次结构,因为 Unity 序列化不支持它们。

【问题讨论】:

  • 似乎可编写脚本的对象不是您用例的正确选择,为什么不在 MonoBehaviour 中使用字符串? ScriptableObjects 是用来重复使用和引用的吗?
  • 感谢您的评论。我发布的代码当然只是测试代码——实际的类比一个字符串成员复杂得多:一个基类和具有不同成员的多个派生类。这会使在 MonoBehaviour 本身中处理所有这些变得非常尴尬,所以我认为我需要一个单独的数据类。我也已经问过自己,是否可以替代使用 ScriptableObject 作为我的数据类的基础。我将据此扩展我的问题。
  • 您可以有一个单独的数据类,它不保存到可编写脚本的对象中,而是序列化到您的 Monobehaviour 实例中。然后,当您复制或复制 GameObject 而不是引用单独的 Scriptable Object 时,它将被克隆。
  • 嗯...必须考虑一下...谢谢
  • 现在试用了,不适用于类层次结构。据此调整了我的问题。

标签: c# unity3d


【解决方案1】:

编辑:

--- 注意---

此答案仅部分有效。会话之间的唯一实例 id 更改,这使得它对克隆检测和序列化无用:( https://answers.unity.com/questions/863084/does-getinstanceid-ever-change-on-an-object.html

.

in the Unity forum 找到解决方案: 存储和检查 GameObjectMonoBehaviour 附加到的实例 id 效果很好(请参阅 MakeListElementsUnique())。

唯一会失败的情况是复制MonoBehaviour 并将其粘贴到相同的GameObject。但这对我的用例和我认为的许多其他用例来说都不是真正的问题。

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public abstract class MyAbstractBaseClass : ScriptableObject
{
    public abstract void foo();
}

public class MyTestScriptableObject : MyAbstractBaseClass
{
    public string stringMember;
    public override void foo()
    {
    }
}

public class MyTestMonoBehaviour : MonoBehaviour
{
    public int instanceID = 0;
    public List<MyAbstractBaseClass> testList;
}

[CustomEditor(typeof(MyTestMonoBehaviour))]
public class MyTestMonoBehaviourEditor : Editor
{
    const int NUM_LISTENTRIES = 5;

    public override void OnInspectorGUI()
    {
        MyTestMonoBehaviour myScriptableObject = (MyTestMonoBehaviour)target;

        SerializedProperty testListProp = serializedObject.FindProperty("testList");

        MakeListElementsUnique(myScriptableObject, testListProp);

        for (int i = 0; i < testListProp.arraySize; i++)
        {

            SerializedObject myTestScriptableObjectSO = new SerializedObject(testListProp.GetArrayElementAtIndex(i).objectReferenceValue);
            SerializedProperty stringMemberProp = myTestScriptableObjectSO.FindProperty("stringMember");
            EditorGUILayout.PropertyField(stringMemberProp);
            myTestScriptableObjectSO.ApplyModifiedProperties();
        }

        if (GUILayout.Button("Generate List"))
        {
            testListProp.arraySize = NUM_LISTENTRIES;
            for (int i = 0; i < NUM_LISTENTRIES; i++)
                testListProp.GetArrayElementAtIndex(i).objectReferenceValue = ScriptableObject.CreateInstance<MyTestScriptableObject>();
        }
        serializedObject.ApplyModifiedProperties();
    }

    private void MakeListElementsUnique( MyTestMonoBehaviour scriptableObject, SerializedProperty testListProp )
    {
        SerializedProperty instanceIdProp = serializedObject.FindProperty("instanceID");
        // stored instance id == 0: freshly created, just set the instance id of the game object
        if (instanceIdProp.intValue == 0)
        {
            instanceIdProp.intValue = scriptableObject.gameObject.GetInstanceID();
        }
        // stored instance id != current instance id: copied!
        else if (instanceIdProp.intValue != scriptableObject.gameObject.GetInstanceID())
        {
            // don't forget to change the instance id to the new game object
            instanceIdProp.intValue = scriptableObject.gameObject.GetInstanceID();
            // make clones of all list elements
            for (int i = 0; i < testListProp.arraySize; i++)
            {
                SerializedProperty sp = testListProp.GetArrayElementAtIndex(i);
                sp.objectReferenceValue = Object.Instantiate(sp.objectReferenceValue);
            }
        }
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 2011-08-02
    • 2016-12-11
    • 2022-11-01
    • 2023-02-26
    相关资源
    最近更新 更多