【问题标题】:Unity references break after scene reload场景重新加载后统一引用中断
【发布时间】:2020-05-18 17:16:06
【问题描述】:

我在统一方面遇到了一个奇怪的问题,一旦我重新加载场景,引用就会不断中断,我试图了解真正发生了什么但没有运气。 我制作了一个脚本来复制您可以在下面找到的问题。

当我通过更改大小来编辑最后一个数据元素“列表”时,更改会反映在其他数据对象列表中,因为它们被视为引用。

如果我重新加载场景,更改将不再像以前那样反映,这次它的行为就像副本而不是引用。
有人可以帮我弄清楚发生了什么吗?

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);
        }
    }
}

【问题讨论】:

  • 尝试在 Start of Awake 函数中添加 DontDestroyOnLoad(this.gameObject);
  • 嗨,这是一个编辑器插件问题,我发现了这个问题,我在下面的评论中提到了这个问题

标签: c# unity3d


【解决方案1】:

一般情况下:不要直接访问和更改 MonoBehaviour 实例的值!

正如您所指出的,您将不得不处理各种脏标记并自救。您在编辑器中重新打开场景时遇到的情况是,某些内容未正确标记为脏,因此未与场景一起保存。


总是通过SerializedPropertys 处理所有标记脏和保存,尤其是自动撤消/重做等:

[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!它的设置看起来有点棘手,但功能非常强大:除此之外,顾名思义,它允许您通过在 Inspector 中拖放来简单地重新排序元素,还允许从中间删除一个项目,这是不可能的普通列表抽屉。这完全取代了您的 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();
    }
}


注意如果不是这样的话: testEditor 部分应该

  • 要么放在名为Editor 的文件夹中的不同脚本中
  • 或者您应该将与 UnityEditor 命名空间相关的任何内容包装在预处理器中,例如

    #if UNITY_EDITOR
    using UnityEditor;
    using UnityEditorInternal;
    #endif
    
    ...
    
    #if UNITY_EDITOR
    [CustomEditor(typeof(test))]
    public class testEditor : Editor
    {
        ...
    }
    #endif
    

否则在构建应用程序时会出错,因为 UnityEditor 命名空间在构建中被剥离并且仅存在于 Unity 编辑器本身中。

【讨论】:

  • 嗨,感谢您抽出宝贵的时间,以及关于使用可重新排序列表和序列化属性的建议,事实上我一直在使用它们,但在我上面的示例中,我只是想复制这个问题,它看起来就像我发现了这一切,我上面写的代码是正确的,它看起来像问题是Unity没有序列化文档here中提到的自定义类,当unity进行反序列化,它会创建类对象的副本,而不是再次链接引用,
  • @BadrDouah 没有,因为您可以在检查器中看到该类,因此序列化有效...不起作用的是您的脚本中关于将内容标记为脏的内容,因此它没有正确保存到场景,而是保持一个临时值,直到您重新打开场景。因此如前所述:不要直接写值,而是使用 SerializedProperty,这是编写编辑器脚本的正确方法
  • 嗨,您是否阅读了我在上面发布的统一文档链接的这一部分。 “对于不是从 UnityEngine.Object 派生的自定义类,Unity 通过值将它们内联序列化,类似于它序列化结构的方式。如果您将对自定义类实例的引用存储在几个不同的字段中,它们在序列化时将成为单独的对象. 然后,当 Unity 反序列化字段时,它们包含具有相同数据的不同不同对象。”
猜你喜欢
  • 1970-01-01
  • 2022-11-15
  • 1970-01-01
  • 1970-01-01
  • 2013-11-15
  • 1970-01-01
  • 2021-05-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多