本菜狗在上周领了一个做LogManager的任务。很高兴也很慌,毕竟从来没做过编辑器开发,于是面向搜索引擎编程开始辣,找了一些教程学着做,顺便分享一下。
需求分析(? 组里对这个LogManager的要求是在Unity原来的Log功能上再加上根据标签和危险度(?)来筛选。 仔细想想还挺麻烦。需要筛选的话,那自然需要一个Console面板,似乎Unity原本的Console不方便扩展,索性跟着网上的教程重新做一个。
弹出窗口 首先我们先把窗口弹出来。代码很简单。
我们可以从Unity头顶的菜单栏中的Window中找到这个面板并打开。打开了是空白的,当然啦,因为还什么都没画上去。
1 2 3 4 5 6 7 8 9 10 11 public class TempConsoleWindow : EditorWindow { [MenuItem("Window/Temp Console" ) ] private static void OpenWindow ( ) { TempConsoleWindow window = GetWindow<TempConsoleWindow>(); GUIContent titleContent = new GUIContent("TempConsole" , EditorGUIUtility.Load("icons/UnityEditor.ConsoleWindow.png" ) as Texture2D, "a clone sonsole" ); window.titleContent = titleContent; } }
分割区块
我们把原本的面板分成4块:1.菜单栏;2. Log区;3. 调整棒(上下移动调整区域大小);4. 详情区。于是乎我们给这个4个区创建响应的变量为了方便绘制。
1 2 3 4 5 6 7 8 9 10 private Rect m_menuUpperBar = default ;private Rect m_upperPanel = default ;private Rect m_lowerPanel = default ;private Rect m_resizer = default ;private readonly float MENU_BAR_HEIGHT = 20.0f ;private float m_upperSizeRatio = 0.5f ;private readonly float RESIZER_HEIGHT = 4.0f ;private float PanelGroupHeight => position.height - MENU_BAR_HEIGHT;private bool m_isResizing = false ;
偷皮 Unity的绘制GUI方法中可以填写风格参数,我们绘制四个区块的时候自然也需要为区块准备皮和文字颜色。
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 private GUIStyle m_panelLabelStyle = default ;private GUIStyle m_panelStyle = default ;private GUIStyle m_resizerStyle = default ;private GUIStyle m_boxItemStyle = default ;private GUIStyle m_textAreaStyle = default ;private GUIStyle m_labelButtonStyle = default ;private Texture2D m_infoIcon = null ;private Texture2D m_infoIconSmall = null ;private Texture2D m_warningIcon = null ;private Texture2D m_warningIconSmall = null ;private Texture2D m_errorIcon = null ;private Texture2D m_errorIconSmall = null ;private Texture2D m_boxBgOdd = null ;private Texture2D m_boxBgEven = null ;private Texture2D m_boxBgSelected = null ;private Texture2D m_boxIcon = null ;private void GetAssets ( ) { m_panelLabelStyle = new GUIStyle(); m_panelLabelStyle.fixedHeight = 30.0f ; m_panelLabelStyle.richText = true ; m_panelLabelStyle.normal.textColor = Color.white; m_panelLabelStyle.fontSize = 20 ; m_infoIcon = EditorGUIUtility.Load("icons/console.infoicon.png" ) as Texture2D; m_infoIconSmall = EditorGUIUtility.Load("icons/console.infoicon.sml.png" ) as Texture2D; m_warningIcon = EditorGUIUtility.Load("icons/console.warnicon.png" ) as Texture2D; m_warningIconSmall = EditorGUIUtility.Load("icons/console.warnicon.sml.png" ) as Texture2D; m_errorIcon = EditorGUIUtility.Load("icons/console.erroricon.png" ) as Texture2D; m_errorIconSmall = EditorGUIUtility.Load("icons/console.erroricon.sml.png" ) as Texture2D; m_resizerStyle = new GUIStyle(); m_panelStyle = new GUIStyle(); m_panelStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/projectbrowsericonareabg.png" ) as Texture2D; m_boxItemStyle = new GUIStyle(); m_boxItemStyle.normal.textColor = new Color(0.7f , 0.7f , 0.7f ); m_boxBgOdd = EditorGUIUtility.Load("builtin skins/darkskin/images/cn entrybackodd.png" ) as Texture2D; m_boxBgEven = EditorGUIUtility.Load("builtin skins/darkskin/images/cnentrybackeven.png" ) as Texture2D; m_boxBgSelected = EditorGUIUtility.Load("builtin skins/darkskin/images/menuitemhover.png" ) as Texture2D; m_textAreaStyle = new GUIStyle(); m_textAreaStyle.normal.textColor = new Color(0.9f , 0.9f , 0.9f ); m_textAreaStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/projectbrowsericonareabg.png" ) as Texture2D; m_labelButtonStyle = new GUIStyle(); m_labelButtonStyle.normal.textColor = Color.green; m_labelButtonStyle.normal.background = m_textAreaStyle.normal.background; m_labelButtonStyle.alignment = TextAnchor.MiddleLeft; m_labelButtonStyle.stretchWidth = false ; var b = m_labelButtonStyle.border; b.left = 0 ; b.right = 0 ; b.top = 0 ; b.bottom = 0 ; m_labelButtonStyle.border = b; } private void OnEnable ( ) { GetAssets(); }
获取图像的参数都是magic number,这里不多提。图标浏览和获取参数可以参考下面两个页面。当然你也可以准备自己的素材。
https://unitylist.com/p/5c3/Unity-editor-icons https://gist.github.com/rus89/375e107ed8c6db79d0c41b8612e5dbf3
绘制菜单栏 菜单栏上有几个按钮,我们把最常用的如清理,Play开始清除以及右边三个Filter选项。除了’Clear’是Button,其它的都是Toggle。所以得准备一些布尔变量。
1 2 3 4 5 6 7 8 9 10 11 12 private bool m_isClearOnPlay = false ;private bool m_isClearOnBuild = false ;public bool IsClearOnBuild => m_isClearOnBuild;private bool m_isErrorPause = false ;private bool m_isShowLog = true ;private bool m_isShowWarning = true ;private bool m_isShowError = true ;private int m_normalLogCount = 0 ;private int m_warningLogCount = 0 ;private int m_errorLogCount = 0 ;private HashSet<LogType> m_logTypeForUnshow = null ;
准备好了,那么就可以开始绘制菜单栏了。
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 private void DrawMenuUpperBar ( ) { m_menuUpperBar = new Rect(0.0f , 0.0f , this .position.width, MENU_BAR_HEIGHT); GUILayout.BeginArea(m_menuUpperBar, EditorStyles.toolbar); GUILayout.BeginHorizontal(); if (GUILayout.Button(new GUIContent("Clear" ), EditorStyles.toolbarButton, GUILayout.Width(40.0f ))) { ClearLogs(); } GUILayout.Space(5.0f ); m_isClearOnPlay = GUILayout.Toggle(m_isClearOnPlay, new GUIContent("Clear On Play" ), EditorStyles.toolbarButton, GUILayout.Width(80.0f )); m_isClearOnBuild = GUILayout.Toggle(m_isClearOnBuild, new GUIContent("Clear On Build" ), EditorStyles.toolbarButton, GUILayout.Width(85.0f )); m_isErrorPause = GUILayout.Toggle(m_isErrorPause, new GUIContent("Error Pause" ), EditorStyles.toolbarButton, GUILayout.Width(70.0f )); GUILayout.FlexibleSpace(); m_normalLogCount = Mathf.Clamp(m_normalLogCount, 0 , 100 ); m_warningLogCount = Mathf.Clamp(m_warningLogCount, 0 , 100 ); m_errorLogCount = Mathf.Clamp(m_errorLogCount, 0 , 100 ); m_isShowLog = GUILayout.Toggle(m_isShowLog, new GUIContent(m_numStrs[m_normalLogCount], m_infoIconSmall), EditorStyles.toolbarButton, GUILayout.Width(30.0f )); m_isShowWarning = GUILayout.Toggle(m_isShowWarning, new GUIContent(m_numStrs[m_warningLogCount], m_warningIconSmall), EditorStyles.toolbarButton, GUILayout.Width(30.0f )); m_isShowError = GUILayout.Toggle(m_isShowError, new GUIContent(m_numStrs[m_errorLogCount], m_errorIconSmall), EditorStyles.toolbarButton, GUILayout.Width(30.0f )); m_logTypeForUnshow.Clear(); if (!m_isShowLog) { m_logTypeForUnshow.Add(LogType.Log); } if (!m_isShowWarning) { m_logTypeForUnshow.Add(LogType.Warning); } if (!m_isShowError) { m_logTypeForUnshow.Add(LogType.Error); m_logTypeForUnshow.Add(LogType.Assert); m_logTypeForUnshow.Add(LogType.Exception); } GUILayout.EndHorizontal(); GUILayout.EndArea(); } private void OnGUI ( ) { DrawMenuUpperBar(); }
这样就把菜单栏绘制好了。
简单绘制上下区栏 接下来再绘制调整棒之前先简单绘制上下区,待会儿做好调整棒之后就能直接测试效果。要注意给菜单栏以及调整版预留的高度,不然会得到错误的区域大小,添加调整棒后会更会出现奇怪现象。 现在只需要给上下区绘制空白就可以了。
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 private void DrawUpperPanel ( ) { m_upperPanel = new Rect(0 , MENU_BAR_HEIGHT, this .position.width, (this .position.height - MENU_BAR_HEIGHT) * m_upperSizeRatio); GUILayout.BeginArea(m_upperPanel, m_panelStyle); GUILayout.Label("Log" , m_panelLabelStyle); m_upperPanelScroll = GUILayout.BeginScrollView(m_upperPanelScroll); GUILayout.EndScrollView(); GUILayout.EndArea(); } private void DrawLowerPanel ( ) { float yPos = PanelGroupHeight * m_upperSizeRatio + MENU_BAR_HEIGHT + RESIZER_HEIGHT; m_lowerPanel = new Rect(0 , yPos, this .position.width, PanelGroupHeight * (1.0f - m_upperSizeRatio)); GUILayout.BeginArea(m_lowerPanel, m_panelStyle); GUILayout.Label("Log Detail" , m_panelLabelStyle); m_lowerPanelScroll = GUILayout.BeginScrollView(m_lowerPanelScroll); GUILayout.EndScrollView(); GUILayout.EndArea(); } private void OnGUI ( ) { DrawMenuUpperBar(); DrawUpperPanel(); DrawLowerPanel(); }
绘制调整棒 添加区域调整 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 private void DrawResizer ( ) { float yPos = (this .position.height - MENU_BAR_HEIGHT) * m_upperSizeRatio + MENU_BAR_HEIGHT; m_resizer = new Rect(0 , yPos, this .position.width, RESIZER_HEIGHT); GUILayout.BeginArea(new Rect(m_resizer.position + (Vector2.up * RESIZER_HEIGHT), new Vector2(this .position.width, 2.0f )), m_resizerStyle); GUILayout.EndArea(); EditorGUIUtility.AddCursorRect(m_resizer, MouseCursor.ResizeVertical); } private void Resize (Event currentEvent ) { if (m_isResizing) { float pos = currentEvent.mousePosition.y - MENU_BAR_HEIGHT; m_upperSizeRatio = pos / PanelGroupHeight; m_upperSizeRatio = Mathf.Clamp(m_upperSizeRatio, 0.5f , 0.8f ); Repaint(); } } private void ProcessEvents (Event currentEvent ) { if (EventType.MouseDown == currentEvent.type) { m_isResizing = (0 == currentEvent.button && m_resizer.Contains(currentEvent.mousePosition)); } else if (EventType.MouseUp == currentEvent.type) { m_isResizing = false ; } Resize(currentEvent); }
总结 其实感觉编辑器开发挺麻烦的,除了区域绘制要注意,还有一堆API不好查不会用。下一期将会将Log信息捕获并显示在我们自己的这个Console上。
完整工程链接(持续更新中) :https://github.com/2C2C2C/TempUnityLogConsoleClone
P.S : 我在做的时候是参考了某个游戏工作室发布的教程,我也顺便把这个教程分享出来。https://gram.gs/gramlog/creating-editor-windows-in-unity/