前言
Unity开发者对Profile并不会陌生,我们如何开发一个类似Profile的Editor工具来实现我们想要监控的数据呢,这里以监控网络消息包数据为例,开发一个数据监控工具。
思路
主要就是采集数据和数据的表格化,采集数据我是以200毫秒时间内搜集收发的数据列表做成一个数据包,表格绘制采用Handles.DrawAAPolyLine接口来绘制。
效果图
代码
#if UNITY_EDITOR
using BayatGames.SaveGamePro;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Base.Framework.Tools
{
public class MessageMonitorManager : RootMotion.Singleton<MessageMonitorManager>
{
private System.DateTime mTime = default(System.DateTime);
private MessageMonitorPacketGroup mCurretnMonitorPackageGroup;
private int mMillisecond = 200;//间隔200毫秒搜集时间
Queue<MessageMonitorPacket> mMessages = new Queue<MessageMonitorPacket>();
Queue<MessageMonitorPacketGroup> mMessageGroup = new Queue<MessageMonitorPacketGroup>();
private bool mBeginCollectData = false;
//同步到本地的数据
List<MessageMonitorPacket> mSyncDatas = new List<MessageMonitorPacket>();
public bool CollectData
{
get
{
return mBeginCollectData;
}
set
{
if (!value)
{
SaveDataToLocal();
}
else
{
mMessageGroup.Clear();
mSyncDatas.Clear();
mMessages.Clear();
mMessageGroup.Clear();
mCurretnMonitorPackageGroup = null;
}
mBeginCollectData = value;
}
}
public UnityAction<MessageMonitorPacketGroup> MessageMonitorPackGroupEvent;
MessageMonitorPacket DequeMessage()
{
return mMessages.Dequeue();
}
public void EnqueueMessage(MessageMonitorPacket msg)
{
mMessages.Enqueue(msg);
mSyncDatas.Add(msg);
if (mTime == default(DateTime))
mTime = msg.MsgTime;
TimeSpan ts = msg.MsgTime - mTime;
if (ts.Milliseconds < mMillisecond)
{
CollectMessageMonitorPackGroup(msg);
}
else
{
EnqueueMessageGroup();
mTime = msg.MsgTime;
mCurretnMonitorPackageGroup = null;
CollectMessageMonitorPackGroup(msg);
}
}
void CollectMessageMonitorPackGroup(MessageMonitorPacket msg)
{
if (mCurretnMonitorPackageGroup != null)
{
mCurretnMonitorPackageGroup.Msgs.Add(msg);
mCurretnMonitorPackageGroup.MsgLength += msg.MsgLength;
}
else
{
mCurretnMonitorPackageGroup = new MessageMonitorPacketGroup();
mCurretnMonitorPackageGroup.MsgBeginTime = msg.MsgTime;
mCurretnMonitorPackageGroup.Msgs.Add(msg);
mCurretnMonitorPackageGroup.MsgLength += msg.MsgLength;
}
}
void EnqueueMessageGroup()
{
mMessageGroup.Enqueue(mCurretnMonitorPackageGroup);
if (EditorPlayMode._currentState == PlayModeState.Playing)
{
var msg = DequeueMessageGroup();
var floatValue = (float)(msg.MsgLength / 1024f);
msg.MsgKBLength = (float)Math.Round((double)floatValue, 1);
MessageMonitorPackGroupEvent?.Invoke(msg);
}
}
MessageMonitorPacketGroup DequeueMessageGroup()
{
return mMessageGroup.Dequeue();
}
void SaveDataToLocal()
{
if (mSyncDatas.Count > 0)
{
List<MessageMonitorPacket> datas = new List<MessageMonitorPacket>();
datas.AddRange(mSyncDatas);
SaveGame.SaveAsync<List<MessageMonitorPacket>>("NetMsgMonitorDatas.dat", datas);
}
}
}
}
#endif
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Base.Framework.Tools;
using System.Linq;
using System.Reflection;
public class DataAnalyzerTool : EditorWindow
{
static EditorWindow mWindow;
[UnityEditor.MenuItem("Tools/DataAnalyzer")]
private static void Open()
{
mWindow = EditorWindow.GetWindow(typeof(DataAnalyzerTool), true, "数据监视器", true);
mWindow.Show();
mWindow.Focus();
mWindow.position = new Rect(300, 50, 1190, 860);
}
const int LAYERS = 2;
//绘制参数
private GUIStyle mHeadStyle;
private Material mGraphMaterial;
private Vector2 scrollPos;
private Rect mAxisRect = new Rect(150, 50, 800, 300);
private Rect mGraphRect = new Rect(170, 70, 760, 280);
private Rect mGraphContentRect = new Rect(170, 70, 760, 280);
private Color[] mLayerColors = new Color[LAYERS]
{
new Color(190f / 255f, 192f / 255f, 40f / 255f),
new Color(54f / 255f, 137f / 255f, 168f / 255f),
};
private bool mClickGraph;
private int mCurrent;
private const int mSampleCount = 100;
private List<MessageMonitorPacketGroup> mSamples = new List<MessageMonitorPacketGroup>();
private Vector3[][] mPoints = new Vector3[LAYERS][];
PropertyInfo[] mProperties;
List<int> mWidths = new List<int>();
private void OnEnable()
{
mProperties = typeof(MessageMonitorPacket).GetProperties();
MessageMonitorManager.sInstance.CollectData = true;
InitDefaultData();
MessageMonitorManager.sInstance.MessageMonitorPackGroupEvent += OnMessageMonitorPackGroupCollected;
}
private void OnDisable()
{
MessageMonitorManager.sInstance.CollectData = false;
MessageMonitorManager.sInstance.MessageMonitorPackGroupEvent -= OnMessageMonitorPackGroupCollected;
}
private void OnMessageMonitorPackGroupCollected(MessageMonitorPacketGroup msg)
{
mSamples.RemoveAt(0);
mSamples.Add(msg);
}
private void OnInspectorUpdate()
{
if (EditorPlayMode._currentState == PlayModeState.Playing)
mWindow?.Repaint();
}
private MessageMonitorPacketGroup DefaultMessagePacket()
{
var msg = new MessageMonitorPacketGroup();
msg.MsgLength = 0;
msg.MsgKBLength = 0f;
msg.MsgBeginTime = System.DateTime.Now;
msg.Msgs = new List<MessageMonitorPacket>();
return msg;
}
private void InitDefaultData()
{
mSamples.Clear();
for (int i = 0; i < mSampleCount; i++)
{
mSamples.Add(DefaultMessagePacket());
}
}
private void OnGUI()
{
if (mHeadStyle == null)
{
mHeadStyle = new GUIStyle();
mHeadStyle.fontSize = 20;
mHeadStyle.alignment = TextAnchor.MiddleCenter;
mHeadStyle.normal.textColor = new Color(0.8f, 0.8f, 0.8f);
}
if (mGraphMaterial == null)
{
mGraphMaterial = new Material(Shader.Find("Hidden/Internal-Colored"));
mGraphMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mGraphMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
mGraphMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
mGraphMaterial.SetInt("_ZWrite", 0);
}
if (EditorApplication.isPlaying)
{
if (mSamples.Count > 0)
DrawGraph();
HandleEvent();
}
//显示数据列表
if (EditorPlayMode._currentState == PlayModeState.Paused && mCurrent != 0)
{
GUI.Label(new Rect(250, 450, 600, 40), "详细数据", mHeadStyle);
GUILayout.Space(510);
DrawDetailInfos();
}
}
private void DrawDetailInfos()
{
mWidths.Clear();
EditorGUILayout.BeginHorizontal();
for (int i = 0; i < mProperties.Length; i++)
{
int w = (mProperties[i].Name.Length / 5 + 1) * 75;
mWidths.Add(w);
EditorGUILayout.LabelField(mProperties[i].Name, GUILayout.Width(w));
}
EditorGUILayout.EndHorizontal();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(1000), GUILayout.Height(350));
var msgs = mSamples[mCurrent]?.Msgs;
for (int i = 0; i < msgs.Count; i++)
{
EditorGUILayout.BeginHorizontal();
var data = msgs[i];
for (int j = 0; j < mProperties.Length; j++)
{
string showValue = mProperties[j].GetValue(data).ToString();
if (mProperties[j].Name.Contains("MsgId"))
{
showValue += $"({HotfixMessageIdList.MsgIdToType((ushort)mProperties[j].GetValue(data))})";
}
EditorGUILayout.LabelField(showValue, GUILayout.Width(mWidths[j]));
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
}
private void DrawGraph()
{
EditorGUI.LabelField(new Rect(mAxisRect.center.x - 400, mAxisRect.y - 50, 800, 50), "网络消息数据监控", mHeadStyle);
if (mPoints[0] == null || mPoints[0].Length != mSampleCount)
{
for (int layer = 0; layer < LAYERS; ++layer)
mPoints[layer] = new Vector3[mSampleCount];
}
long maxValue = GetListMaxValue(mSamples);
for (int i = 0; i < mSamples.Count; ++i)
{
for (int layer = 0; layer < LAYERS; layer++)
{
mPoints[layer][i].x = (float)i / mSampleCount * mGraphContentRect.width + mGraphContentRect.xMin;
float showData = 0;
if (layer == 0)
{
showData = (float)mSamples[i].MsgKBLength;
}
else if (layer == 1)
{
showData = (float)mSamples[i].Msgs.Count;
}
mPoints[layer][i].y = mGraphContentRect.yMax - showData / maxValue * mGraphContentRect.height;
}
}
//画边(这里的作用是去锯齿)
Handles.BeginGUI();
for (int layer = 0; layer < LAYERS; ++layer)
{
Handles.color = mLayerColors[layer];
Handles.DrawAAPolyLine(mPoints[layer].Where(p => mGraphRect.Contains(p)).ToArray());
}
Handles.EndGUI();
//定位线
if (mGraphRect.Contains(mPoints[0][mCurrent]))
{
Handles.BeginGUI();
Handles.color = Color.white;
Handles.DrawAAPolyLine(3, new Vector2(mPoints[0][mCurrent].x, mAxisRect.yMin), new Vector2(mPoints[0][mCurrent].x, mAxisRect.yMax));
Handles.DrawAAPolyLine(2, new Vector2(mAxisRect.x, mPoints[0][mCurrent].y), mPoints[0][mCurrent]);
Handles.DrawAAPolyLine(2, new Vector2(mAxisRect.x, mPoints[1][mCurrent].y), mPoints[1][mCurrent]);
Handles.EndGUI();
EditorGUI.LabelField(new Rect(mPoints[0][mCurrent].x - 10, mAxisRect.yMax + 5, 50, 20), mCurrent.ToString());
EditorGUI.LabelField(new Rect(mAxisRect.xMin - 140, mPoints[0][mCurrent].y - 10, 200, 20), "PackageSize:" + mSamples[mCurrent].MsgKBLength.ToString() + " KB");
EditorGUI.LabelField(new Rect(mAxisRect.xMin - 140, mPoints[1][mCurrent].y - 10, 200, 20), "PackageCount:" + mSamples[mCurrent].Msgs.Count.ToString());
//详细数据
string detail = string.Format($"MsgPackageCount:{mSamples[mCurrent].Msgs.Count},Length:{mSamples[mCurrent].MsgKBLength},Time:{mSamples[mCurrent].MsgBeginTime}");
EditorGUI.LabelField(new Rect(mAxisRect.center.x - 400, mAxisRect.yMax + 20, 800, 50), detail, mHeadStyle);
}
//坐标轴
DrawArrow(new Vector2(mAxisRect.xMin, mAxisRect.yMax), new Vector2(mAxisRect.xMin, mAxisRect.yMin), Color.white);
DrawArrow(new Vector2(mAxisRect.xMin, mAxisRect.yMax), new Vector2(mAxisRect.xMax, mAxisRect.yMax), Color.white);
}
private void HandleEvent()
{
var point = Event.current.mousePosition;
switch (Event.current.type)
{
case EventType.MouseDrag:
{
if (Event.current.button == 0 && mClickGraph)
{
UpdateCurrentUI();
Repaint();
}
if (Event.current.button == 2 && mClickGraph)
{
mGraphContentRect.x += Event.current.delta.x;
if (mGraphContentRect.x > mGraphRect.x)
mGraphContentRect.x = mGraphRect.x;
if (mGraphContentRect.xMax < mGraphRect.xMax)
mGraphContentRect.x = mGraphRect.xMax - mGraphContentRect.width;
Repaint();
}
}
break;
case EventType.MouseDown:
{
mClickGraph = mGraphRect.Contains(point);
if (mClickGraph)
EditorGUI.FocusTextInControl(null);
if (Event.current.button == 0 && mClickGraph)
{
UpdateCurrentUI();
Repaint();
}
if (Event.current.button == 1)
{
Repaint();
}
EditorPlayMode.Pause();
}
break;
case EventType.KeyDown:
{
if (Event.current.keyCode == KeyCode.LeftArrow)
SetCurrentIndex(mCurrent - 1);
if (Event.current.keyCode == KeyCode.RightArrow)
SetCurrentIndex(mCurrent + 1);
Repaint();
}
break;
case EventType.ScrollWheel:
{
Repaint();
}
break;
}
}
private void UpdateCurrentUI()
{
float x = Event.current.mousePosition.x;
float distance = float.MaxValue;
int index = 0;
for (int i = 0; i < mPoints[0].Length; ++i)
{
if (mGraphRect.Contains(mPoints[0][i]) && Mathf.Abs(x - mPoints[0][i].x) < distance)
{
distance = Mathf.Abs(x - mPoints[0][i].x);
index = i;
}
}
SetCurrentIndex(index);
}
private void SetCurrentIndex(int i)
{
mCurrent = Mathf.Clamp(i, 0, mSampleCount - 1);
}
private string Color2String(Color color)
{
string c = "#";
c += ((int)(color.r * 255)).ToString("X2");
c += ((int)(color.g * 255)).ToString("X2");
c += ((int)(color.b * 255)).ToString("X2");
return c;
}
//绘制带箭头的线
private void DrawArrow(Vector2 from, Vector2 to, Color color)
{
Handles.BeginGUI();
Handles.color = color;
//绘制线
Handles.DrawAAPolyLine(3, from, to);
//箭头
Vector2 v0 = from - to;
v0 *= 10 / v0.magnitude;
Vector2 v1 = new Vector2(v0.x * 0.866f - v0.y * 0.5f, v0.x * 0.5f + v0.y * 0.866f);
Vector2 v2 = new Vector2(v0.x * 0.866f + v0.y * 0.5f, v0.x * -0.5f + v0.y * 0.866f); ;
Handles.DrawAAPolyLine(3, to + v1, to, to + v2);
Handles.EndGUI();
}
private long GetListMaxValue(List<MessageMonitorPacketGroup> list)
{
List<long> newList = new List<long>();
for (int i = 0; i < list.Count; i++)
{
newList.Add(list[i].Msgs.Count);
}
return newList.Max();
}
}
填充实线
//填充曲线
_graphMaterial.SetPass(0);
for (int layer = 0; layer < LAYERS; ++layer)
{
GL.Begin(GL.TRIANGLE_STRIP);
GL.Color(_layerColor[layer]);
for (int i = 0; i < _samples.Count; ++i)
{
if (_graphRect.Contains(_points[layer][i]))
{
GL.Vertex(_points[layer][i]);
if (layer == LAYERS - 1)
GL.Vertex3(_points[layer][i].x, _graphContentRect.yMax, 0);
else
GL.Vertex(_points[layer + 1][i]);
}
}
GL.End();
}
在此思路的基础上耐心扩展,还是可以做成类似Profile那么强大的数据分析工具的,这里只是提供的核心思路。
更多精品教程
转载:https://blog.csdn.net/s10141303/article/details/101594074
查看评论