Unity開發類似Profile那樣的數據分析工具

前言

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那麼強大的數據分析工具的,這裏只是提供的核心思路。

更多精品教程

http://dingxiaowei.cn/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章