|
在去年实习摸鱼的时候无聊想着自己写个行为树的节点编辑器,因为之前用过Unity的Shader Graph,就想着说用Graph View 去做这个。不得不说Graph View的官方文档几乎可以说是没有任何用,倒不如说Unity Editor相关的文献都很少,这导致用这个的时候得去查阅很多资料。当时做完了想着把一些遇到的没人问的问题总结一下,结果咕了大半年……
关于GraphView的基础使用知乎有几篇文章已经讲得很全了,入门可以参考。想学习GraphView更多的一些东西可以考虑直接去看Shader Graph源码,虽然代码量很多。以下着重讲一下我在使用GraphView过程中遇到的一些问题和解决方法。
首先是Graph的拖动和缩放问题,如果不做处理的话,Graph初始化后不会保存上一次的状态,这会导致你的图初始化后节点的布局出现一些问题,这里可以考虑简单的记录一下数据,并在GraphView初始化的时候去设置。
为了避免图里面节点的布局变化,最好也记录下节点的位置,并在节点初始化时为其设置,简单地调用SetPosition函数就可以实现。
if (data.position != default(Vector3) && data.scale != default(Vector3))
{
contentViewContainer.transform.position = data.position;
contentViewContainer.transform.scale = data.scale;
}
GraphView里面的节点创建基本是通过右键打开搜索菜单进行创建的,默认的创建位置并不是在鼠标附近,可能会在一个很远的位置,此时需要在搜索菜单创建节点完成时的回调里面去根据当前鼠标的位置设置节点的位置
private bool OnMenuSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
{
var windowRoot = window.rootVisualElement;
var windowMousePosition = windowRoot.ChangeCoordinatesTo(windowRoot.parent, context.screenMousePosition - window.position.position);
var graphMousePosition = contentViewContainer.WorldToLocal(windowMousePosition);
var type = searchTreeEntry.userData as Type;
CreateNode(type, new Rect(graphMousePosition, Vector2.zero), Data.NodeList.Count, false, out T3 node);
return true;
}
关于两个节点的连接,实际上是由两个Port和一条Edge实现的连接,通常我们会去记录节点之间的连接关系,好像是默认的连接方法不能提供这样的处理(有点记不清了,总之无法达到上述目的)这里参照了Shader Graph里面的写法,实现端口间的连接。首先是实现IEdgeConnectorListener的监听类,这个用于处理Edge
Drop(连接)和Drop out的情况。
/// <summary>
/// 端口连接时的处理函数
/// </summary>
public class EdgeConnectorListener : IEdgeConnectorListener
{
protected BaseNodeData parentData,childData;
public virtual void OnDropOutsidePort(Edge edge, Vector2 position)
{
var leftPort = edge.output;
var rightPort = edge.input;
if(leftPort!=null && rightPort !=null)
{
if (leftPort.node.userData != null && rightPort.node.userData != null)
{
parentData = (BaseNodeData)leftPort.node.userData;
childData = (BaseNodeData)rightPort.node.userData;
parentData.child.Remove(childData.id);
childData.parent.Remove(parentData.id);
}
}
}
public virtual void OnDrop(GraphView graphView, Edge edge)
{
var leftPort = edge.output;
var rightPort = edge.input;
if (leftPort != null && rightPort != null && !rightPort.connected)
{
Edge newEdge = leftPort.ConnectTo<Edge>(rightPort);
graphView.AddElement(newEdge);
if(leftPort.node.userData!=null&&rightPort.node.userData!=null)
{
parentData = (BaseNodeData)leftPort.node.userData;
childData = (BaseNodeData)rightPort.node.userData;
parentData.child.Add(childData.id);
childData.parent.Add(parentData.id);
}
}
}
}然后实现新的Port类,使其创建时使用EdgeConnectorListener处理其连接
public class GraphPort : Port
{
private GraphPort(Orientation portOrientation, Direction portDirection, Capacity portCapacity, Type type) : base(portOrientation, portDirection, portCapacity, type)
{
}
public static Port Create(Orientation portOrientation, Direction portDirection, Capacity portCapacity, IEdgeConnectorListener connectorListener)
{
var port = new GraphPort(portOrientation, portDirection, portCapacity, typeof(Port))
{
m_EdgeConnector = new EdgeConnector<Edge>(connectorListener),
};
port.AddManipulator(port.m_EdgeConnector);
return port;
}
}最后在创建Node的时候使用新的Port的创建方法即可。 |
|