【问题标题】:Prevent losing data for EditorWindow in dll when Editor recompiles防止在编辑器重新编译时丢失 dll 中 EditorWindow 的数据
【发布时间】:2018-06-13 05:12:56
【问题描述】:

我正在制作一个自定义编辑器窗口。非常简化的版本如下所示:

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

namespace testDllForUnity {
    [Serializable]
    public class WindowTestDllForUnity : EditorWindow {
        string myString = "Hello World";
        private static float x = 0;
        private static float y = 0;        
        private static int yOffset = 10;
        private static float width = 100f;
        private static float height = 40f;

        private static List<Rect> buttonsRectList = new List<Rect>();

        [MenuItem("Window/WindowTestDllForUnity")]
        static void Init() {            
            WindowTestDllForUnity window = (WindowTestDllForUnity)EditorWindow.GetWindow(typeof(WindowTestDllForUnity));
            buttonsRectList.Add(new Rect(x, y, width, height));

            window.Show();
        }


        void OnGUI() {
            GUILayout.Label(myString, EditorStyles.boldLabel);
            if (GUILayout.Button("AddButton")) {
                y += height + yOffset;                
                buttonsRectList.Add(new Rect(x, y, width, height));
            }

            for (int i = 0; i < buttonsRectList.Count; i++) {
                if (GUI.Button(new Rect(buttonsRectList[i]), "button_" + i))
                    Debug.Log("button_" + i + " clicked!");                
            }
        }
    }
}

其实我的窗口比这更复杂。

我用 VisualStudio 将这段代码构建为 dll 并放入 Unity 的“Assets/Editor/”文件夹中。

它工作正常,但是...如果我在 Unity 中添加任何 C# 脚本,写任何东西并保存它 - Unity 启动他的自动编译,我的自定义窗口变为空,只是清除窗口。

我应该怎么做才能防止我的窗口被清除?是否可以要求Unity 在编译其他脚本时不要重建窗口?或者我应该每次都保存数据并在重新编译后恢复它?这不是一个坏方法吗?

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    当您在 Unity 中添加、更改或删除脚本(或 DLL)时,所有受影响的程序集都会重新编译和重新加载。当您进入和退出播放模式时,程序集也会重新加载。这会导致所有静态变量都被清除,因为它们属于加载的程序集并且不会保存在其他任何地方。

    要通过程序集重新加载来保持更改,Unity 需要能够将数据(您的成员变量)保存到磁盘,然后在重建所有内容后重新填充它们。幸运的是,在很多情况下,这是自动完成的。 EditorWindow 基类已经被标记为[Serializable],所以不需要也标记我们的子类。接下来,所有需要保存的变量也必须是可序列化的。简单的数据类型(如 string 和 int)是可序列化的,但某些类型(如 Rect)不是,这就是您的列表不会保存的原因,即使它是实例的一部分(不是静态的)。请参阅Unity Manual - Script Serialization。请注意,EditorWindow 类的规则似乎与MonoBehaviour 类的序列化略有不同。例如,私有变量在没有[SerializeField] 属性的情况下被序列化,至少在我的 Unity 版本 2018.1.0f2 中是这样。

    尝试以下代码以获得更好的理解:

    using UnityEditor;
    using UnityEngine;
    using System.Collections.Generic;
    
    namespace PersistChangesThroughAssemblyLoad
    {
        public class MyEditorWindow : EditorWindow
        {
            Vector2 buttonStartPosition = new Vector2(0, 0);
            Vector2 buttonStartSize = new Vector2(100f, 40f);
            int yOffset = 10;
    
            // All data types which should be peristent, need to be
            // serializable, Vector2 is, but Rect is not.
            List<Vector2> buttonPositions = new List<Vector2>();
            List<Vector2> buttonSizes = new List<Vector2>();
    
            [MenuItem("Window/MyEditorWindow")]
            static void Init()
            {
                MyEditorWindow window = GetWindow<MyEditorWindow>("MyWindow");
                LogMessage(window, "Window will be opened via the menu item.");
                window.Show();
            }
    
            void OnEnable()
            {
                LogMessage("Window is enabled (either after opening or after assembly reload/recompile).");
            }
    
            void OnDisable()
            {
                LogMessage("Window is disabled (either when being closed or because the assembly is about to reload/recompile).");
            }
    
            void OnGUI()
            {
                if (GUILayout.Button("Add Button"))
                {
                    buttonStartPosition.y += buttonStartSize.y + yOffset;
                    AddNewButton(buttonStartPosition, buttonStartSize);
                }
    
                for (int i = 0; i < buttonPositions.Count; i++)
                {
                    string buttonName = "Button " + i;
                    if (GUI.Button(new Rect(buttonPositions[i], buttonSizes[i]), buttonName))
                    {
                        LogMessage(buttonName + " was clicked!");
                    }
                }
            }
    
            void AddNewButton(Vector2 position, Vector2 size)
            {
                buttonPositions.Add(position);
                buttonSizes.Add(size);
                LogMessage("Added new button. Total count: " + buttonPositions.Count);
            }
    
            void LogMessage(string message)
            {
                LogMessage(this, message);
            }
    
            static void LogMessage(Object context, string message)
            {
                Debug.Log("Window [" + context.GetInstanceID() + "]: " + message);
            }
        }
    }
    

    注意控制台中记录的实例ID。从菜单创建新窗口时,id 会发生变化,但如果窗口是可序列化的,它会在播放模式下存活。这种方法应该会让您走得更远,因为大多数数据都可以分解为简单的数据类型。创建自定义结构并使用[Serializable] 属性标记它们也很方便,如下所示:

    [System.Serializable]
    public struct MyRect
    {
        public float x, y, width, height;
    }
    

    除此之外,可以在 OnDisable 中将数据写入EditorPrefs 并在 OnEnable 中加载。

    【讨论】:

    • 在我的示例中,列表是静态的,这就是它不能被序列化的原因。但非静态列表 - 可以))正如我所写:我的应用程序比示例更复杂,所以我似乎必须重新检查我的所有代码)感谢您提供有用和有用的信息。它让我明白了在哪里看。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2014-10-21
    • 1970-01-01
    • 2021-07-07
    • 2021-12-19
    • 1970-01-01
    • 2011-03-18
    相关资源
    最近更新 更多