跳转至
using UnityEditor;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
using System.Collections.Generic;

using GameCoreEditor.Tool.DamageDebugger;



public partial class BulletMonitorWindow : EditorWindow
{

    private E_DmgDebugType _dmgDebugType;
    private E_DmgDebugType _preDmgDebugType;

    private static BulletMonitorWindow window; //窗口实例对象,必须是一个static
    private float m_ToolBarHeight; //工具栏高度,TreeView使用此偏移
    /// <summary>
    /// 窗口内显示的GUI面板
    /// </summary>
    [SerializeField] TreeViewState m_TreeViewState;
    [SerializeField] MultiColumnHeaderState m_MultiColumnHeaderState;
    BulletAnalyzeTreeView m_TreeView;

    private GameObject _bulletRoot;
    private GameObject BulletRoot
    {
        get
        {
            if (_bulletRoot == null)
            {
                _bulletRoot = GameObject.Find("GameLevelMGRoot/BulletRoot");
            }
            return _bulletRoot;
        }
    }

    [MenuItem("合金弹头工具集/策划/子弹监控工具")] 
    public static void OpenMonitorWindow() //打开窗口函数,必须是static
    {

        window = GetWindow<BulletMonitorWindow>(false, "子弹监控工具", true); //实例化窗口
        window.Show(); //显示窗口



    }

    //初始化树形结构
    void OnEnable()
    {
        //检查是否已存在序列化视图状态(在程序集重新加载后
        // 仍然存在的状态)
        if (m_TreeViewState == null)
            m_TreeViewState = new TreeViewState();

        //初始化多列头
        bool firstInit = m_MultiColumnHeaderState == null;
        var headerState = BulletAnalyzeTreeView.CreateDefaultMultiColumnHeaderState(multiColumnTreeViewRect.width);
        if (MultiColumnHeaderState.CanOverwriteSerializedFields(m_MultiColumnHeaderState, headerState))
            MultiColumnHeaderState.OverwriteSerializedFields(m_MultiColumnHeaderState, headerState);
        m_MultiColumnHeaderState = headerState;

        var multiColumnHeader = new MyMultiColumnHeader(headerState);
        if (firstInit)
            multiColumnHeader.ResizeToFit();

        //搜索框初始化
        searchField = new SearchField();

        //根据viewState和多列头初始化树形视图
        m_TreeView = new BulletAnalyzeTreeView(m_TreeViewState, multiColumnHeader);





        //事件监听
        DamageDebuggerManager.bulletClickedCallback += OnClickedCallback;
        DamageDebuggerManager.bulletCreateCallback += BulletCreateCallback;
        DamageDebuggerManager.bulletOnCollisionCallback += BulletCollisionInfoNewAddCallback;
    }

    public void OnDisable()
    {
        DamageDebuggerManager.bulletClickedCallback -= OnClickedCallback;
        DamageDebuggerManager.bulletCreateCallback -= BulletCreateCallback;
        DamageDebuggerManager.bulletOnCollisionCallback -= BulletCollisionInfoNewAddCallback;
    }

    #region 点击、生命周期、碰撞等注册事件的函数
    private void OnClickedCallback(Bullet bullet)
    {
        Selection.activeGameObject = bullet.gameObject;
        m_TreeView.SetBulletItemSelection(bullet.Ident);
    }



    private void BulletCreateCallback(Bullet bullet)
    {
        m_TreeView?.AddBullet(bullet);
    }

    private void ActorCreateCallback(Actor actor)
    {
        m_TreeView?.AddActor(actor);
    }
    private void BulletCollisionInfoNewAddCallback(Bullet bullet, E_DmgDebugEvent debugEvent, string hitBoxName = "", Actor actor = null)
    {
        m_TreeView.RefreshCollisionInfo(bullet.Ident, bullet.m_bulletData, debugEvent, hitBoxName, actor);
    }
    #endregion

    #region GUI显示

    Vector2 m_ScrollPosition;

    Rect multiColumnTreeViewRect
    {
        get { return new Rect(20, 30, position.width - 40, position.height - 60); }
    }

    Rect searchToolbarRect
    {
        get { return new Rect(20f, m_ToolBarHeight, position.width - 40f, 20f); }
    }

    SearchField searchField;

    private void DrawSeacrchBar()
    {
        m_TreeView.state.searchString = searchField.OnGUI(searchToolbarRect, m_TreeView.state.searchString);
    }


