防止在编辑器重新编译时丢失 dll 中编辑器窗口的数据



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

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在编译其他脚本时不要重建窗口?还是我应该每次都保存数据并在重新编译后恢复它?这不是一个坏方法吗?

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

要通过程序集重新加载来保留更改,Unity 需要能够将数据(成员变量(保存到磁盘,然后在重新构建所有内容后重新填充它们。幸运的是,对于很多情况,这是自动完成的。EditorWindow基类已经标记为[Serializable],因此不需要也标记我们的子类。接下来,所有需要保存的变量也必须是可序列化的。字符串和 int 等简单数据类型是可序列化的,但某些类型(如 Rect(则不可序列化,这就是列表不会保存的原因,即使它是实例的一部分(非静态(。请参阅 Unity 手册 - 脚本序列化。请注意,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;
}

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

最新更新