场景重新加载后Unity引用断开

3

我在unity中遇到了一个奇怪的问题,每次重新加载场景时,引用都会中断。我尝试理解发生了什么,但没有成功。
我制作了一个脚本来复制这个问题,你可以在下面找到。

当我通过更改最后一个数据元素“list”的大小来编辑它时,其他数据对象的列表也会反映出变化,因为它们只被视为引用。
enter image description here

如果我重新加载场景,之前的更改就不再反映出来了,这次它表现得像是副本而不是引用。
有人能帮我弄清楚发生了什么吗?
enter image description here

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

public class test : MonoBehaviour
{

    public List<data> Data = new List<data>();
}
[System.Serializable]
public class data
{

    public List<int> list = new List<int>();
}
[CustomEditor(typeof(test))]
public class testEditor:Editor
{
    test test;
    public void OnEnable()
    {
        test = (test)target;
    }
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        if (GUILayout.Button("Add"))
        {
            data data = new data();
            if (test.Data.Count >= 1) data.list = test.Data[test.Data.Count - 1].list;
            test.Data.Add(data);

            EditorUtility.SetDirty(test);
        }
        if (GUILayout.Button("Clear"))
        {
            test.Data.Clear();

            EditorUtility.SetDirty(test);
        }
    }
}

1
尝试在你的Start或Awake函数中添加DontDestroyOnLoad(this.gameObject); - Ghost The Punisher
嗨,这是一个编辑器插件的问题,我已经找到了问题,并在下面的评论中提到了它。谢谢! - Badr Douah
1个回答

2
通常情况下:不要直接访问和更改您的MonoBehaviour实例的值!
正如您所指出的,您将不得不处理各种标记脏和保存自己的任务。当您在编辑器中重新打开场景时,您会遇到一些未正确标记为脏的问题,因此未与场景一起保存。
始终要通过处理所有标记脏、保存以及撤销/重做等操作的SerializedProperty来完成,这样会更好。
[CustomEditor(typeof(test))]
public class testEditor : Editor
{
    private SerializedProperty Data;

    public void OnEnable()
    {
        Data = serializedObject.FindProperty(nameof(test.Data));
    }

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        // load all current values of the properties in test into the SerializedProperty "clones"
        serializedObject.Update();

        if (GUILayout.Button("Add"))
        {
            // this simply adds a new entry to the list
            // since the data and list are both serializable this already initializes them with values
            Data.arraySize++;


            // Actually the entire following block is redundant 
            // by using Data.arraySize++; the new added entry automatically 
            // is a full copy of the entry before!
            // I just decided to add it as example how you would access further nested SerializedProperties

            //// if there was an element before now there are two
            //if (Data.arraySize >= 2)
            //{
            //    // get the last added element
            //    var lastElement = Data.GetArrayElementAtIndex(Data.arraySize - 1);
            //    var beforeElement = Data.GetArrayElementAtIndex(Data.arraySize - 2);

            //    // deep clone the list
            //    var lastElementList = lastElement.FindPropertyRelative(nameof(data.list));
            //    var beforeElementList = beforeElement.FindPropertyRelative(nameof(data.list));

            //    lastElementList.arraySize = beforeElementList.arraySize;
            //    for (var i = 0; i < lastElementList.arraySize; i++)
            //    {
            //        lastElementList.GetArrayElementAtIndex(i).intValue = beforeElementList.GetArrayElementAtIndex(i).intValue;
            //    }
            //}
        }

        if (GUILayout.Button("Clear"))
        {
            Data.arraySize = 0;
        }

        // write back the values of the SerializedProperty "clones" into the real properties of test
        serializedObject.ApplyModifiedProperties();
    }
}

现在,所有的标记脏数据、正确保存场景、自动撤销/重做等操作都已经处理好了,你不再需要再去关心这些了。
然后是一个小的专业提示:使用ReorderableList!它看起来有点棘手设置,但非常强大:正如其名称所示,它允许您在检查器中通过拖放来轻松重新排序元素,并且还允许删除列表中间的项目,这是普通列表抽屉不可能做到的。这完全替代了您的AddClear按钮:
using UnityEditor;
using UnityEditorInternal;

