Playable Graph Visualizer源码剖析

Node

  • object content 
  • float weight 
  • bool active
  • Node parent
  • IList<Node> childeren

  • +AddChild(child : Node)
  • +depth   实际上调用内部方法 GetDepthRecursive(node : Node)
  • +GetContentType : Type 返回content的类型
  • +GetContentTypeName : string 返回content的类型的名称
  • +GetContentTypeShortName : string 返回content的类型的简写名称
  • +GetColor : Color  根据简写名称求出一个特定的颜色值
            string shortName = type.ToString().Split('.').Last();
            float h = (float)Math.Abs(shortName.GetHashCode()) / int.MaxValue;
            return Color.HSVToRGB(h, 0.6f, 1.0f);

Graph

  • m_Nodes : List<Node>  
  • NodeWeight  :  class
  •         node : object
  •         weight : float

  • GetChilden(node : Node) : IEnumerable<Node> 抽象方法
  • Populate 抽象方法
  • AddNodeHierarchy(root : Node)   
  • AddNode(node : Node)
  • Clear
  • Refresh
  • GetEnumerator
  • IsEmpty

IGraphLayout

  • vertices : IEnumerable<Vertex>   所有的结点信息
  • edges : IEnumerable<Edge>  所有的连接线信息

Edge

  • source : Vertex
  • destination : Vertex

Vertex

  • position : Vector2
  • node : Node

ReingoldTilford 

主要做结点的布局计算相关。主要方法为CalculateLayout方法:

        public void CalculateLayout(Graph graph)
        {
            m_NodeVertexLookup.Clear();
            foreach (Node node in graph)
            {
                m_NodeVertexLookup.Add(node, new Vertex(node));
            }

            if (m_NodeVertexLookup.Count == 0) return;

            IList<float> horizontalPositions = ComputeHorizontalPositionForEachLevel();

            List<Node> roots = m_NodeVertexLookup.Keys.Where(n => n.parent == null).ToList();

            for (int i = 0; i < roots.Count; ++i)
            {
                RecursiveLayout(roots[i], 0, horizontalPositions);

                if (i > 0)
                {
                    Vector2 previousRootRange = ComputeRangeRecursive(roots[i - 1]);
                    RecursiveMoveSubtree(roots[i], previousRootRange.y + s_VerticalDistanceBetweenTrees + s_DistanceBetweenNodes);
                }
            }
        }

调用了ComputeHorizontalPositionForEachLevel方法计算每一层的水平位置。

RecursiveLayout根据传入的结点,获取它的子节点,根据子节点确定其y方向上的位置。

RecursiveModeSubtree递归应用子结点的垂直偏移。

IGraphRenderer

  • Draw(graphLayout : IGraphLayout, drawingArea : Rect)
  • Draw(graphLayout : IGraphLayout, drawingArea : Rect, graphSettings : GraphSettings)

GraphSettings

主要记录一些绘制相关的基础设置信息。

DefaultGraphRenderer

根据传入的布局相关信息进行相应的显示。主要关注它的Draw方法:

        public void Draw(IGraphLayout graphLayout, Rect totalDrawingArea, GraphSettings graphSettings)
        {
            var legendArea = new Rect();
            var drawingArea = new Rect(totalDrawingArea);

            PrepareLegend(graphLayout.vertices);

            if (graphSettings.showInspector)
            {
                legendArea = new Rect(totalDrawingArea)
                {
                    width = Mathf.Max(EstimateLegendWidth(), drawingArea.width * 0.25f) + s_BorderSize * 2
                };

                legendArea.x = drawingArea.xMax - legendArea.width;
                drawingArea.width -= legendArea.width; // + s_BorderSize;

                DrawLegend(graphSettings, legendArea);
            }

            if (m_SelectedNode != null)
            {
                Event currentEvent = Event.current;
                if (currentEvent.type == EventType.MouseUp && currentEvent.button == 0)
                {
                    Vector2 mousePos = currentEvent.mousePosition;
                    if (drawingArea.Contains(mousePos))
                    {
                        m_SelectedNode = null;

                        if (nodeClicked != null)
                            nodeClicked(m_SelectedNode);
                    }
                }
            }

            DrawGraph(graphLayout, drawingArea, graphSettings);
        }

在这个方法中,主要是绘制他的Inspector区域。在末尾处调用DrawGraph方法绘制实际的graph中的结点。

        // Draw the graph and returns the selected Node if there's any.
        private void DrawGraph(IGraphLayout graphLayout, Rect drawingArea, GraphSettings graphSettings)
        {
            // add border, except on right-hand side where the legend will provide necessary padding
            drawingArea = new Rect(drawingArea.x + s_BorderSize,
                drawingArea.y + s_BorderSize,
                drawingArea.width - s_BorderSize * 2,
                drawingArea.height - s_BorderSize * 2);

            var b = new Bounds(Vector3.zero, Vector3.zero);
            foreach (Vertex v in graphLayout.vertices)
            {
                b.Encapsulate(new Vector3(v.position.x, v.position.y, 0.0f));
            }

            // Increase b by maximum node size (since b is measured between node centers)
            b.Expand(new Vector3(graphSettings.maximumNormalizedNodeSize, graphSettings.maximumNormalizedNodeSize, 0));

            var scale = new Vector2(drawingArea.width / b.size.x, drawingArea.height / b.size.y);
            var offset = new Vector2(-b.min.x, -b.min.y);

            Vector2 nodeSize = ComputeNodeSize(scale, graphSettings);

            GUI.BeginGroup(drawingArea);

            foreach (var e in graphLayout.edges)
            {
                Vector2 v0 = ScaleVertex(e.source.position, offset, scale);
                Vector2 v1 = ScaleVertex(e.destination.position, offset, scale);
                Node node = e.source.node;

                if (graphLayout.leftToRight)
                    DrawEdge(v1, v0, node.weight);
                else
                    DrawEdge(v0, v1, node.weight);
            }

            Event currentEvent = Event.current;

            bool oldSelectionFound = false;
            Node newSelectedNode = null;

            foreach (Vertex v in graphLayout.vertices)
            {
                Vector2 nodeCenter = ScaleVertex(v.position, offset, scale) - nodeSize / 2;
                var nodeRect = new Rect(nodeCenter.x, nodeCenter.y, nodeSize.x, nodeSize.y);

                bool clicked = false;
                if (currentEvent.type == EventType.MouseUp && currentEvent.button == 0)
                {
                    Vector2 mousePos = currentEvent.mousePosition;
                    if (nodeRect.Contains(mousePos))
                    {
                        clicked = true;
                        currentEvent.Use();
                    }
                }

                bool currentSelection = (m_SelectedNode != null)
                    && v.node.content.Equals(m_SelectedNode.content); // Make sure to use Equals() and not == to call any overriden comparison operator in the content type.

                DrawNode(nodeRect, v.node, currentSelection || clicked);

                if (currentSelection)
                {
                    // Previous selection still there.
                    oldSelectionFound = true;
                }
                else if (clicked)
                {
                    // Just Selected a new node.
                    newSelectedNode = v.node;
                }
            }

            if (newSelectedNode != null)
            {
                m_SelectedNode = newSelectedNode;

                if (nodeClicked != null)
                    nodeClicked(m_SelectedNode);
            }
            else if (!oldSelectionFound)
            {
                m_SelectedNode = null;
            }

            GUI.EndGroup();
        }

主要是绘制连接线和对应的结点,分别在两个主要方法DrawEdge和DrawNode中。

PlayableNodes

SharedPlayableNode  针对内容做部分个性化输出显示

PlayableNode

PlayableOutputNode

AnimationClipPlayableNode

AnimationLayerMixerPlayableNode

PlayableGraphVisualizer

继承自Graph类,复写Populate方法,主要是根据传入的PlayableGraph对象,获取它的所有输出的PlayableOutput对象,并调用基类的AddNodeHierarchy方法将Node暂存起来。

PlayableGraphVisualizerWindow 

菜单项在这里:

        [MenuItem("Window/Analysis/PlayableGraph Visualizer")]
        public static void ShowWindow()
        {
            GetWindow<PlayableGraphVisualizerWindow>("PlayableGraph Visualizer");
        }

创建完成之后在OnEnable方法中,获取到所有的PlayableGraph

m_Graphs = new List<PlayableGraph>(UnityEditor.Playables.Utility.GetAllGraphs());

在OnGUI中完成绘制:

首先去获取当前的selectedGraphs。

通过下拉框确定当前选中的graph:

 m_CurrentGraph = GetSelectedGraphInToolBar(selectedGraphs, m_CurrentGraph);

初始化PlayableGraphVisualizer,并调用它的Refresh方法:

 var graph = new PlayableGraphVisualizer(m_CurrentGraph);
 graph.Refresh();

初始化布局类

     if (m_Layout == null)
                m_Layout = new ReingoldTilford();

调用其计算方法

 m_Layout.CalculateLayout(graph);

初始化渲染类

        if (m_Renderer == null)
                m_Renderer = new DefaultGraphRenderer();

调用其渲染方法

  m_Renderer.Draw(m_Layout, graphRect, m_GraphSettings);

总结:工具够用,但功能偏少,不支持拖拽缩放等功能,对于需要商业化的游戏项目来说,支持力度远远不够。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值