我正在制作自定义的EditorWindow。非常简化的版本如下所示:
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
&#34;资产/编辑/&#34;文件夹中。
它工作正常,但是......如果我在Unity
中添加任何C#脚本,写任何内容并保存它 - Unity
启动他的自动编译,我的自定义窗口变空,只是清除窗口
我应该怎么做以防止我的窗户清理?是否有可能要求Unity
在编译其他脚本时不重建窗口?或者我应该每次都保存数据并在重新编译后恢复它?这不是一个坏方法吗?
答案 0 :(得分:0)
在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中。