细说EditorWindow

发布时间:2023-05-22

一、EditorWindow是什么?

EditorWindow是Unity引擎中的一个可自定义弹出窗口,可以用来作为拓展编辑器界面的工具。在Window菜单下可以找到自定义的EditorWindow。 相对于Inspector面板,EditorWindow提供了更高自由度的自定义操作。一个EditorWindow可以用来做各种各样的编辑器任务,包括显示自定义UI、手动让用户输入信息以及执行多步骤的编辑和操作。 下面是一个快速的EditorWindow例子。

using UnityEngine;
using UnityEditor;
public class ExampleEditorWindow : EditorWindow
{
    string myString = "Hello, World!";
    [MenuItem("Window/Example")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow<ExampleEditorWindow>("Example");
    }
    private void OnGUI()
    {
        GUILayout.Label("This is an example EditorWindow", EditorStyles.boldLabel);
        myString = EditorGUILayout.TextField("Text Field", myString);
        if (GUILayout.Button("Say Hello"))
        {
            Debug.Log(myString);
        }
    }
}

二、EditorWindow分类

在Unity中,通常情况下,可以根据窗口的打开方式对EditorWindow进行分类。

1. 手动打开窗口

手动打开窗口方式是就是通过Unity Editor菜单中的Window选项进行打开。通过在EditorWindow脚本中添加MenuItem特性,可以将自己构建的EditorWindow添加到Window菜单中。示例代码中,通过添加MenuItem特性将ExampleEditorWindow添加到Window菜单中。

[MenuItem("Window/Example")]
public static void ShowWindow()
{
    EditorWindow.GetWindow<ExampleEditorWindow>("Example");
}

2. 在特定场合打开窗口

除以上的手动打开窗口之外,还有一些特定场合下会自动打开窗口。例如通过组合键打开FirstPassProfiler窗口,或者是添加此类代码:

[UnityEditor.Callbacks.OnOpenAssetAttribute(1)]
public static bool OpenAsset(int instanceID, int line)
{
    Object obj = EditorUtility.InstanceIDToObject(instanceID);
    if (instanceID != 0 && obj is GameObject)
    {
        var window = EditorWindow.GetWindow(typeof(ExampleEditorWindow));
        window.Show();
        return true;
    }
    return false; // let the system open the asset
}

三、常用EditorWindow API

1. public static T GetWindow(string title, [Type type], [bool utility]);

表示根据类型T或者类型type来查找已经打开的EditorWindow实例,如果没有找到就创建一个新的并返回。

ExampleEditorWindow window = (ExampleEditorWindow)EditorWindow.GetWindow(typeof(ExampleEditorWindow));

2. public void Focus();

表示将窗口设置为焦点,在用户输入时将不会和Inspector或其他窗口争夺焦点。

void OnEnable() {
    EditorWindow.GetWindow<ExampleEditorWindow>().Focus();
}

3. public object GetService(Type serviceType);

获取EditorWindow所依赖的接口或对象。

public void OnGUI()
{
    if (Selection.activeObject)
        assetPath = AssetDatabase.GetAssetPath(Selection.activeObject.GetInstanceID());
    else
        assetPath = "";
    EditorGUI.TextField(new Rect(3, 3, position.width - 6, 20), "Selected Asset:", assetPath);
    IPreviewWrapper preview = (IPreviewWrapper)this.GetService(typeof(IPreviewWrapper));
}

4. public void ShowUtility();

表示打开一个工具窗口,不会在任何面板标签之间显示,并且不会在场景视图上出现背景遮罩。

[MenuItem("Window/Example")]
static void Open()
{
    ExampleWindow window = CreateInstance<ExampleWindow>();
    window.ShowUtility();
}

5. 处理Event事件

在EditorWindow子类中,可以通过继承于OnGUI函数的Event类,实现处理一些常用Event事件,例如键盘事件和鼠标移动事件。

void OnGUI()
{
    if (Event.current.type == EventType.KeyDown)
    {
        Debug.Log("Detected key code: " + Event.current.keyCode);
    }
}

四、常见UI控件使用

EditorWindow中使用一些常见的UI控件方式和在普通的GUI界面中几乎一样,主要是利用EditorGUILayout函数、GUILayout函数和GUILayoutOption类作为参数来构建UI。

1. GUILayout.Label(string text, params GUILayoutOption[] options);

表示创建一个标签。

void OnGUI()
{
    GUILayout.Label("这是一个标签", GUILayout.Width(100));
}

2. GUILayout.Button(Texture image, [style], [options]);

创建一个按钮。

void OnGUI()
{
    if (GUILayout.Button("按钮"))
        Debug.Log("按钮被点击");
}

3. GUILayout.TextField(string text, [style], [options]);

创建一个可编辑的单行文本控件。

void OnGUI()
{
    myString = GUILayout.TextField(myString, 25);
}

4. GUILayout.TextArea(string text, [options]);

创建一个可编辑的多行文本控件。

void OnGUI()
{
    myString = EditorGUILayout.TextArea(myString);
}

5. GUILayout.HorizontalSlider(float value, float leftValue, float rightValue, [style], [slider], [thumb], [options]);

创建一个水平的滑动条。

void OnGUI()
{
    sliderValue = GUILayout.HorizontalSlider(sliderValue, 0.0f, 10.0f);
}

五、EditorWindow Best Practices

1. 记得手动释放EditorWindow对象和资源

EditorWindow需要手动释放对象和资源,虽然在EditorWindow关闭时,资源和内存会被自动清理,但是最好手动释放防止内存泄漏。

void OnDestroy(){
    if (textureX && !EditorUtility.IsPersistent(textureX))
        DestroyImmediate(textureX, false);
}

2. 需要并发运行的编写

由于EditorWindow可能会被并发显示,因此,对于全局数据或变量,需要加锁处理,应该注意防止资源竞争和线程安全!

static void ButtonCallBack() {
    Debug.Log("Button " + TextB);
}
private void OnGUI() {
    GUILayout.Label("Enter Text : ", EditorStyles.boldLabel);
    TextA = GUILayout.TextField(TextA);
    TextB = GUILayout.TextField(TextB);
    if(GUILayout.Button("Get Text From button")) {
        showText = TextA;
        EditorApplication.delayCall += ButtonCallBack;
    }
    GUILayout.Label("Show Text : "+showText, EditorStyles.boldLabel);
}

3. 对空引用的判断

在EditorWindow中,如果需要引用Unity Editor中的一个GameObject,可以在OnDestroy函数中判断空引用并进行释放。

private void OnDestroy()
{
    if (m_mesh != null)
        GameObject.DestroyImmediate(m_mesh);
}

六、结语

本文从EditorWindow基本介绍,EditorWindow分类,常用API,常见UI控件及其最佳实践进行了详细分析。EditorWindow的扩展性给编程带来了全新的广阔空间和发展方向,可以用其优秀的自由度、灵活性和多样性不断构建自己的实用工具。