Unity开发类似Profile那样的数据分析工具

Posted 丁小未

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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/

以上是关于Unity开发类似Profile那样的数据分析工具的主要内容,如果未能解决你的问题,请参考以下文章

Unity实用小工具或脚本——可折叠伸缩的多级(至少三级)内容列表(类似于Unity的Hierarchy视图中的折叠效果)

unity和ue4哪个适合个人开发

Unity插件 - MeshEditor 模型风力拉扯特效

类似于 Firefox 的 Chrome 样式编辑器的开发工具

从 AWS 开发工具包访问多个配置文件 - Python

对个人开发者虚幻4和unity哪个简单