Unity3D节点编辑器 树状节点布局算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_28474981/article/details/88934197


在这里插入图片描述

1.原始需求

在编辑器中显示出树状图,希望能满足以下几个要求:
(1)节点将会根据父节点的中点作为中间,分两边布局
(2)节点与节点之间不能覆盖,并留有一些空隙

2.算法详情

2.1节点布局算法

(1)递归计算每一个树状节点需要的宽度(NeedWidth),即子节点需要的宽度累加决定父节点需要的宽度。
(2)将当前节点的子节点所需要的宽度进行累加,求出子节点所在层级所需的宽度(curLayerNeedWidth)
(3)子节点所在层级所需的宽度(curLayerNeedWidth)除于2,即为以当前节点为中心开始的布局的初始位置(initOffset)
(4)从初始位置(initOffset)开始,累加对应节点所需宽度,即为子节点应在的位置

2.2 点与点间的画线算法

(1)当子节点数为1时,直接绘制当前节点到子节点位置的连线
(2)当子节点数大于1时,先绘制一条横线,起始点x为第一个子节点的中点值,y为两个父子节点之间高度差除于2;终点x为最后一个子节点的中点值,y为两个父子节点之间高度差除于2

3.核心代码

3.1 节点数据结构

using UnityEngine;
public class EditorNodeData
{
        /// <summary>节点ID </summary>
        public int NodeId;
        /// <summary>节点位置 </summary>
        public Rect WindowRect;
        /// <summary>节点所需宽度 </summary>
        public float NeedWidth;
}

3.2 节点所需宽度计算方法

        public void ComputeNodeNeedWidth(NodeData node,float width)
        {
            width += offset.x;
            if (!NodeEditorWindow.NodeId2NodeDataRects.ContainsKey(node.Id))
            {
                EditorNodeData nodeData = new EditorNodeData();
                nodeData.NodeId = node.Id;
                NodeEditorWindow.NodeId2NodeDataRects.Add(node.Id, nodeData);
            }

            int count = node.ChildNodes.Count;
            for (int i = 0; i < count; i++)
            {
                ComputeNodeNeedWidth(node.ChildNodes[i], width);
            }
            if (count==0)
            {
                NodeEditorWindow.NodeId2NodeDataRects[node.Id].NeedWidth = width;
            }
            else
            {
                float needWith = 0;
                for (int i = 0; i < count; i++)
                {
                    needWith += NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[i].Id].NeedWidth;
                }
                NodeEditorWindow.NodeId2NodeDataRects[node.Id].NeedWidth = needWith;
            }

        }

3.3 节点之间画线方法

        public void DrawLineNode2ChildNode(NodeData node)
        {
            Handles.color = Color.black;
            Rect startRect;
            Rect endRect;
            if (node.ChildNodes.Count == 1) 
            {
                startRect = NodeEditorWindow.NodeId2NodeDataRects[node.Id].WindowRect;
                endRect= NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[0].Id].WindowRect;
                Vector3 verticalStartPoint = new Vector3(startRect.x + startRect.width / 2, startRect.y + startRect.height);
                Vector3 verticalEndPoint = new Vector3(endRect.x + endRect.width / 2, endRect.y );

                Handles.DrawLine(verticalStartPoint, verticalEndPoint);
                return;
            }

            //多个子节点时,绘制横线
            startRect = NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[0].Id].WindowRect;
            endRect = NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes.Last().Id].WindowRect;
            Vector3 horizontalStartPoint = new Vector3(startRect.x+startRect.width/2, startRect.y - (startRect.height + offset.y) / 2, 0);
            Vector3 horizontalEndPoint = new Vector3(endRect.x + endRect.width / 2, endRect.y - (endRect.height + offset.y) / 2, 0);
            Handles.DrawLine(horizontalStartPoint, horizontalEndPoint);

            //父节点引出 竖线
            startRect = NodeEditorWindow.NodeId2NodeDataRects[node.Id].WindowRect;
            endRect = NodeEditorWindow.NodeId2NodeDataRects[node.Id].WindowRect;
            Vector3 parentNodeverticalStartPoint = new Vector3(startRect.x + startRect.width / 2, startRect.y + startRect.height);
            Vector3 parentNodeverticalEndPoint = new Vector3(endRect.x + endRect.width / 2, endRect.y+endRect.height +(endRect.height+ offset.y)/2);
            

            Handles.DrawLine(parentNodeverticalStartPoint, parentNodeverticalEndPoint);

            for (int i = 0; i < node.ChildNodes.Count; i++)
            {
                startRect = NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[i].Id].WindowRect;
                endRect = NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[i].Id].WindowRect;
                Vector3 childNodeverticalStartPoint = new Vector3(startRect.x + startRect.width / 2, startRect.y);
                Vector3 childNodeverticalEndPoint = new Vector3(endRect.x + endRect.width / 2, endRect.y - (endRect.height + offset.y) / 2);
                Handles.DrawLine(childNodeverticalStartPoint, childNodeverticalEndPoint);

            }
        }

3.4 绘制节点方法

        public override void DrawNode(object data,Rect rect)
        {

            NodeData node = (NodeData)data;

            rect=GUILayout.Window(node.Id, rect, DrawNodeWindow, "My Window");

            if (NodeEditorWindow.NodeId2NodeDataRects.ContainsKey(node.Id))
            {
                NodeEditorWindow.NodeId2NodeDataRects[node.Id].WindowRect = rect;
            }
            else
            {
                Debug.LogError("NodeId2NodeDataRects Not Contains  node,nodeId is " + node.Id);
            }

            if (node.ChildNodes.Count > 0)
            {
                DrawLineNode2ChildNode(node);

                int count = node.ChildNodes.Count;
                float initOffset=0;
                float curLayerNeedWidth = 0;

                for (int i = 0; i < count; i++)
                {
                    curLayerNeedWidth+= NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[i].Id].NeedWidth;
                }

                for (int i = 0; i < count; i++)
                {
                    Rect childRect;
                    float curNodeNeedWidth = NodeEditorWindow.NodeId2NodeDataRects[node.ChildNodes[i].Id].NeedWidth;
                    childRect = new Rect(rect.x - (curLayerNeedWidth / 2) + curNodeNeedWidth / 2 + initOffset , rect.y + rect.height*2 + offset.y, rect.width, rect.height);
                    initOffset += curNodeNeedWidth;
                    DrawNode(node.ChildNodes[i], childRect);
                }
            }
        }
        protected override void DrawNodeWindow(int id)
        {
            GUILayout.Label("Dialogue Node - ID: "+id);
            if (GUILayout.Button("xxx"))
            {

            }
        }

猜你喜欢

转载自blog.csdn.net/qq_28474981/article/details/88934197