string shortName = type.ToString().Split('.').Last();
float h = (float)Math.Abs(shortName.GetHashCode()) / int.MaxValue;
return Color.HSVToRGB(h, 0.6f, 1.0f);
Edge
Vertex
主要做结点的布局计算相关。主要方法为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递归应用子结点的垂直偏移。
主要记录一些绘制相关的基础设置信息。
根据传入的布局相关信息进行相应的显示。主要关注它的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中。
SharedPlayableNode 针对内容做部分个性化输出显示
PlayableNode
PlayableOutputNode
AnimationClipPlayableNode
AnimationLayerMixerPlayableNode
继承自Graph类,复写Populate方法,主要是根据传入的PlayableGraph对象,获取它的所有输出的PlayableOutput对象,并调用基类的AddNodeHierarchy方法将Node暂存起来。
菜单项在这里:
[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);
总结:工具够用,但功能偏少,不支持拖拽缩放等功能,对于需要商业化的游戏项目来说,支持力度远远不够。