Unity拓展Game视图的工具栏/任务栏+自定修改Game分辨率

该文章已生成可运行项目,

效果

效果如图,给标题栏右侧加上了3个按钮,用于方便分辨率切换,做分辨率适配的时候比较方便,可自行替换为其他实现。


实现

通过查看UnityEditor相关的源码可以看到,Game视图的绘制在UnityEditor.GameView中,但该类是个internal类,所以需要用反射去Hook相关的绘制回调,添加我们需要的GUI代码;分辨率的设置方法也在该类中。以下给出两种实现:

1.通过分析调用堆栈发现GameView中的GUI绘制是通过UIElement的,而GameView是继承自PlayModeView,PlayModeView继承自EditorWindow,EditorWindow持有一个rootVisualElement。

如下:

    /// <summary>
    ///   <para>Retrieves the root visual element of this window hierarchy.</para>
    /// </summary>
    public VisualElement rootVisualElement
    {
      get
      {
        if (!(this is ISupportsOverlays))
          return this.baseRootVisualElement;
        if (!this.m_OverlaysInitialized)
        {
          this.baseRootVisualElement.Add(this.overlayCanvas.rootVisualElement);
          this.overlayCanvas.Initialize(this);
          this.m_OverlaysInitialized = true;
        }
        return this.overlayCanvas.windowRoot;
      }
    }

那么很明显了,rootVisualElement就是窗口UXML的根节点,UIElement提供的Debugger也可以看到这点。我们往里面插入想要绘制的元素即可。

using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;


[InitializeOnLoad]
public static class GameViewExtension
{
    private static int _screenIndex;

    private static readonly Type GameViewType = typeof(Editor).Assembly.GetType("UnityEditor.GameView");

    private static EditorWindow _gameViewWindow;

    static GameViewExtension()
    {
        EditorApplication.update += OnEditorUpdate;
    }

    private static void OnEditorUpdate()
    {
        EditorApplication.update -= OnEditorUpdate;
        DrawCustomGameViewToolbar();
    }

    private static void DrawCustomGameViewToolbar()
    {
        _gameViewWindow = EditorWindow.GetWindow(GameViewType);
        var root = _gameViewWindow.rootVisualElement;
        VisualElement element = new VisualElement
        {
            style =
            {
                flexDirection = FlexDirection.Row,
                justifyContent = Justify.FlexEnd,
                alignSelf = Align.FlexEnd,
                marginRight = 275
            }
        };
        IMGUIContainer container = new IMGUIContainer();
        container.onGUIHandler += CustomOnGUI;
        element.Add(container);
        if (root.childCount > 0) root.RemoveAt(0);
        root.Add(element);
    }

    private static void CustomOnGUI()
    {
        GUILayout.BeginHorizontal();
        if (GUILayout.Button(new GUIContent("720", EditorGUIUtility.IconContent("PlayButton").image)))
        {
            SetSize(6 + 4);
        }

        if (GUILayout.Button(new GUIContent("1920", EditorGUIUtility.IconContent("PlayButton").image)))
        {
            SetSize(6 + 1);
        }

        if (GUILayout.Button(new GUIContent("3440", EditorGUIUtility.IconContent("PlayButton").image)))
        {
            SetSize(6 + 5);
        }

        GUILayout.EndHorizontal();
    }


    private static void SetSize(int index)
    {
        var sizeSelectionCallback = GameViewType.GetMethod("SizeSelectionCallback",
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        sizeSelectionCallback?.Invoke(_gameViewWindow, new object[] { index, null });
    }
}

2.第二种则是通过HostView,HostView负责各Editor窗口的管理,其中有各窗口的GUI、Focus、LostFocus等回调,是一个HostView.EditorWindowDelegate的委托类型,m_OnGUI中持有GameView的OnGUI方法,可往该委托中添加我们的自定义绘制方法实现Hook。不过处理有些麻烦,没有写完,给出部分示意代码:

    // 使用反射Hook GameView的OnGUI 方法 主要是去Hostview中获取onGui的委托并添加自定义的委托,gameview的OnGUI方法由该委托持有
    private static void OnEditorUpdate()
    {
        // 查找 GameView 窗口实例
        var gameViewWindow = EditorWindow.GetWindow(gameViewType);
        EditorApplication.update -= OnEditorUpdate;
        
        // 获取所有 HostView 实例
        var hostViewType = typeof(Editor).Assembly.GetType("UnityEditor.HostView");
        var hostViews = Resources.FindObjectsOfTypeAll(hostViewType);
        
        foreach (var hostView in hostViews)
        {
            var viewType = hostViewType.GetField("m_ActualView", BindingFlags.NonPublic | BindingFlags.Instance);
            
            if(viewType.GetValue(hostView).GetType() != gameViewType) continue;
            
            // 获取 m_OnGUI 字段
            var onGUIField = hostViewType.GetField("m_OnGUI", BindingFlags.NonPublic | BindingFlags.Instance);
            var onGUIDelegate = onGUIField.GetValue(hostView);
            
            // 获取委托类型
            var onGUIdelegateType = onGUIDelegate.GetType();
        
            // 创建自定义委托
            MethodInfo onGUIhandler = typeof(GameViewExtension).GetMethod(nameof(CustomOnGUI), BindingFlags.Static | BindingFlags.NonPublic);
            if (onGUIhandler != null)
            {
                var customOnGUIDelegate = Delegate.CreateDelegate(onGUIdelegateType, onGUIhandler);
        
                // // 将原始委托和自定义委托合并
                var combinedDelegate = Delegate.Combine(onGUIDelegate as Delegate, customOnGUIDelegate);
        
                // 将更新后的委托设置回 m_OnGUI 字段
                onGUIField.SetValue(hostView, combinedDelegate);
            }
        }
    }

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值