在Unity 3D编辑器中复制时,如何确保ScriptableObject是唯一的?

时间:2019-03-25 12:24:10

标签: c# unity3d

在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克隆ScriptableObject.Instantiate()引用-我的问题是,我不知道何时克隆。

我的问题是:

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

frankhermes使用简单的序列化数据类。对于单个类来说,这很好用,但是不幸的是,对于具有基类的类层次结构而言,这不是可行的,因为afaik在Unity序列化中不支持它们。

1 个答案:

答案 0 :(得分:0)

编辑:

---注意---

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

in the Unity forum找到了解决方案: 存储并检查GameObject附加到的MonoBehaviour的实例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);
            }
        }
    }

}