为何要实现Custom Toolbar
在项目的开发或者测试过程中使用某些常用功能,不管是放到右键的菜单栏里还是编辑器顶部的菜单栏里都需要至少2、3步操作才可以点到想要的按钮,譬如:
或者
如果可以像平时点击播放按钮一样,在工具栏里有一些极其常用的按钮、当前分支的SVN地址、调节TimeScale等,会极大的提高我们的开发效率。
如何实现Custom Toolbar
在实现Custom Toolbar的过程中,参考了:https://github.com/marijnz/unity-toolbar-extender
ToolbarCallback
首先我们需要一个回调类,去监听编辑器的刷新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | using System; using UnityEngine; using UnityEditor; using System.Reflection; #if UNITY_2019_1_OR_NEWER using UnityEngine.UIElements; #else using UnityEngine.Experimental.UIElements; #endif namespace UnityToolbarExtender { public static class ToolbarCallback { static Type m_toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar"); static Type m_guiViewType = typeof(Editor).Assembly.GetType("UnityEditor.GUIView"); static PropertyInfo m_viewVisualTree = m_guiViewType.GetProperty("visualTree", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); static FieldInfo m_imguiContainerOnGui = typeof(IMGUIContainer).GetField("m_OnGUIHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); static ScriptableObject m_currentToolbar; /// <summary> /// Callback for toolbar OnGUI method. /// </summary> public static Action OnToolbarGUI; static ToolbarCallback() { EditorApplication.update -= OnUpdate; EditorApplication.update += OnUpdate; } static void OnUpdate() { // Relying on the fact that toolbar is ScriptableObject and gets deleted when layout changes if (m_currentToolbar == null) { // Find toolbar var toolbars = Resources.FindObjectsOfTypeAll(m_toolbarType); m_currentToolbar = toolbars.Length > 0 ? (ScriptableObject) toolbars[0] : null; if (m_currentToolbar != null) { // Get it's visual tree var visualTree = (VisualElement) m_viewVisualTree.GetValue(m_currentToolbar, null); // Get first child which 'happens' to be toolbar IMGUIContainer var container = (IMGUIContainer) visualTree[0]; // (Re)attach handler var handler = (Action) m_imguiContainerOnGui.GetValue(container); handler -= OnGUI; handler += OnGUI; m_imguiContainerOnGui.SetValue(container, handler); } } } static void OnGUI() { var handler = OnToolbarGUI; if (handler != null) handler(); } } } |
ToolbarExtender
那么ToolBarCallBack在哪里注册,其中OnToolbarGUI在哪里赋值呢?这时,我们需要定义一个ToolBar的拓展类,在里面实现ToolBarCallBack.OnToolbarGUI的注册、ToolBar区域的计算,话不多说,直接放代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | using System; using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; namespace UnityToolbarExtender { [InitializeOnLoad] public static class ToolbarExtender { static int m_toolCount; static GUIStyle m_commandStyle = null; public static readonly List<Action> LeftToolbarGUI = new List<Action>(); public static readonly List<Action> RightToolbarGUI = new List<Action>(); static ToolbarExtender() { Type toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar"); #if UNITY_2019_1_OR_NEWER string fieldName = "k_ToolCount"; #else string fieldName = "s_ShownToolIcons"; #endif FieldInfo toolIcons = toolbarType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); #if UNITY_2019_1_OR_NEWER m_toolCount = toolIcons != null ? ((int) toolIcons.GetValue(null)) : 7; #elif UNITY_2018_1_OR_NEWER m_toolCount = toolIcons != null ? ((Array) toolIcons.GetValue(null)).Length : 6; #else m_toolCount = toolIcons != null ? ((Array) toolIcons.GetValue(null)).Length : 5; #endif ToolbarCallback.OnToolbarGUI -= OnGUI; ToolbarCallback.OnToolbarGUI += OnGUI; } static void OnGUI() { //根据需求,这里面的style跟Rect可以随便改 // Create two containers, left and right // Screen is whole toolbar if (m_commandStyle == null) { m_commandStyle = new GUIStyle("CommandLeft"); } var screenWidth = EditorGUIUtility.currentViewWidth; // Following calculations match code reflected from Toolbar.OldOnGUI() float playButtonsPosition = (screenWidth - 100) / 2; Rect leftRect = new Rect(0, 0, screenWidth, Screen.height); leftRect.xMin += 10; // Spacing left leftRect.xMin += 32 * m_toolCount; // Tool buttons leftRect.xMin += 20; // Spacing between tools and pivot leftRect.xMin += 64 * 2; // Pivot buttons leftRect.xMax = playButtonsPosition; Rect rightRect = new Rect(0, 0, screenWidth, Screen.height); rightRect.xMin = playButtonsPosition; rightRect.xMin += m_commandStyle.fixedWidth * 3; // Play buttons rightRect.xMax = screenWidth; rightRect.xMax -= 10; // Spacing right rightRect.xMax -= 80; // Layout rightRect.xMax -= 10; // Spacing between layout and layers rightRect.xMax -= 80; // Layers rightRect.xMax -= 20; // Spacing between layers and account rightRect.xMax -= 80; // Account rightRect.xMax -= 10; // Spacing between account and cloud rightRect.xMax -= 32; // Cloud rightRect.xMax -= 10; // Spacing between cloud and collab rightRect.xMax -= 78; // Colab // Add spacing around existing controls leftRect.xMin += 10; leftRect.xMax -= 10; rightRect.xMin += 10; rightRect.xMax -= 10; // Add top and bottom margins leftRect.y = 5; leftRect.height = 24; rightRect.y = 5; rightRect.height = 24; if (leftRect.width > 0) { GUILayout.BeginArea(leftRect); GUILayout.BeginHorizontal(); foreach (var handler in LeftToolbarGUI) { handler(); } GUILayout.EndHorizontal(); GUILayout.EndArea(); } if (rightRect.width > 0) { GUILayout.BeginArea(rightRect); GUILayout.BeginHorizontal(); foreach (var handler in RightToolbarGUI) { handler(); } GUILayout.EndHorizontal(); GUILayout.EndArea(); } } } } |
ToolBarExtenderView
刷新的事件已经实现,也注册成功,那么怎么往ToolBar的左区域或者右区域添加指定的按钮或者显示呢,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; namespace UnityToolbarExtender.View { static class ToolbarStyles { public static readonly GUIStyle ToolBarExtenderBtnStyle; static ToolbarStyles() { ToolBarExtenderBtnStyle = new GUIStyle("Command") { fontSize = 12, alignment = TextAnchor.MiddleCenter, imagePosition = ImagePosition.ImageAbove, fontStyle = FontStyle.Normal, fixedWidth = 60 }; } } [InitializeOnLoad] public class ToolbarExtenderView { static ToolbarExtenderView() { ToolbarExtender.LeftToolbarGUI.Add(OnToolbarGUILeft); ToolbarExtender.RightToolbarGUI.Add(OnToolbarGUIRight); } static void OnToolbarGUILeft() { //下面只是Demo,可随便更改 GUILayout.FlexibleSpace(); string finalStr = "当前是:主干项目/***分支"; GUILayout.Label(finalStr , new GUIStyle("WarningOverlay")); if(GUILayout.Button(new GUIContent("入口场景", "Start [startup] Scene"), ToolbarStyles.ToolBarExtenderBtnStyle)) { SceneHelper.StartScene("Assets/***.unity"); } } static void OnToolbarGUIRight() { //下面只是Demo,可随便更改 GUILayout.FlexibleSpace(); if(GUILayout.Button(new GUIContent("SVN更新", "更新当前的客户端"), ToolbarStyles.ToolBarExtenderBtnStyle)) { Debug.Log("TODO : SVN更新"); } if(GUILayout.Button(new GUIContent("SVN提交", "提交客户端"), ToolbarStyles.ToolBarExtenderBtnStyle)) { Debug.Log("TODO : SVN提交"); } Time.timeScale = GUILayout.HorizontalSlider(Time.timeScale, 0, 10 , new GUIStyle("MiniSliderHorizontal") , new GUIStyle("MinMaxHorizontalSliderThumb") , GUILayout.MinWidth(200),GUILayout.MinHeight(20) ); } } static class SceneHelper { static string sceneToOpen; public static void StartScene(string scene) { if(EditorApplication.isPlaying) { EditorApplication.isPlaying = false; } sceneToOpen = scene; EditorApplication.update += OnUpdate; } static void OnUpdate() { if (sceneToOpen == null || EditorApplication.isPlaying || EditorApplication.isPaused || EditorApplication.isCompiling || EditorApplication.isPlayingOrWillChangePlaymode) { return; } EditorApplication.update -= OnUpdate; if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) { EditorSceneManager.OpenScene(sceneToOpen); EditorApplication.isPlaying = true; } sceneToOpen = null; } } } |
效果展示
将上述脚本放到Editor下,即可看效果啦:
附言
在写的过程中,我们可能会纠结GUIStyle做成什么样的比较好看?这里推荐另外一篇文章:《Unity中的GUIStyle详解》,可以从内置的GUIStyle中找到自己想要的样式哟: