FishPlayer

一个喜欢摸鱼的废物

0%

摸一个简单的 Unity Log Console (1)

本菜狗在上周领了一个做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
// log区风格以及log区的标题风格
private GUIStyle m_panelLabelStyle = default;
private GUIStyle m_panelStyle = default;
// 调整棒儿的风格
private GUIStyle m_resizerStyle = default;
// box item 其实是在log区显示的每一个log item
private GUIStyle m_boxItemStyle = default;
// 这是 log详情的文字风格
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;
// 这行是同事告诉我的,但是不知道为什么不管用,直接用会有空引用报错,要在GUI里用
// m_panelStyle.normal.background = GUI.skin.window.normal.background;

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)))
{
// 用于清空所有的 log
ClearLogs();
}
GUILayout.Space(5.0f);

// m_isCollapse = GUILayout.Toggle(m_isCollapse, new GUIContent("Collapse"), EditorStyles.toolbarButton, GUILayout.Width(55.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));

// 弹性空白,我不太清楚应该怎么描述,但是它可以把后面的几个Toggle都尽可能往后面推
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();
//DrawUpperPanel();
//DrawLowerPanel();
//DrawResizer();
}

这样就把菜单栏绘制好了。

简单绘制上下区栏

接下来再绘制调整棒之前先简单绘制上下区,待会儿做好调整棒之后就能直接测试效果。要注意给菜单栏以及调整版预留的高度,不然会得到错误的区域大小,添加调整棒后会更会出现奇怪现象。
现在只需要给上下区绘制空白就可以了。

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);

// 为了画log item而准备的 ScrollView
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);

// 为 log详情准备的 ScrollView
m_lowerPanelScroll = GUILayout.BeginScrollView(m_lowerPanelScroll);
GUILayout.EndScrollView();

GUILayout.EndArea();
}

// 实际调用绘制
private void OnGUI()
{
DrawMenuUpperBar();
DrawUpperPanel();
DrawLowerPanel();
//DrawResizer();
}

绘制调整棒 添加区域调整

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();

// 把 m_resizer 区域内的光标换成拉伸指示光标
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);
//Debug.Log($"next upper ratio {m_upperSizeRatio}");
Repaint();
}
}

private void ProcessEvents(Event currentEvent)
{
if (EventType.MouseDown == currentEvent.type)
{
// if press mouse left in resizer
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/