简体   繁体   中英

Unity references break after scene reload

I'm facing a weird issue in unity, the references keep breaking as soon as i reload a scene, i tried to understand what is really going on but no luck. i made a script to replicate the issue you can find bellow.

when i edit the last data element "list" by changing the size, the change is reflected on the other data objects list, cause they are treated just as references.
在此处输入图片说明 .

if i reload the scene, the changes are not reflected anymore as previously, it behaves like copies instead of references this time.
can someone help me figure out what is going on ?
在此处输入图片说明 .

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

In general: Don't directly access and change the values of your MonoBehaviour instance!

As you noted you will have to handle all kind of marking dirty and saving yourself. What you experience when re-opening the scene in the Editor that something wasn't marked dirty correctly and thus not saved together with the scene.


Always rather go through SerializedProperty s which handle all the marking dirty and saving and especially also undo/redo etc. automatically:

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

This now handles all marking dirty, saving the scene correctly, undo/redo etc automatically and you don't have to take care about that anymore.


And then a little "pro" tip: Use ReorderableList ! It looks a bit tricky to set up but is extremly powerful: Among other things as the name says it allows you to simply reorder the elements by drag and drop in the Inspector and also allows to delete an item from the middle which is impossible with the normal list drawer. This completely replaces your Add and Clear buttons:

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

在此处输入图片说明


Note If not the case already anyway: The testEditor part should

  • 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

otherwise you will get errors when building the app since the UnityEditor namespace is stripped of in a build and exists only inside the Unity Editor itself.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM