FishPlayer

一个喜欢摸鱼的废物

0%

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

上一期我们做完了基本的面板绘制,现在我们可以往里面添加简单的内容了。

定义显示单位

既然我们要显示Log,那就定义一些数据结构用于存储这些Log。因为这些东西在我们的console中只需要显示就行了,我们只需要定义只读的数据就可以,或许还能提高性能。

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

public class LogItem
{
public bool IsSelected { get; set; }
public readonly string LogInfo = string.Empty; // Log简要信息
public readonly string LogMessage = string.Empty; // Log 调用栈!
public readonly LogType GetLogType = LogType.Log;

public LogItem(bool isSelected, string info, string message, LogType type)
{
IsSelected = isSelected;
// 记得加上时间!!!
LogInfo = string.Format("[{0}] {1}", System.DateTime.Now.ToLongTimeString(), info);
LogMessage = message;
GetLogType = type;
}
}

public class TempConsoleWindow : EditorWindow
{
private void LogMessageReceived(string condition, string stackTrace, LogType type)
{
LogItem log = new LogItem(false, condition, stackTrace, type);
m_logItems.Add(log);
switch (type)
{
case LogType.Error:
m_errorLogCount++;
break;
case LogType.Assert:
m_errorLogCount++;
break;
case LogType.Warning:
m_warningLogCount++;
break;
case LogType.Log:
m_normalLogCount++;
break;
case LogType.Exception:
m_errorLogCount++;
break;
default:
m_errorLogCount++;
break;
}

// 主动刷新,因为当此窗口没有焦点时似乎无法走 OnGUI() 刷新。
Repaint();
//GUI.changed = true;
}

private void OnEnable()
{
// ....
// 监听此事件,当Debug.Log("")被调用的时候就会响应
Application.logMessageReceived += LogMessageReceived;
}

private void OnDestroy()
{
// ....
Application.logMessageReceived -= LogMessageReceived;
}

}

在上部面板显示Log

像原本Unity Console一样,每一条Log以一个方条item的形式显示在上半部分面板。
我们先把这个item画出来。
m_selectedLogItem 是我们选中的 LogItem。

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
private bool DrawLogBox(in string content, LogType logType, bool isOdd, bool isSelected)
{
if (isSelected)
{
m_boxItemStyle.normal.background = m_boxBgSelected;
}
else
{
if (isOdd)
{
m_boxItemStyle.normal.background = m_boxBgOdd;
}
else
{
m_boxItemStyle.normal.background = m_boxBgEven;
}
}

switch (logType)
{
case LogType.Error:
m_boxIcon = m_errorIcon;
break;
case LogType.Assert:
m_boxIcon = m_errorIcon;
break;
case LogType.Warning:
m_boxIcon = m_warningIcon;
break;
case LogType.Log:
m_boxIcon = m_infoIcon;
break;
case LogType.Exception:
m_boxIcon = m_errorIcon;
break;
default:
break;
}
// 这个按钮是因为这条box item是可以被点击选择的!!!
return GUILayout.Button(new GUIContent(content, m_boxIcon), m_boxItemStyle, GUILayout.ExpandWidth(true), GUILayout.Height(30.0f));
}

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

// 在scrollview里填充log,unity
m_upperPanelScroll = GUILayout.BeginScrollView(m_upperPanelScroll);
for (int i = 0; i < m_logItems.Count; i++)
{
if (m_logTypeForUnshow.Contains(m_logItems[i].GetLogType))
{
continue;
}

// 画的时候,顺便接受item的点击的结果
if (DrawLogBox(m_logItems[i].LogInfo, m_logItems[i].GetLogType, i % 2 == 0, m_logItems[i].IsSelected))
{
if (null != m_selectedLogItem)
{
if (m_logItems[i] == m_selectedLogItem)
{
// click a some one, open code
// JumpToCurrentLogPos(); // 跳转到你点击的Log的顶部的代码文件(如果可以
}
else
{
m_selectedLogItem.IsSelected = false;
m_selectedLogItem = m_logItems[i];
m_selectedLogItem.IsSelected = true;
}
}
else
{
m_selectedLogItem = m_logItems[i];
m_selectedLogItem.IsSelected = true;
}
// 准备刷新
GUI.changed = true;
}
}

GUILayout.EndScrollView();
GUILayout.EndArea();
}


private void OnGUI()
{
// .....
if (GUI.changed)
{
Repaint();
}
}

显示Log详情以及调用栈

由于我脑子不好使,我不知道如何完美复刻Unity Console的详情,Unity自带Console中显示的代码链接既可以复制也可以点击跳转,我只做了简单的跳转。如果有比较熟悉编辑器开发的朋友,可以提供修改的方法。

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

string logDetail = null;
string[] logDetailMutiLine = null;

// TODO : code clean here
string pathline = "";
string tempCase = ".cs:";
string path = string.Empty;
int line = 0;
int splitwa = 0;

if (null != m_selectedLogItem)
{
logDetail = m_selectedLogItem.LogMessage;
GUILayout.TextArea(string.Format("{0}\n", m_selectedLogItem.LogInfo), m_textAreaStyle);

logDetailMutiLine = logDetail.Split('\n');
for (int i = 0; i < logDetailMutiLine.Length; i++)
{
// Regex match 'at xxx'
Match matches = Regex.Match(logDetailMutiLine[i], @"\(at (.+)\)", RegexOptions.Multiline);

if (matches.Success)
{
while (matches.Success)
{
pathline = matches.Groups[1].Value;
if (pathline.Contains(tempCase))
{
int splitIndex = pathline.LastIndexOf(":");
path = pathline.Substring(0, splitIndex);
line = Convert.ToInt32(pathline.Substring(splitIndex + 1));
string fullpath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets"));
fullpath = fullpath + path;
splitwa = logDetailMutiLine[i].LastIndexOf("(");
logDetailMutiLine[i] = logDetailMutiLine[i].Substring(0, splitwa);

GUILayout.BeginHorizontal();
GUILayout.TextArea(string.Format(" (at : {0})\n", logDetailMutiLine[i]), m_textAreaStyle);
if (GUILayout.Button(string.Format(" ( {0} )\n", pathline), m_labelButtonStyle))
{
// 打开文件的魔法
UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(fullpath.Replace('/', '\\'), line);
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
break;
}
}
}
else
{
GUILayout.TextArea(logDetailMutiLine[i], m_textAreaStyle);
}

}
}

GUILayout.EndScrollView();
GUILayout.EndArea();
}

这边用了正则表达式去匹配去寻找代码文件,是从网上查到的,我用着还不太熟练。所以写得很乱,后续会在工程中更新干净些的版本。

完善菜单栏功能

既然我们能显示Log了,那就别忘了补上清除Log的功能。

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
public void ClearLogs()
{
if (null != m_selectedLogItem)
{
m_selectedLogItem.IsSelected = false;
}
m_selectedLogItem = null;

m_normalLogCount = 0;
m_warningLogCount = 0;
m_errorLogCount = 0;
m_logItems.Clear();
GUI.changed = true;
}


private void DrawMenuUpperBar()
{
// .....
if (GUILayout.Button(new GUIContent("Clear"), EditorStyles.toolbarButton, GUILayout.Width(40.0f)))
{
// 用于清空所有的 log
ClearLogs();
}
// .....
}

总结

这一期就到这里。感觉工具开发也有点搬砖,但是会用到各种各样的搬砖工具去搬运契合不同的砖,能接触到很多东西,还蛮有趣的。
下一期我会尝试把当前这个乞丐Log Console的功能都补齐。
代码有缺漏可以先参考工程。


完整工程链接

https://github.com/2C2C2C/TempUnityLogConsoleClone