    private void OnGUI()
    {
        if (DamageDebuggerManager.Enable() == false)
        {
            DamageDebuggerManager.DamageDebuggerEnableSwitch();
        }


        DrawToolBar();
        DrawSeacrchBar();
        m_TreeView.OnGUI(new Rect(0, m_ToolBarHeight + 20f, position.width, position.height / 2));
        // 获取选中的项
        IList<int> selectedIds = m_TreeView.GetSelection();
        if (selectedIds.Count > 0)
        {
            EditorGUILayout.Space(position.height / 2); 
            EditorGUILayout.BeginVertical();
            m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition, GUILayout.Width(position.width));
            //信息标题
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("配置ID");
            EditorGUILayout.LabelField("名称");
            EditorGUILayout.LabelField("当前状态");
            EditorGUILayout.LabelField("创建时间");
            EditorGUILayout.LabelField("创建帧号");
            EditorGUILayout.LabelField("创建位置");
            EditorGUILayout.LabelField("销毁时间");
            EditorGUILayout.LabelField("销毁帧号");
            EditorGUILayout.LabelField("销毁方式");
            EditorGUILayout.LabelField("销毁位置");
            EditorGUILayout.LabelField("碰撞类型");
            EditorGUILayout.LabelField("碰撞对象");
            EditorGUILayout.LabelField("碰撞位置");




            EditorGUILayout.EndHorizontal();


            // 遍历选中的项
            foreach (int id in selectedIds)
            {

                GetSceneSelectedObj();
                // 使用TreeViewItem来获取具体信息
                var item = m_TreeView.FindBulletAnalyzeItem(id);

                //场景
                OnSelectionHierachyShow(item);

                EditorGUILayout.BeginHorizontal();

                if (item != null)
                {
                    // 在TreeView下方显示信息
                    //EditorGUI.BeginChangeCheck();
                    EditorGUILayout.TextField(item.configID.ToString());
                    string newItemDisplayName = EditorGUILayout.TextField(item.displayName);
                    EditorGUILayout.TextField(item.curState);
                    EditorGUILayout.TextField(item.createTime);
                    EditorGUILayout.TextField(item.createFrame);
                    EditorGUILayout.TextField(item.createPos);
                    EditorGUILayout.TextField(item.deadTime);
                    EditorGUILayout.TextField(item.deadFrame);
                    EditorGUILayout.TextField(item.deadType);
                    EditorGUILayout.TextField(item.deadPos);
                    EditorGUILayout.TextField(item.hitBoxType);
                    EditorGUILayout.TextField(item.hitBoxName);
                    EditorGUILayout.TextField(item.hitPos);



                    //if (EditorGUI.EndChangeCheck())
                    //{
                    // 更新TreeViewItem的显示名称
                    //    item.displayName = newItemDisplayName;
                    //    m_TreeView.Reload();
                    //}
                }

                EditorGUILayout.EndHorizontal();
            }
            EditorGUILayout.EndScrollView();
            EditorGUILayout.EndVertical();
        }
    }

    private void GetSceneSelectedObj()
    {
        GameObject selectedGameObject = Selection.activeGameObject;
        // 检查游戏对象是否不为空
        if (selectedGameObject != null)
        {
            // 获取Bullet组件
            Bullet bullet = selectedGameObject.GetComponent<Bullet>();
            if (bullet != null)
            {
                var bulletItem = m_TreeView.GetBulletItemByIdent(bullet.Ident);
                if (bulletItem != null)
                {
                    m_TreeView.SetSelection(new List<int> { bulletItem.id });

                }
            }
        }


}

        private void OnSelectionHierachyShow(BulletAnalyzeTreeItem item)
    {
        var bullets = BulletRoot.GetComponentsInChildren<BulletBase>();
        foreach (BulletBase bullet in bullets)
        {
            if (bullet.Ident == item.ident)
            {
                Selection.activeGameObject = bullet.gameObject;

            }
        }
    }




    #region 主窗口
    private GUIContent[] toolbarContent = null;

    private GUIContent[] selectedListContent = null;
    private int selectedId = 0;




    private void OnSelectedChange(E_DmgDebugType preType, E_DmgDebugType curType)
    {
        if (DamageDebuggerManager.Enable())
        {
            //关掉之前的
            if (DamageDebuggerManager.Enable() && DamageDebuggerManager.Instance.TypeDebugConfigSet.TryGetValue(preType, out var preTypeConfig))
            {
                preTypeConfig.SetEnable(false);  //切换打开状态
                DamageDebuggerManager.Instance.TypeDebugConfigSet[preType] = preTypeConfig;
            }
            //打开现在的
            if (DamageDebuggerManager.Enable() && DamageDebuggerManager.Instance.TypeDebugConfigSet.TryGetValue(_dmgDebugType, out var curTypeConfig))
            {
                curTypeConfig.SetEnable(true);  //切换打开状态
                DamageDebuggerManager.Instance.TypeDebugConfigSet[curType] = curTypeConfig;
            }
        }
    }

    private void OpenAnotherWindow<T>(string title) where T : EditorWindow
    {
        T window = EditorWindow.GetWindow<T>(true); //实例化窗口
        window.titleContent = new GUIContent(title);
        window.Show(); //显示窗口
    }

    /// <summary>
    /// 顶边栏
    /// </summary>
    void DrawToolBar()
    {
        if (toolbarContent == null || toolbarContent.Length == 0)
        {
            toolbarContent = new GUIContent[]
            {
                new GUIContent("子弹命中率统计"), //index = 0
                new GUIContent("DPS分析"),
                new GUIContent("子弹追踪开关"),
                new GUIContent("局内伤害类型实时显示"),


            };
        }

        if (selectedListContent == null || selectedListContent.Length == 0)
        {
            selectedListContent = new GUIContent[]
            {
                new GUIContent("不显示"), //index = 0
                new GUIContent("死亡信息"), //index = 0
                new GUIContent("碰撞信息"),
                new GUIContent("碰撞失效情况"),
            };
        }

        // 绘制工具栏
        GUILayoutOption[] toolbarOptions =
            {
                GUILayout.ExpandWidth(true),
                GUILayout.Height(40)
            };


        GUILayout.BeginHorizontal(EditorStyles.toolbar, toolbarOptions);
        if (GUILayout.Button(toolbarContent[0]))
        {
            OpenAnotherWindow<BulletAccuracyCheckerWindow>("子弹命中率统计");
        }
        if (GUILayout.Button(toolbarContent[1]))
        {
            OpenAnotherWindow<DamageShow>("DPS记录");
        }
        DamageDebuggerManager.Instance.openTrace = GUILayout.Toggle(DamageDebuggerManager.Instance.openTrace, toolbarContent[2]);

        GUILayout.FlexibleSpace();

        GUILayout.Label(toolbarContent[3]);
        selectedId = EditorGUILayout.Popup(selectedId, selectedListContent);
        DamageDebuggerUIManager.IsShowInGame = selectedId > 0;
        if (DamageDebuggerUIManager.IsShowInGame)
        {
            DamageDebuggerUIManager.UIDisplayTime = 5;
            DamageDebuggerUIManager.MaxDisplayCount = 10;
        }
        _dmgDebugType = (E_DmgDebugType)selectedId;
        OnSelectedChange(_preDmgDebugType, _dmgDebugType);
        _preDmgDebugType = _dmgDebugType;

        GUILayout.EndHorizontal();

        m_ToolBarHeight = GUILayoutUtility.GetLastRect().yMax;
    }

    #endregion
    private void OnInspectorUpdate()
    {
        m_TreeView?.Refresh();
        Repaint();
    }



    void DrawTreeBottomToolBar(Rect rect)
    {
        GUILayout.BeginArea(rect);
        using (new EditorGUILayout.HorizontalScope())
        {
            var style = "miniButton";
            //展开和关闭
            if (GUILayout.Button("Expand All", style))
            {
                m_TreeView.ExpandAll();
            }
            if (GUILayout.Button("Collapse All", style))
            {
                m_TreeView.CollapseAll();
            }
            //自由空行
            GUILayout.FlexibleSpace();

            //自由空行
            GUILayout.FlexibleSpace();

            if (GUILayout.Button("Set sorting", style))
            {
                var myColumnHeader = (MyMultiColumnHeader)m_TreeView.
multiColumnHeader;
                myColumnHeader.SetSortingColumns(new int[] { 4, 3, 2 }, new
[] { true, false, true });
                myColumnHeader.mode = MyMultiColumnHeader.Mode.LargeHeader;
            }

            //
            GUILayout.Label("Header: ", "minilabel");
            if (GUILayout.Button("Large", style))
            {
                var myColumnHeader = (MyMultiColumnHeader)m_TreeView.
multiColumnHeader;
                myColumnHeader.mode = MyMultiColumnHeader.Mode.LargeHeader;
            }
            if (GUILayout.Button("Default", style))
            {
                var myColumnHeader = (MyMultiColumnHeader)m_TreeView.
multiColumnHeader;
                myColumnHeader.mode = MyMultiColumnHeader.Mode.
DefaultHeader;
            }
            if (GUILayout.Button("No sort", style))
            {
                var myColumnHeader = (MyMultiColumnHeader)m_TreeView.
multiColumnHeader;
                myColumnHeader.mode = MyMultiColumnHeader.Mode.
MinimumHeaderWithoutSorting;
            }
            GUILayout.Space(10);

            //if (GUILayout.Button("values <-> controls", style))
            //{
            //    m_TreeView.showControls = !m_TreeView.showControls;
            //}

        }

        GUILayout.EndArea();
    }


    #endregion


    #region 多行设置
    internal class MyMultiColumnHeader : MultiColumnHeader
    {
        Mode m_Mode;

        public enum Mode
        {
            LargeHeader,
            DefaultHeader,
            MinimumHeaderWithoutSorting
        }

        public MyMultiColumnHeader(MultiColumnHeaderState state)
            : base(state)
        {
            mode = Mode.DefaultHeader;
        }

        public Mode mode
        {
            get
            {
                return m_Mode;
            }
            set
            {
                m_Mode = value;
                switch (m_Mode)
                {
                    case Mode.LargeHeader:
                        canSort = true;
                        height = 37f;
                        break;
                    case Mode.DefaultHeader:
                        canSort = true;
                        height = DefaultGUI.defaultHeight;
                        break;
                    case Mode.MinimumHeaderWithoutSorting:
                        canSort = false;
                        height = DefaultGUI.minimumHeight;
                        break;
                }
            }
        }

        protected override void ColumnHeaderGUI(MultiColumnHeaderState.Column column, Rect headerRect, int columnIndex)
        {
            // Default column header gui
            base.ColumnHeaderGUI(column, headerRect, columnIndex);

            // Add additional info for large header
            if (mode == Mode.LargeHeader)
            {
                // Show example overlay stuff on some of the columns
                if (columnIndex > 2)
                {
                    headerRect.xMax -= 3f;
                    var oldAlignment = EditorStyles.largeLabel.alignment;
                    EditorStyles.largeLabel.alignment = TextAnchor.UpperRight;
                    GUI.Label(headerRect, 36 + columnIndex + "%", EditorStyles.largeLabel);
                    EditorStyles.largeLabel.alignment = oldAlignment;
                }
            }
        }
    };
    #endregion

}

TreeVIew

using System;
using System.Collections.Generic;
using System.Linq;
using GameCoreEditor.SkillLink;
using GameCoreEditor.Tool.DamageDebugger;
using MS.GameLevel;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using static CSkillInfo;
using static GameCoreEditor.SkillLink.SkillLinkProfilerWindow;


public partial class BulletMonitorWindow : EditorWindow
{


    class BulletAnalyzeTreeView : TreeView
    {
        const int KRowHeight = 20;
        const int KToggleWidth = 20;

        private static int m_TreeViewIdPosition = 0;
        private static int NextId => m_TreeViewIdPosition++;


        BulletAnalyzeTreeItem root;


        private Dictionary<int, BulletAnalyzeTreeItem> items = new Dictionary<int, BulletAnalyzeTreeItem>();
        NBDict<ulong, BulletAnalyzeTreeItem_Actor> actorMap = new NBDict<ulong, BulletAnalyzeTreeItem_Actor>();//角色数据

        // All columns
        enum MyColumns
        {
            Icon,
            Value,
            Name,
        }

        public enum SortOption
        {
            Name,
            Value
        }



        #region TreeView编辑器方法


        /// <summary>
        /// 构建单个actor的子弹目录
        /// </summary>
        /// <param name="actor"></param>
        /// <param name="actorNode"></param>
        /// <param name="depth"></param>
        void BuildActorCategory(Actor actor, BulletAnalyzeTreeItem_Actor actorNode, int depth)
        {
            GameLevelMG gameLevelMg = GetTempLevelMG();
            // 子弹列表
            if (gameLevelMg != null)
            {
                BulletManager bulletManager = gameLevelMg.BulletManager;
                foreach (var actorBullet in bulletManager.GetAllBullet(actor))
                {
                    if (actorBullet == null)
                    {
                        continue;
                    }
                    var bullet = bulletManager.Get(actorBullet);

                    if (actorNode.bulletMap.ContainsKey(bullet.Ident))
                    {
                        continue;
                    }

                    var pRoot = new BulletAnalyzeTreeItem_Bullet();
                    pRoot.id = NextId;
                    pRoot.depth = depth;
                    pRoot.CanDraw = true;
                    pRoot.ItemInvalid = false;
                    pRoot.configID = bullet.Config.ConfigID;
                    pRoot.ident = bullet.Ident;
                    pRoot.createTime = bullet.CreateTime.ToString();
                    pRoot.createFrame = gameLevelMg.currentGame.LogicFrameCount.ToString();
                    pRoot.createPos = bullet.m_bulletData.m_bornPos.ToString();
                    //pRoot.baseName = "子弹列表";
                    pRoot.displayName = bullet.name.ToString();
                    //actorNode.bulletRoot = pRoot;
                    if (actorNode.children != null)
                        actorNode.children.Insert(0, pRoot);
                    else
                        actorNode.AddChild(pRoot);

                    actorNode.bulletMap.Add(bullet.Ident, pRoot);
                }

            }
            Repaint();
        }


        /// <summary>
        /// 构造函数,带多个列
        /// </summary>
        /// <param name="treeViewState"></param>
        /// <param name="multicolumnHeader"></param>
        public BulletAnalyzeTreeView(TreeViewState treeViewState, MultiColumnHeader multicolumnHeader) : base(treeViewState, multicolumnHeader)
        {
            rowHeight = 20;
            columnIndexForTreeFoldouts = 2;
            showBorder = true;
            customFoldoutYOffset = 0;
            //multicolumnHeader.sortingChanged += OnSortingChanged; 检测行排序何时发生改变
            Reload();
        }

        /// <summary>
        /// 构造行
        /// </summary>
        /// <param name="root"></param>
        /// <returns></returns>
        protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
        {
            var rows = base.BuildRows(root); // 获取默认的行列表
            List<TreeViewItem> filteredRows = new List<TreeViewItem>();

            bool hasResults = false;
            foreach (var row in rows)
            {
                if (DoesItemMatchSearch(row))
                {
                    filteredRows.Add(row);
                    hasResults = true;
                }
            }

            return filteredRows;
        }


        /// <summary>
        /// 搜索匹配
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private bool DoesItemMatchSearch(TreeViewItem item)
        {
            // 如果搜索字符串为空,显示所有项
            if (string.IsNullOrEmpty(state.searchString))
            {
                return true;
            }

            // 检查项的displayName是否包含searchString
            return item.displayName.IndexOf(state.searchString, StringComparison.OrdinalIgnoreCase) >= 0;
        }



        /// <summary>
        /// 建树函数
        /// </summary>
        /// <returns>最终的树结构</returns>
        protected override TreeViewItem BuildRoot()
        {
            if(root == null)
            root = new BulletAnalyzeTreeItem();
            if (items == null) items = new Dictionary<int, BulletAnalyzeTreeItem>();
            //this.rootItem?.children.Clear();
            m_TreeViewIdPosition = 0;
            root.id = NextId;
            root.depth = -1;
            if(!items.ContainsKey(root.id))
            items.Add(root.id, root);


            // 构造Actor信息
            var actorCount = BuildActorMap(root, 0);
            if (actorCount == 0) // 没有actor
                return AddEmptyChild(root);

            return root;
        }


        /// <summary>
        /// 重写每行GUI的显示方法
        /// </summary>
        /// <param name="args"></param>
        protected override void RowGUI(RowGUIArgs args)
        {
            var item = (BulletAnalyzeTreeItem)args.item;

            for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
            {
                CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
            }
            Repaint();
        }

        /// <summary>
        /// 增加新的单节点
        /// </summary>
        /// <param name="root">根节点</param>
        /// <returns>返回树结构</returns>
        BulletAnalyzeTreeItem AddEmptyChild(BulletAnalyzeTreeItem root)
        {
            BulletAnalyzeTreeItem leaf = new BulletAnalyzeTreeItem();
            leaf.id = NextId;
            leaf.depth = 0;
            items.Add(leaf.id, leaf);
            root.CanDraw = false;
            root.ItemInvalid = true;
            root.AddChild(leaf);
            return root;
        }

        void CellGUI(Rect cellRect, BulletAnalyzeTreeItem item, MyColumns column, ref RowGUIArgs args)
        {
            // Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
            CenterRectUsingSingleLineHeight(ref cellRect);

            switch (column)
            {
                case MyColumns.Icon:
                    {
                        if (item.enableTrace != GUI.Toggle(cellRect, item.enableTrace, "轨迹显示"))
                        {

                            item.enableTrace = !item.enableTrace;
                            var gameLevelMg = GetTempLevelMG();
                            var bullet = gameLevelMg.BulletManager.Get(item.ident);
                            if (bullet != null)
                            {
                                bullet.openTraceView = item.enableTrace;
                            }

                        }
                    }
                    break;
                case MyColumns.Value:
                    {

                        GUI.Label(cellRect, item.createFrame);
                    }
                    break;

                case MyColumns.Name:
                    {
                        //这里只需要调整一下显示位置,其他的就直接使用基类的显示方法
                        args.rowRect = cellRect;
                        base.RowGUI(args);
                    }
                    break;



            }
        }

        protected override void SelectionChanged(IList<int> selectedIDs)
        {
            foreach (var id in selectedIDs)
            {
                BulletAnalyzeTreeItem item = FindItem(id, root) as BulletAnalyzeTreeItem;
            }
        }



        #endregion

        #region Sorting
        void OnSortingChanged(MultiColumnHeader multiColumnHeader)
        {
            SortIfNeeded(rootItem, GetRows());
        }

        void SortIfNeeded(TreeViewItem root, IList<TreeViewItem> rows)
        {
            if (rows.Count <= 1)
                return;

            if (multiColumnHeader.sortedColumnIndex == -1)
            {
                return; // No column to sort for (just use the order the data are in)
            }

            // Sort the roots of the existing tree items
            //SortByMultipleColumns();
            //TreeToList(root, rows);
            Repaint();
        }


        #endregion



        #region 轨迹追踪显示

        public BulletTrackTracer trackTracer = new BulletTrackTracer();

        #endregion


        #region 生命周期与碰撞
        /// <summary>
        /// 通过消息更新角色
        /// </summary>
        /// <param name="newActor"></param>
        public void AddActor(Actor newActor)
        {
            GameLevelMG gameLevelMg = GetTempLevelMG();

            //actorMap.Clear();

            if (null != gameLevelMg)
            {
                ActorManager actorManager = gameLevelMg.ActorManager;
                foreach (Actor actor in actorManager.GetAllActor())
                {
                    if (actor == null) continue;

                    // 获取所有的actor信息
                    //如果已经存在,则直接结束
                    if (actorMap.ContainsKey(actor.InstanceId))
                    {
                        return;
                    }
                }
                BulletAnalyzeTreeItem_Actor item = new BulletAnalyzeTreeItem_Actor();
                item.id = NextId;
                item.CanDraw = true;
                item.ItemInvalid = false;
                item.displayName = newActor.name;
                item.createTime = gameLevelMg.currentGame.Time.ToString();
                item.createFrame = gameLevelMg.currentGame.LogicFrameCount.ToString();
                actorMap.Add(newActor.InstanceId, item);
                root.AddChild(item);
            }

            base.Repaint();
        }


        /// <summary>
        /// 通过消息更新子弹
        /// </summary>
        /// <param name="bullet"></param>
        public void AddBullet(Bullet bullet)
        {
            var actor = bullet.Actor;
            if(actor == null)
            {
                return;
            }
            var actorNode = actorMap[actor.InstanceId];
            if (actorNode == null)
            {
                return;
            }
            var gameLevelMg = GetTempLevelMG();
            var pRoot = new BulletAnalyzeTreeItem_Bullet();
            pRoot.id = NextId;
            pRoot.CanDraw = true;
            pRoot.ItemInvalid = false;
            pRoot.configID = bullet.Config.ConfigID;
            pRoot.ident = bullet.Ident;
            pRoot.createTime = bullet.CreateTime.ToString();
            pRoot.createFrame = gameLevelMg.currentGame.LogicFrameCount.ToString();
            pRoot.createPos = bullet.m_bulletData.m_bornPos.ToString();
            //pRoot.baseName = "子弹列表";
            pRoot.displayName = bullet.name.ToString();
            //actorNode.bulletRoot = pRoot;
            if (actorNode.children != null)
                actorNode.children.Insert(0, pRoot);
            else
                actorNode.AddChild(pRoot);
            actorNode.bulletMap.Add(bullet.Ident, pRoot);
            base.Repaint();
        }

        /// <summary>
        /// 获得关卡信息
        /// </summary>
        /// <returns></returns>
        static public GameLevelMG GetTempLevelMG()
        {
            GameLevelMG levelMG = null;
#if MS_SERVER
                        levelMG = GameLevelMG.UnsafeInstance;
#else
            levelMG = GameLevelMG.Instance;
#endif
            return levelMG;
        }


        /// <summary>
        /// 构造角色目录
        /// </summary>
        /// <param name="root"></param>
        /// <param name="depth"></param>
        /// <returns></returns>
        int BuildActorMap(BulletAnalyzeTreeItem root, int depth)
        {
            GameLevelMG gameLevelMg = GetTempLevelMG();

            //actorMap.Clear();

            if (null != gameLevelMg)
            {
                ActorManager actorManager = gameLevelMg.ActorManager;
                foreach (Actor actor in actorManager.GetAllActor())
                {
                    if (actor == null) continue;

                    // 获取所有的actor信息

                    //如果已经存在,则重新构建目录并跳过新生成
                    if (actorMap.ContainsKey(actor.InstanceId))
                    {
                        BulletAnalyzeTreeItem_Actor thisActor = actorMap[actor.InstanceId];
                        BuildActorCategory(actor, thisActor, depth + 1);
                        continue;
                    }
                    BulletAnalyzeTreeItem_Actor item = new BulletAnalyzeTreeItem_Actor();
                    item.id = NextId;
                    item.depth = depth;
                    item.CanDraw = true;
                    item.displayName = actor.name;
                    item.createTime = gameLevelMg.currentGame.Time.ToString();
                    item.createFrame = gameLevelMg.currentGame.LogicFrameCount.ToString();
                    //BuildActorCategory(actor, item, depth + 1);
                    actorMap.Add(actor.InstanceId, item);
                    root.AddChild(item);
                }
            }

            Repaint();
            return actorMap.Count;
        }



        #endregion


        #region 外部调用


        /// <summary>
        /// 重新建树
        /// </summary>
        public void Refresh()
        {
            BuildActorMap(root, 0);
            Repaint();
        }


        /// <summary>
        /// 通过树id获取子弹结构
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public BulletAnalyzeTreeItem FindBulletAnalyzeItem(int id)
        {
            BulletAnalyzeTreeItem item = FindItem(id, root) as BulletAnalyzeTreeItem;
            return item;
        }


        public void SetBulletItemSelection(int ident)
        {
            var bulletItem = GetBulletItemByIdent(ident);
            List<int> selectid = new List<int>();
            selectid.Add(bulletItem.id);
            SetSelection(selectid);
        }
        public BulletAnalyzeTreeItem_Bullet GetBulletItemByIdent(int ident)
        {
            GameLevelMG gameLevelMg = GetTempLevelMG();
            foreach (var actorItem in actorMap)
            {
                foreach (var bulletItem in actorItem.Value.bulletMap)
                {
                    if (bulletItem.Value.ident == ident)
                    {
                        return bulletItem.Value;
                    }
                }
            }
            return null;
        }


        /// <summary>
        /// 通过ident获取子弹结构并修改信息
        /// </summary>
        /// <param name="ident"></param>
        /// <param name="deadType"></param>
        public void RefreshDeadInfo(int ident, BulletData bulletData)
        {
            var bulletItem = GetBulletItemByIdent(ident);
            GameLevelMG gameLevelMg = GetTempLevelMG();
            if(bulletItem!=null)
            {
                bulletItem.deadTime = gameLevelMg.currentGame.Time.ToString();
                bulletItem.deadFrame = gameLevelMg.currentGame.UpdateFrameCount.ToString();
                bulletItem.deadType = bulletData.m_DeadType.ToString();
                bulletItem.deadPos = bulletData.m_lastFramePosition.ToString();
                bulletItem.curState = "已销毁";
            }

            base.Repaint();
        }

        /// <summary>
        /// 子弹碰撞信息刷新
        /// </summary>
        /// <param name="ident"></param>
        /// <param name="bulletData"></param>
        /// <param name="debugEvent"></param>
        /// <param name="hitboxName"></param>
        /// <param name="actor"></param>
        public void RefreshCollisionInfo(int ident, BulletData bulletData,E_DmgDebugEvent debugEvent,string hitboxName,Actor actor)
        {
            GameLevelMG gameLevelMg = GetTempLevelMG();
            foreach (var actorItem in actorMap)
            {
                foreach (var bulletItem in actorItem.Value.bulletMap)
                {
                    if (bulletItem.Value.ident == ident)
                    {
                        if(debugEvent == E_DmgDebugEvent.BulletDead)
                        {
                            bulletItem.Value.deadTime = gameLevelMg.currentGame.Time.ToString();
                            bulletItem.Value.deadFrame = gameLevelMg.currentGame.LogicFrameCount.ToString();
                            bulletItem.Value.deadType = bulletData.m_DeadType.ToString();
                            bulletItem.Value.deadPos = bulletData.m_lastFramePosition.ToString();
                            bulletItem.Value.curState = "已销毁";
                        }
                        else
                        {
                            bulletItem.Value.hitBoxName = hitboxName;
                            bulletItem.Value.hitPos = bulletData.m_lastFramePosition.ToString();
                            switch (debugEvent)
                            {
                                case E_DmgDebugEvent.BulletHitBlock:
                                    bulletItem.Value.hitBoxType = "打墙";
                                    bulletItem.Value.actorName = "";
                                    break;
                                case E_DmgDebugEvent.BulletHitColliderWithIgnoreTag:
                                    bulletItem.Value.hitBoxType = "打含无视子弹Tag物体";
                                    bulletItem.Value.actorName = "";
                                    break;
                                case E_DmgDebugEvent.BulletHitThroughWall:
                                    bulletItem.Value.hitBoxType = "穿墙打人";
                                    bulletItem.Value.actorName = actor.name;
                                    break;
                                case E_DmgDebugEvent.TriggerMultiHitBlock:
                                    bulletItem.Value.hitBoxType = "命中但是被墙挡";
                                    bulletItem.Value.actorName = actor.name;
                                    break;

                            }
                        }
                    }
                }
            }
            base.Repaint();
        }



        /// <summary>
        /// 创建多列数据表头
        /// </summary>
        /// <param name="treeViewWidth"></param>
        /// <returns></returns>
        public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
        {
            var columns = new[]
            {

                new MultiColumnHeaderState.Column
                {
                    headerContent = new GUIContent(EditorGUIUtility.FindTexture("EchoFilter Icon"), "查看轨迹"),
                    contextMenuText = "Track",
                    headerTextAlignment = TextAlignment.Center, //中心对齐
                    sortedAscending = true,
                    sortingArrowAlignment = TextAlignment.Right, //箭头在右边对齐
                    width = 80,
                    minWidth = 40,
                    maxWidth = 100,
                    autoResize = false,
                    allowToggleVisibility = true
                },
                 new MultiColumnHeaderState.Column
                {
                    headerContent = new GUIContent("创建帧号"),
                    headerTextAlignment = TextAlignment.Left,
                    sortedAscending = true,
                    sortingArrowAlignment = TextAlignment.Center,
                    width = 100,
                    minWidth = 60,
                    autoResize = false,
                    allowToggleVisibility = false
                },
                new MultiColumnHeaderState.Column
                {
                    headerContent = new GUIContent("子弹名称"),
                    headerTextAlignment = TextAlignment.Left,
                    sortedAscending = true,
                    sortingArrowAlignment = TextAlignment.Center,
                    width = 300,
                    minWidth = 100,
                    autoResize = false,
                    allowToggleVisibility = false
                },
            };

            var state = new MultiColumnHeaderState(columns);
            return state;
        }
        #endregion
    }
}

Item

using UnityEditor;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
using MS.GameLevel;
using System.Collections.Generic;
using MS.Framework.GameConfig;
using MS.Framework;
using PbProtocol;


    public partial class BulletMonitorWindow : EditorWindow
    {
        public class BulletAnalyzeTreeItem : TreeViewItem
        {
            /// <summary>
            /// 禁用行绘制
            /// </summary>
            public bool ItemInvalid;

            /// <summary>
            /// 禁用单元格绘制

        /// </summary>
            public bool CanDraw;
            public bool enableTrace;
            public bool isDead;
            public int ident;
            public string curState = "已创建";
            public string createTime;
            public string createFrame;
            public string createPos;
            public string deadTime;
            public string deadFrame;
            public string deadPos;
            public string deadType;
            public string hitBoxType;
            public string hitBoxName;
            public string hitPos;
            public uint configID;
            public string actorName;
            public StatNode NodeValue;
            public string baseName = "";
            private string displayString;
        }

        class BulletAnalyzeTreeItem_Actor : BulletAnalyzeTreeItem
        {

            //public BulletAnalyzeTreeItem bulletRoot;
            public NBDict<int, BulletAnalyzeTreeItem_Bullet> bulletMap = new NBDict<int, BulletAnalyzeTreeItem_Bullet>();



        }



        class BulletAnalyzeTreeItem_Bullet : BulletAnalyzeTreeItem
        {


        }

    }

DamageDebugManager

//--------------------------------------------------------------------------
/**
    @file   : DamageDebuggerManager
    @date   : 2023/1/11 16:07
    @author : eddiezhao
**/
//--------------------------------------------------------------------------

using DocumentFormat.OpenXml.Drawing.Charts;
using MS.CanvasNode;
using MS.Framework.Protocol.PbProtocol.AILab;
using MS.Tool;
using System.Collections.Generic;
using UnityEngine;
using static DmgDebugInfoDetail;

public class DamageDebuggerManager
{
#if UNITY_EDITOR && MS_ENABLE_DEBUG && !MS_SERVER

    public struct DmgDebugTypeConfig
    {
        public bool Enable;
        public Color LableColor;
        public string TypeName;

        public void SwitchEnable()
        {
            Enable = !Enable;
        }

        public void SetEnable(bool enable)
        {
            Enable = enable;
        }

    }

    public NBDict<E_DmgDebugType, DmgDebugTypeConfig> TypeDebugConfigSet = new NBDict<E_DmgDebugType, DmgDebugTypeConfig>();

    public static bool Enable()
    {
        return Instance != null;
    }

    public static void DamageDebuggerEnableSwitch()
    {
        if (Instance == null)
        {
            Instance = new DamageDebuggerManager();
        }
        else
        {
            Instance.Clear();
            Instance = null;
        }
        MSCheckMenuItem.RefreshGroup("DmgDebug");
    }

    public void DamageDebugTypeSwitch(E_DmgDebugType debugType)
    {
        if (TypeDebugConfigSet.TryGetValue(debugType, out var typeConfig))
        {
            typeConfig.SwitchEnable();
            TypeDebugConfigSet[debugType] = typeConfig;
        }
    }

    private DamageDebuggerManager()
    {
        // 如果新增类型,需要在下面添加相关的配置,UI会基于这些配置来显示
        TypeDebugConfigSet.Add(E_DmgDebugType.BulletDeadType, new DmgDebugTypeConfig()
        {
            Enable = false,
            LableColor = new Color(255 / 255f, 255 / 255f, 255 / 255f, 255 / 255f),
            TypeName = "子弹死亡信息",
        });

        TypeDebugConfigSet.Add(E_DmgDebugType.BulletCollision, new DmgDebugTypeConfig()
        {
            Enable = false,
            LableColor = new Color(255 / 255f, 255 / 255f, 255 / 255f, 255 / 255f),
            TypeName = "子弹碰撞信息",
        });

        TypeDebugConfigSet.Add(E_DmgDebugType.TriggerMultiHitConditionIgnore, new DmgDebugTypeConfig()
        {
            Enable = false,
            LableColor = new Color(255 / 255f, 255 / 255f, 255 / 255f, 255 / 255f),
            TypeName = "伤害框碰撞失效情况",
        });
    }

    private void Clear()
    {
        TypeDebugConfigSet.Clear();
    }

    public static DamageDebuggerManager Instance;

    public NBDict<E_DmgDebugType, NBDict<E_DmgDebugEvent, NBDict<ulong, DmgDebugInfoDetail>>> allDebugInfos = new NBDict<E_DmgDebugType, NBDict<E_DmgDebugEvent, NBDict<ulong, DmgDebugInfoDetail>>>();
    // 目前只给 日志编辑器窗口 使用
    private NBDict<E_DmgDebugType, NBList<DmgDebugInfoDetail>> dmgDebugInfoDetailLogSet = new NBDict<E_DmgDebugType, NBList<DmgDebugInfoDetail>>();



    // 这里做一个静态回调,让编辑器代码主动监听对应的事件
    public static event System.Action<E_DmgDebugType, DmgDebugInfoDetail> DamageDebugInfoChangeCallback;
    public static event System.Action<E_DmgDebugType, DmgDebugInfoDetail> DamageDebugInfoNewAddCallback;





    private void SendDmgDebugEvent(E_DmgDebugType debugType, DmgDebugInfoDetail dmgDebugInfoDetail)
    {
        //if (!typeDebugEnable[debugType])return;

        if (!allDebugInfos.TryGetValue(debugType, out var dict))
        {
            dict = new NBDict<E_DmgDebugEvent, NBDict<ulong, DmgDebugInfoDetail>>();
            allDebugInfos.Add(debugType, dict);
        }

        if (!dict.TryGetValue(dmgDebugInfoDetail.debugEvent, out var debugInfo))
        {
            debugInfo = new NBDict<ulong, DmgDebugInfoDetail>();
            dict.Add(dmgDebugInfoDetail.debugEvent, debugInfo);
        }

        /// 这里需要做一下去重,因为有可能同一个事件,同一个对象,但是不同时间点,会触发多次

        if (debugInfo.ContainsKey(dmgDebugInfoDetail.hash))
        {
            debugInfo[dmgDebugInfoDetail.hash] = dmgDebugInfoDetail;

            if (TypeDebugConfigSet.TryGetValue(debugType, out var typeConfig) && typeConfig.Enable)
            {
                GetDmgDebugInfoDetailSet(debugType)
                    .Add(dmgDebugInfoDetail);

                DamageDebugInfoChangeCallback?.Invoke(debugType, dmgDebugInfoDetail);
            }
        }
        else
        {
            debugInfo.Add(dmgDebugInfoDetail.hash, dmgDebugInfoDetail);

            if (TypeDebugConfigSet.TryGetValue(debugType, out var typeConfig) && typeConfig.Enable)
            {
                GetDmgDebugInfoDetailSet(debugType)
                    .Add(dmgDebugInfoDetail);


                DamageDebugInfoNewAddCallback?.Invoke(debugType, dmgDebugInfoDetail);
            }
        }
        //Logger.LogErrorP($"[伤害Debug工具][开发中调试信息][{dmgDebugInfoDetail.label}]{dmgDebugInfoDetail.info} {dmgDebugInfoDetail.hash}");
    }

    public void SendDmgDebugEvent_Bullet(E_DmgDebugType debugType, E_DmgDebugEvent debugEvent, Bullet bullet, string extraInfo = "", Actor actor = null)
    {
        SendDmgDebugEvent(debugType, new BulletDebugInfo(bullet, debugEvent, extraInfo, actor));
    }

    public void SendDmgDebugEvent_TriggerMultiHit(E_DmgDebugType debugType, E_DmgDebugEvent debugEvent, Actor hitActor, string extraInfo = "")
    {
        SendDmgDebugEvent(debugType, new TriggerMultiHitDebugInfo(debugEvent, hitActor, extraInfo));
    }


    /// <summary>
    /// 通过类型获取对应的调试信息
    /// </summary>
    /// <param name="dmgDebugType"></param>
    /// <returns></returns>
    public NBList<DmgDebugInfoDetail> GetDmgDebugInfoDetailSet(E_DmgDebugType dmgDebugType)
    {
        if (dmgDebugInfoDetailLogSet.TryGetValue(dmgDebugType, out var dmgDebugInfoDetailSet) == false)
        {
            dmgDebugInfoDetailSet = new NBList<DmgDebugInfoDetail>(1000);
            dmgDebugInfoDetailLogSet.Add(dmgDebugType, dmgDebugInfoDetailSet);
        }
        return dmgDebugInfoDetailSet;
    }

    #region 子弹监控工具


    //-------------------点击反馈------------------

    public static event System.Action<Bullet> bulletClickedCallback;
    public void SendBulletClickedEvent(Bullet bullet)=>bulletClickedCallback?.Invoke(bullet);


    //-------------------路径追踪------------------

    public bool openTrace;
    //-------------------生命周期------------------

    //子弹监控工具使用(其实根本就不需要那么多东西,避免创建新对象直接传Bullet)


    public static event System.Action<Bullet> bulletCreateCallback;
    public static event System.Action<Actor> actorCreateCallback;

    public static event System.Action<Bullet, E_DmgDebugEvent, string,Actor> bulletOnCollisionCallback;

    public void SendBulletCollisionEvent(Bullet bullet, E_DmgDebugEvent debugEvent, string hitBoxName = "",Actor actor = null)
    {
        bulletOnCollisionCallback?.Invoke(bullet, debugEvent, hitBoxName,actor);
    }

    /// <summary>
    /// 合并之前工具和现在工具需要的消息函数,相互兼容。
    /// </summary>
    /// <param name="debugType"></param>
    /// <param name="debugEvent"></param>
    /// <param name="bullet"></param>
    /// <param name="extraInfo"></param>
    /// <param name="actor"></param>
    public void SendAllDebugEvent(E_DmgDebugType debugType, E_DmgDebugEvent debugEvent, Bullet bullet, string extraInfo = "", Actor actor = null)
    {
        bulletOnCollisionCallback?.Invoke(bullet, debugEvent, extraInfo, actor);
        if(debugEvent != E_DmgDebugEvent.TriggerMultiHitBlock)
        {
            SendDmgDebugEvent(debugType, new BulletDebugInfo(bullet, debugEvent, extraInfo, actor));
        }
        else
        {
            SendDmgDebugEvent(debugType, new TriggerMultiHitDebugInfo(debugEvent, actor, extraInfo));
        }
    }

    public void SendBulletCreateInfo(Bullet bullet)
    {
        bulletCreateCallback.Invoke(bullet);
    }

    public void SendActorCreateInfo(Actor actor)
    {
        actorCreateCallback.Invoke(actor);
    }



    #endregion

    #region 子弹监控工具 -- 子弹追踪



    #endregion
#endif
}

#if UNITY_EDITOR && MS_ENABLE_DEBUG && !MS_SERVER

public enum E_DmgDebugType
{
    None = 0,
    BulletDeadType = 1,     //子弹死亡类型
    BulletCollision = 2,    //子弹碰撞
    TriggerMultiHitConditionIgnore = 3,   //伤害框碰撞失效情况
}

public enum E_DmgDebugEvent
{
    None = 0,
    BulletDead = 1,
    BulletHitColliderWithIgnoreTag = 2,
    BulletHitBlock = 3,
    TriggerMultiHitBlock = 4,
    BulletHitThroughWall = 5,
}

public abstract class DmgDebugInfoDetail
{

    public E_DmgDebugEvent debugEvent;

    public string label;   //显示的标签文本

    public Vector3 eventShowPos;   //事件发生的位置 用于显示标签

    public string info;    //显示在log list里显示的内容

    public ulong hash;      //相同hash的代表 在逻辑上是同一个事件 要顶掉之前的

    protected void SetInfoDetail(E_DmgDebugEvent debugEvent, string label, Vector3 eventShowPos, string info, ulong hash)
    {
        this.debugEvent = debugEvent;
        this.info = info;
        this.label = label;
        this.eventShowPos = eventShowPos;
        this.hash = hash;
    }
}

public class BulletDebugInfo : DmgDebugInfoDetail
{
    public BulletDebugInfo(Bullet bullet, E_DmgDebugEvent debugEvent, string extraInfo, Actor actor = null)
    {

        switch (debugEvent)
        {
            case E_DmgDebugEvent.BulletDead:
                SetInfoDetail(debugEvent, bullet.m_bulletData.m_DeadType.ToString(), bullet.GetPosition(), $"子弹:{bullet.name} id:{bullet.m_ConfigID} 死亡 类型为{bullet.m_bulletData.m_DeadType}", (ulong)bullet.Ident);
                break;
            case E_DmgDebugEvent.BulletHitColliderWithIgnoreTag:
                SetInfoDetail(debugEvent, "Ignore", bullet.GetPosition(), $"子弹:{bullet.name} id:{bullet.m_ConfigID} 碰到了带有无视子弹tag的 {extraInfo}", (ulong)(extraInfo.GetHashCode() + bullet.Ident));
                break;
            case E_DmgDebugEvent.BulletHitBlock:
                SetInfoDetail(debugEvent, "打墙", bullet.GetPosition(), $"子弹:{bullet.name} id:{bullet.m_ConfigID} 撞到墙 {extraInfo} 了", (ulong)(extraInfo.GetHashCode() + bullet.Ident));
                break;
            case E_DmgDebugEvent.BulletHitThroughWall:
                SetInfoDetail(debugEvent, "穿墙", bullet.GetPosition(), $"子弹:{bullet.name} id:{bullet.m_ConfigID} 穿墙 {extraInfo} 打人 {actor.name} 了", (ulong)(extraInfo.GetHashCode() + bullet.Ident + actor.Ident));
                break;
            default:
                Logger.LogErrorP($"[伤害debug工具]该事件{debugEvent}不属于子弹debug类型 @eddiezhao");
                break;
        }
    }
}

public class TriggerMultiHitDebugInfo : DmgDebugInfoDetail
{
    public TriggerMultiHitDebugInfo(E_DmgDebugEvent debugEvent, Actor hitActor, string extraInfo)
    {
        switch (debugEvent)
        {
            case E_DmgDebugEvent.TriggerMultiHitBlock:
                SetInfoDetail(debugEvent, "被墙挡", hitActor.BosomPoint == null? hitActor.ActorPos : hitActor.BosomPoint.transform.position, $"伤害框命中 {hitActor.name} 但被墙 {extraInfo} 挡了", (ulong)(extraInfo.GetHashCode() + hitActor.Ident));
                break;
            default:
                Logger.LogErrorP($"[伤害debug工具]该事件{debugEvent}不属于伤害框debug类型 @eddiezhao");
                break;
        }
    }
}


#endif

UIManager

#if MS_ENABLE_DEBUG && !MS_SERVER
using MS.GameLevel;
using MS.UI;
using MS.UI.UI3D;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;


namespace GameCoreEditor.Tool.DamageDebugger
{

#region 对象池

    internal interface IObjectPool<T>
    {
        T Allocate();
        void Release(T _object);
        void ClearPool();
    }

    internal class ObjectPool<T> : IObjectPool<T>
    {
        private Stack<T> objectStack;

        public event Func<T> CreateObjectAction;
        public event Action<T> DestroyObjectAction;

        public event Action<T> GetObjectAction;
        public event Action<T> ReleaseObjectAction;

        /// <summary>
        /// 池最大容量,超出的对象再回收以后立即删除
        /// </summary>
        private int poolMaxCount;

        public void Initialize(Func<T> createObjectAction, Action<T> destroyObjectAction, Action<T> getObjectAction, Action<T> releaseObjectAction)
        {
            CreateObjectAction = createObjectAction;
            DestroyObjectAction = destroyObjectAction;
            GetObjectAction = getObjectAction;
            ReleaseObjectAction = releaseObjectAction;

            objectStack = new Stack<T>(10);
        }

        public T Allocate()
        {
            T _object;
            if (objectStack.Count != 0)
            {
                _object = objectStack.Pop();
            }
            else
            {
                if (CreateObjectAction == null)
                {
                    _object = default;
                }
                else
                {
                    _object = this.CreateObjectAction.Invoke();
                }
            }

            GetObjectAction?.Invoke(_object);
            return _object;
        }

        public void Release(T _object)
        {
            ReleaseObjectAction?.Invoke(_object);

            objectStack.Push(_object);
        }

        public void ClearPool()
        {
            if (DestroyObjectAction != null)
            {
                foreach (var _object in objectStack)
                {
                    DestroyObjectAction.Invoke(_object);
                }
            }

            objectStack.Clear();
        }
    }

#endregion


#region 时钟

    internal interface IClock
    {
        event Action<float> TickCallback;
        void Dispose();
    }

    internal class ColckMono : MonoBehaviour, IClock
    {
        public event Action<float> TickCallback;

        private void Update()
        {
            if (GameLevelMG.Instance == null)
                return;
            if (GameLevelMG.Instance.isGamePaused)
                return;

            TickCallback?.Invoke(Time.deltaTime);
        }

        public void Dispose()
        {
            Destroy(gameObject);
        }

        public static IClock CreateColck(Transform parent = null)
        {
            var colckGameObject = new GameObject($"{nameof(ColckMono)}");
            GameObject.DontDestroyOnLoad(colckGameObject);
            colckGameObject.transform.parent = parent;
            return colckGameObject.AddComponent<ColckMono>();
        }
    }

#endregion

#region UI

    internal interface ITime
    {
        float Time { get; set; }
    }

    internal interface IUIDamageDebugInfo : ITime
    {
        E_DmgDebugType DmgDebugType { get; set; }
        DmgDebugInfoDetail DebugInfoDetail { get; set; }

        int MaxDisplayCount { set; }

        void Display();
        void Hide();
        void UpdateUIPosition(Vector3 position);
        void RefreshUI();
        void Dispose();
    }

#endregion


    /// <summary>
    /// 单局伤害调试UI管理
    /// </summary>
    internal class DamageDebuggerUIManager
    {

        public static bool IsShowInGame;
        public static float UIDisplayTime = 3f;
        public static int MaxDisplayCount = 10;

        private static bool _init = false;
        public static DamageDebuggerUIManager Instance { get; private set; }

        private IClock _clock;
        private IObjectPool<IUIDamageDebugInfo> _uiDamageDebugInfoPool;
        public NBList<IUIDamageDebugInfo> _displayUISet;

        private Transform _uiRoot;
        private Camera _mainCamera;
        private Camera _uiCamera;
        private UIDamageDebugInfo _uiDamageDebugPrefab;


        [RuntimeInitializeOnLoadMethod]
        private static void RuntimeInitializeOnLoadMethod()
        {
#if UNITY_PLAY_TIME_CHECK            
            UnityEngine.Debug.LogWarning($"InitializeOnLoadMethod1 {System.DateTime.Now.ToString("HH:mm:ss.fff")}");
#endif
            // 每次进入播放模式 静态对象 会被重置,所以在运行时的时候注册下回调
            UnregisterDamageDebugEvent(); // 防止多次注册
            RegisterDamageDebugEvent();
#if UNITY_PLAY_TIME_CHECK            
            UnityEngine.Debug.LogWarning($"InitializeOnLoadMethod2 {System.DateTime.Now.ToString("HH:mm:ss.fff")}");
#endif
        }

        private static void RegisterDamageDebugEvent()
        {
            DamageDebuggerManager.DamageDebugInfoChangeCallback += DamageDebugInfoChangeCallback;
            DamageDebuggerManager.DamageDebugInfoNewAddCallback += DamageDebugInfoNewAddCallback;
        }

        private static void UnregisterDamageDebugEvent()
        {
            DamageDebuggerManager.DamageDebugInfoChangeCallback -= DamageDebugInfoChangeCallback;
            DamageDebuggerManager.DamageDebugInfoNewAddCallback -= DamageDebugInfoNewAddCallback;
        }

        private static void DamageDebugInfoChangeCallback(E_DmgDebugType dmgDebugType, DmgDebugInfoDetail dmgDebugInfoDetail)
        {
            if (IsShowInGame == false)
                return;

            TryInit();

            foreach (var ui in Instance._displayUISet)
            {
                if (ui.DmgDebugType == dmgDebugType && ui.DebugInfoDetail.debugEvent == dmgDebugInfoDetail.debugEvent &&  ui.DebugInfoDetail.hash == dmgDebugInfoDetail.hash)
                {
                    ui.Time = 0;
                    ui.MaxDisplayCount = MaxDisplayCount;

                    ui.DebugInfoDetail = dmgDebugInfoDetail;

                    Instance.UpdateDebugUIPosition(ui);
                    ui.RefreshUI();

                    return;
                }
            }

            AddIUIDamageDebugInfo(dmgDebugType, dmgDebugInfoDetail);
        }

        private static void DamageDebugInfoNewAddCallback(E_DmgDebugType dmgDebugType, DmgDebugInfoDetail dmgDebugInfoDetail)
        {
            if (IsShowInGame == false)
                return;

            TryInit();

            AddIUIDamageDebugInfo(dmgDebugType, dmgDebugInfoDetail);
        }

        private static void AddIUIDamageDebugInfo(E_DmgDebugType dmgDebugType, DmgDebugInfoDetail dmgDebugInfoDetail)
        {
            var ui = Instance._uiDamageDebugInfoPool.Allocate();

            ui.Time = 0;
            ui.MaxDisplayCount = MaxDisplayCount;

            ui.DmgDebugType = dmgDebugType;
            ui.DebugInfoDetail = dmgDebugInfoDetail;

            Instance.UpdateDebugUIPosition(ui);
            ui.RefreshUI();


            var removeCount = (Instance._displayUISet.Count + 1) - MaxDisplayCount;
            if (removeCount > 0)
            {
                for (int i = 0; i < removeCount; i++)
                {
                    Instance._uiDamageDebugInfoPool.Release(Instance._displayUISet[i]);
                }
                Instance._displayUISet.RemoveRange(0, removeCount);
            }

            Instance._displayUISet.Add(ui);
        }


        private static void TryInit()
        {
            if (Application.isPlaying == false)
                return;
            if (_init)
                return;

            if (Instance != null)
                Instance.Dispose();

            Instance = new DamageDebuggerUIManager();
            Instance.Init();
            _init = true;
        }


        private void Init()
        {
            Application.quitting += Dispose;

            // init UI
            {
                var rootGO = new GameObject("DamageDebugger UIRoot");
                rootGO.AddComponent<Canvas3D>();
                _uiRoot = rootGO.transform;

                if (UGUIRoot.Instance)
                {
                    _mainCamera = UGUIRoot.Instance.GetMainCamera;
                    _uiCamera = UGUIRoot.Instance.UI3DCamera;

                    if (UGUIRoot.Instance.UI3DRoot != null)
                    {
                        _uiRoot.parent = UGUIRoot.Instance.UI3DRoot;
                    }
                    else
                    {
                        Object.DontDestroyOnLoad(rootGO);
                    }
                }

                _uiDamageDebugPrefab = UIDamageDebugInfo.LoadPrefab(_uiRoot);
            }

            _clock = ColckMono.CreateColck(_uiRoot);
            _clock.TickCallback += Tick;

            var pool = new ObjectPool<IUIDamageDebugInfo>();
            _uiDamageDebugInfoPool = pool;
            pool.Initialize(
                () => GameObject.Instantiate(_uiDamageDebugPrefab, _uiRoot),
                (item) => item.Dispose(),
                (item) => item.Display(),
                (item) => item.Hide()
            );

            _displayUISet = new NBList<IUIDamageDebugInfo>();
        }

        private void Dispose()
        {
            UnregisterDamageDebugEvent();
            Application.quitting -= Dispose;

            _init = false;

            _clock.TickCallback -= Tick;
            var clock = _clock as ColckMono;
            if (clock != null)
                GameObject.Destroy(clock);
            _clock = null;


            _uiDamageDebugInfoPool.ClearPool();
            _uiDamageDebugInfoPool = null;

            foreach (var item in _displayUISet)
            {
                item.Dispose();
            }
            _displayUISet = null;

            // Dispose UI
            {
                GameObject.Destroy(_uiRoot);
                _uiRoot = null;
                _mainCamera = null;
                _uiCamera = null;

                UIDamageDebugInfo.UnloadPrefab(_uiDamageDebugPrefab);
                _uiDamageDebugPrefab = null;
            }
        }


        private void Tick(float deltaTime)
        {
            for (var i = _displayUISet.Count - 1; i > -1; i--)
            {
                var ui = _displayUISet[i];

                if (ui.Time >= UIDisplayTime)
                {
                    _displayUISet.RemoveAt(i);
                    _uiDamageDebugInfoPool.Release(ui);
                }
                ui.Time += deltaTime;

                UpdateDebugUIPosition(ui);
            }
        }


        private void UpdateDebugUIPosition(IUIDamageDebugInfo damageDebug)
        {
            var wordPositon = damageDebug.DebugInfoDetail.eventShowPos;

            var viewportPosition = _mainCamera.WorldToViewportPoint(wordPositon);

            damageDebug.UpdateUIPosition(_uiCamera.ViewportToWorldPoint(viewportPosition));
        }
    }
}
#endif