[CustomEditor(typeof(test))]
public class testEditor : Editor
{
    private SerializedProperty Data;
    private ReorderableList dataList;

    public void OnEnable()
    {
        Data = serializedObject.FindProperty(nameof(test.Data));

        //                                 should the list
        //                                                     | be reorderable by drag&drop of the entries?
        //                                                     |     | display a header for the list?
        //                                                     |     |     | have an Add button?
        //                                                     |     |     |     | have a Remove button?
        //                                                     v     v     v     v
        dataList = new ReorderableList(serializedObject, Data, true, true, true, true)
        {
            // what shall be displayed as header
            drawHeaderCallback = rect => EditorGUI.LabelField(rect, Data.displayName),

            elementHeightCallback = index =>
            {
                var element = Data.GetArrayElementAtIndex(index);
                var elementList = element.FindPropertyRelative(nameof(data.list));
                return EditorGUIUtility.singleLineHeight * (elementList.isExpanded ? elementList.arraySize + 4 : 3);
            },

            drawElementCallback = (rect, index, isFocused, isActive) =>
            {
                var element = Data.GetArrayElementAtIndex(index);

                EditorGUI.LabelField(new Rect(rect.x,rect.y,rect.width,EditorGUIUtility.singleLineHeight), element.displayName);
                // in order to print the list in the next line
                rect.y += EditorGUIUtility.singleLineHeight;

                var elementList = element.FindPropertyRelative(nameof(data.list));
                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width,  EditorGUIUtility.singleLineHeight * (elementList.isExpanded ? elementList.arraySize + 1 : 1)), elementList, true);
            }
        };
    }

    public override void OnInspectorGUI()
    {
        // load all current values of the properties in test into the SerializedProperty "clones"
        serializedObject.Update();

        dataList.DoLayoutList();

        // write back the values of the SerializedProperty "clones" into the real properties of test
        serializedObject.ApplyModifiedProperties();
    }
}

enter image description here


注意:如果尚未如此,testEditor 部分应该
  • either be placed in a different script in a folder called Editor
  • or you should wrap anything related to the UnityEditor namespace within pre-processors like

    #if UNITY_EDITOR
    using UnityEditor;
    using UnityEditorInternal;
    #endif
    
    ...
    
    #if UNITY_EDITOR
    [CustomEditor(typeof(test))]
    public class testEditor : Editor
    {
        ...
    }
    #endif
    
否则,在构建应用程序时会出现错误,因为UnityEditor命名空间在构建中被剥离,仅存在于Unity编辑器本身中。

嗨,感谢你花时间并给出使用可重新排序列表和序列化属性的建议。事实上,我经常使用它们,但在我上面提到的示例中,我只是想复制问题,并且似乎找到了问题所在。我上面编写的代码是正确的,问题似乎是Unity不像文档这里中所述那样对自定义类进行序列化。当Unity进行反序列化时,它会创建类对象的副本,而不是重新链接引用。 - Badr Douah
@BadrDouah 不,正如我所说的,序列化是有效的,因为你可以在检视器中看到该类...不起作用的是你脚本中关于标记物体为脏的部分,因此它没有正确保存到场景中,而是保持为临时值,直到重新打开场景。因此,如我所说:不要直接写入值,而是使用SerializedProperty,这是编写编辑器脚本的正确方式。 - derHugo
嗨,你有没有看过我上面发布的Unity文档链接中的这一部分?"对于不是从UnityEngine.Object派生的自定义类,Unity会按值进行内联序列化,类似于它对结构体的序列化方式。如果你在多个不同的字段中存储了一个自定义类的实例引用,在序列化时它们将变成独立的对象。然后,当Unity反序列化这些字段时,它们包含了具有相同数据的不同独立对象。" - Badr Douah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接