.wlt文件

Unity编辑器布局文件保存在一个.wlt的文件,目录为 ../Unity/Unity2021.3.11f1/Editor/Data/Resources/Layouts/

我们同样一个可以自己保存一个.wlt文件使用的时候加载。具体方法是通过反射拿到UnityEditor.WindowLayout,里面有LoadWindowLayoutSaveWindowLayout方法,用来加载和保存.wlt布局文件。

using System;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEditor;

public class LayoutUtility
{
    private static readonly MethodInfo s_LoadWindowLayoutMethod;
    private static readonly MethodInfo s_SaveWindowLayoutMethod;

    static LayoutUtility()
    {
        var typeWindowLayout = Type.GetType("UnityEditor.WindowLayout,UnityEditor");
        if (typeWindowLayout != null)
        {
            s_LoadWindowLayoutMethod = typeWindowLayout.GetMethod(("LoadWindowLayout"),
                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null,
                new Type[] {typeof(string), typeof(bool)}, null);
            s_SaveWindowLayoutMethod = typeWindowLayout.GetMethod(("SaveWindowLayout"),
                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null,
                new Type[] {typeof(string)}, null);
        }
    }

    /// <summary>
    /// 保存Layout到资源文件
    /// </summary>
    public static void SaveLayoutToAsset(string assetPath)
    {
        if (s_SaveWindowLayoutMethod == null)
            return;
        var path = Path.Combine(Directory.GetCurrentDirectory(), assetPath);
        s_SaveWindowLayoutMethod.Invoke(null, new object[] {path});
    }

    /// <summary>
    /// 加载.wlt文件
    /// </summary>
    public static void LoadLayoutFromAsset(string assetPath)
    {
        if (s_LoadWindowLayoutMethod == null)
            return;
        var path = Path.Combine(Directory.GetCurrentDirectory(), assetPath);
        s_LoadWindowLayoutMethod.Invoke(null, new object[] {path, false});
    }
}

用代码实现窗口停靠

同样通过反射拿到对应的属于和方法将两个界面绑定。

/// <summary>
/// 窗口停靠
/// </summary>
public static void DockEditorWindow(EditorWindow parent, EditorWindow child)
{
    var screenPoint = parent.position.min + new Vector2(parent.position.width * .9f, 100f);

    Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly;
    Type ew = assembly.GetType("UnityEditor.EditorWindow");
    Type da = assembly.GetType("UnityEditor.DockArea");
    Type sv = assembly.GetType("UnityEditor.SplitView");

    FieldInfo tp = ew.GetField("m_Parent", BindingFlags.NonPublic | BindingFlags.Instance);
    if (tp == null)
        return;
    var parentArea = tp.GetValue(parent);
    var childArea = tp.GetValue(child);
    
    PropertyInfo tDockAreaParent = da.GetProperty("parent", BindingFlags.Public | BindingFlags.Instance);
    if (tDockAreaParent == null)
        return;
    var oView = tDockAreaParent.GetValue(parentArea, null);
    
    MethodInfo tDragOver = sv.GetMethod("DragOver", BindingFlags.Public | BindingFlags.Instance);
    if(tDragOver == null)
        return;
    var oDropInfo = tDragOver.Invoke(oView, new object[] { child, screenPoint });
    
    FieldInfo tDockArea = da.GetField("s_OriginalDragSource", BindingFlags.NonPublic | BindingFlags.Static);
    if(tDockArea == null)
        return;
    tDockArea.SetValue(null, childArea);

    MethodInfo tPerformDrop = sv.GetMethod("PerformDrop", BindingFlags.Public | BindingFlags.Instance);
    if (tPerformDrop == null)
        return;
    tPerformDrop.Invoke(oView, new object[] { child, oDropInfo, null });
}

创建多窗口

主窗体

using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

public class MainWindow : EditorWindow
{
    private const string LAYOUT_INFO_PATH = "Assets/Editor/Examples/LayoutInfo/layout.wlt";

    private static MainWindow mainWindow;
    private static SubWindow1 subWindow1;
    private static SubWindow2 subWindow2;

    private void OnEnable()
    {
        var toolbar = new Toolbar();
        var btn1 = new Button(() => { LayoutUtility.LoadLayoutFromAsset(LAYOUT_INFO_PATH); }) {text = "加载布局"};
        toolbar.Add(btn1);
        var btn2 = new Button(() =>
        {
            LayoutUtility.SaveLayoutToAsset(LAYOUT_INFO_PATH);
            AssetDatabase.Refresh();
        }) {text = "保存布局"};
        toolbar.Add(btn2);
        rootVisualElement.Add(toolbar);
    }


    [MenuItem("Tool/多窗口显示")]
    public static void Open()
    {
        mainWindow = GetWindow<MainWindow>("MainWindow");
        mainWindow.Init();
        mainWindow.Show();
    }

    private void Init()
    {
        subWindow1 = SubWindow1.Open();
        subWindow2 = SubWindow2.Open();
        LoadLayoutByCode();
    }
    private async void LoadLayoutByCode()
    {
        await Task.Delay(10);
        LayoutUtility.DockEditorWindow(mainWindow, subWindow1);
        await Task.Delay(10);
        LayoutUtility.DockEditorWindow(mainWindow, subWindow2);
    }
}

子窗体1

using UnityEditor;

public class SubWindow1 : EditorWindow
{
    public static SubWindow1 Open()
    {
        var window = GetWindow<SubWindow1>("SubWindow1");
        window.Show();
        return window;
    }
}

子窗体2

using UnityEditor;

public class SubWindow2 : EditorWindow
{
    public static SubWindow2 Open()
    {
        var window = GetWindow<SubWindow2>("SubWindow2");
        window.Show();
        return window;
    }
}

在打开的时候用代码实现吸附,值得注意是需要在所有窗体完全打开的情况下才能吸附。只能用协程做下延迟。

private async void LoadLayoutByCode()
{
    await Task.Delay(10);
    LayoutUtility.DockEditorWindow(mainWindow, subWindow1);
    await Task.Delay(10);
    LayoutUtility.DockEditorWindow(mainWindow, subWindow2);
}

效果展示

默认打开的界面

调整布局后保存,点击加载布局的界面

参考

UnityToolchainsTrick - 15.EditorWindow代码停靠与合并

最后更新于 2022-11-15