GraphView介绍
GraphView是Unity推出的一个基于UIElement的节点编辑器UI模块,功能很完全,有多选,拖动,缩放,Group等功能。
GraphView的基本使用(一)
图形视图由四个主要元素组成。
- GraphView
- Node
- Port
- Edge
创建编辑器窗口
首先,创建编辑器扩展熟悉的编辑器窗口。
using UnityEditor;
public class SampleGraphEditorWindow : EditorWindow
{
[MenuItem("Window/Open SampleGraphView")]
public static void Open()
{
GetWindow<SampleGraphEditorWindow>("SampleGraphView");
}
}
创建图形视图
接下来,我们将立即创建图形视图,这是节点和边的父级。
using UnityEditor.Experimental.GraphView;
public class SampleGraphView : GraphView
{
}
此外,在此处向编辑器窗口SampleGraphEditorWindow.cs添加图形视图。
void OnEnable()
{
rootVisualElement.Add(new SampleGraphView());
}
创建节点
尽管 GraphView 通常称为节点编辑器,但Node 顾名思义,它是节点编辑器中最重要的部分。
创建基础节点,在实际使用时继承此节点。
using UnityEditor.Experimental.GraphView;
public class SampleNode : Node
{
}
在构造函数中完成将此节点添加到图形视图。
public SampleGraphView() : base()
{
AddElement(new SampleNode());
}
设置高度,使其正确显示。
private void OnEnable()
{
rootVisualElement.Add(new SampleGraphView()
{
style = { flexGrow = 1}
});
}
可以看到显示窗口中已经显示出节点了。
为节点添加端口
节点可以与其他节点连接,可以将边从输出端口连接到输入端口。
public SampleNode()
{
title = "Sample";
var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Port));
inputContainer.Add(inputPort);
var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
outputContainer.Add(outputPort);
}
使节点能够移动
使用SelectionDragger
做AddManipulator。
public SampleGraphView() : base()
{
AddElement(new SampleNode());
this.AddManipulator(new SelectionDragger());
}
创建多个节点
可以从右键单击菜单中添加新节点。
public SampleGraphView() : base()
{
this.AddManipulator(new SelectionDragger());
nodeCreationRequest += context =>
{
AddElement(new SampleNode());
};
}
连接节点
在SampleGraphView中重写GetCompatiblePorts并返回正确的端口。
public override List<Port> GetCompatiblePorts(Port startAnchor, NodeAdapter nodeAdapter)
{
return ports.ToList();
}
可以看到已经能连接节点了。
放大和缩小
只需在图形视图中调用SetupZoom
即可放大和缩小。
public SampleGraphView() : base()
{
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
this.AddManipulator(new SelectionDragger());
nodeCreationRequest += context =>
{
AddElement(new SampleNode());
};
}
更改背景颜色
因为网格背景出现在图形视图的元素之前,所以需要在添加视图显示元素之前调用Insert方法。
public SampleGraphView() : base()
{
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
Insert(0, new GridBackground());
this.AddManipulator(new SelectionDragger());
nodeCreationRequest += context =>
{
AddElement(new SampleNode());
};
}
实现节点
创建派生自基类节点的具体业务节点。创建如下节点为:
- 根节点
- 日志记录节点
- 字符串输出节点
现在,我们将示例节点作为抽象类,并且继承它。
using UnityEditor.Experimental.GraphView;
public abstract class SampleNode : Node
{
}
首先,创建日志处理节点。
using UnityEditor.Experimental.GraphView;
public class ProcessNode : SampleNode
{
public ProcessNode()
{
var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Port));
inputPort.portName = "In";
inputContainer.Add(inputPort);
var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
outputPort.portName = "Out";
outputContainer.Add(outputPort);
}
}
继承此节点创建日志节点。
using UnityEditor.Experimental.GraphView;
public class LogNode : ProcessNode
{
public LogNode() : base()
{
title = "Log";
var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(string));
inputContainer.Add(inputPort);
}
}
创建字符串输出节点。
using UnityEngine.UIElements;
using UnityEditor.Experimental.GraphView;
public class StringNode : SampleNode
{
private TextField textField;
public string Text { get { return textField.value; } }
public StringNode() : base()
{
title = "String";
var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof(string));
outputContainer.Add(outputPort);
textField = new TextField();
mainContainer.Add(textField);
}
}
最后,创建根节点。
由于根节点不消失,请消除相应的委托。
using UnityEditor.Experimental.GraphView;
public class RootNode : SampleNode
{
public RootNode() : base()
{
title = "Root";
capabilities -= Capabilities.Deletable;
var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
outputPort.portName = "Out";
outputContainer.Add(outputPort);
}
}
在生成图形视图时,就立刻放置一个根节点。
root = new RootNode();
AddElement(root);
选择并创建任何节点
使用搜索窗口,可以轻松地创建允许选择节点的 UI。
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.Experimental.GraphView;
public class SampleSearchWindowProvider : ScriptableObject, ISearchWindowProvider
{
private SampleGraphView graphView;
public void Initialize(SampleGraphView graphView)
{
this.graphView = graphView;
}
List<SearchTreeEntry> ISearchWindowProvider.CreateSearchTree(SearchWindowContext context)
{
var entries = new List<SearchTreeEntry>();
entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node")));
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.IsClass && !type.IsAbstract && (type.IsSubclassOf(typeof(SampleNode)))
&& type != typeof(RootNode))
{
entries.Add(new SearchTreeEntry(new GUIContent(type.Name)) { level = 1, userData = type });
}
}
}
return entries;
}
bool ISearchWindowProvider.OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
{
var type = searchTreeEntry.userData as System.Type;
var node = Activator.CreateInstance(type) as SampleNode;
graphView.AddElement(node);
return true;
}
}
创建搜索树将继承示例节点类的类返回到搜索树条目,并处理OnSelectEntry 中选择的项。
右键单击更改您创建节点的位置,以调用搜索窗口。
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
public class SampleGraphView : GraphView
{
public RootNode root;
public SampleGraphView()
{
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
Insert(0, new GridBackground());
//AddElement(new SampleNode());
this.AddManipulator(new SelectionDragger());
var searchWindowProvider = ScriptableObject.CreateInstance<SampleSearchWindowProvider>();
searchWindowProvider.Initialize(this);
root = new RootNode();
AddElement(root);
nodeCreationRequest += context =>
{
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchWindowProvider);
};
}
public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
{
return ports.ToList();
}
}
现在,就可以选择和创建任何节点。
确保只有正确的端口才能彼此连接
由于 UI 实现可以连接所有节点,因此我们将修复它。
public override List<Port> GetCompatiblePorts(Port startAnchor, NodeAdapter nodeAdapter)
{
var compatiblePorts = new List<Port>();
foreach (var port in ports.ToList())
{
if (startAnchor.node == port.node ||
startAnchor.direction == port.direction ||
startAnchor.portType != port.portType)
{
continue;
}
compatiblePorts.Add(port);
}
return compatiblePorts;
}
现在
- 无法连接到同一节点
- 从输入到输入,从输出到输出,不连接
- 端口上配置的类型不匹配,无法连接
实际使用节点进行处理
实际处理时,必须按顺序获取连接到根节点的节点并执行操作。
由于没有获取连接到节点的另一个节点的功能,因此在生成端口时必须缓存它。
此外,由于我们希望在节点端描述处理,因此我们将让进程节点具有用于处理的方法。
using UnityEditor.Experimental.GraphView;
public abstract class ProcessNode : SampleNode
{
public Port InputPort;
public Port OutputPort;
public ProcessNode()
{
InputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(Port));
InputPort.portName = "In";
inputContainer.Add(InputPort);
OutputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
OutputPort.portName = "Out";
outputContainer.Add(OutputPort);
}
public abstract void Execute();
}
using System.Linq;
using UnityEngine;
using UnityEditor.Experimental.GraphView;
public class LogNode : ProcessNode
{
private Port inputString;
public LogNode() : base()
{
title = "Log";
inputString = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(string));
inputContainer.Add(inputString);
}
public override void Execute()
{
var edge = inputString.connections.FirstOrDefault();
var node = edge.output.node as StringNode;
if (node == null) return;
Debug.Log(node.Text);
}
}
using UnityEditor.Experimental.GraphView;
public class RootNode : SampleNode
{
public Port OutputPort;
public RootNode() : base()
{
title = "Root";
capabilities -= Capabilities.Deletable;
OutputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(Port));
OutputPort.portName = "Out";
outputContainer.Add(OutputPort);
}
}
现在,我们从根开始获取节点并执行操作。
using System.Linq
-------------------------------------
public void Execute()
{
var rootEdge = root.OutputPort.connections.FirstOrDefault();
if (rootEdge == null) return;
var currentNode = rootEdge.input.node as ProcessNode;
while (true)
{
currentNode.Execute();
var edge = currentNode.OutputPort.connections.FirstOrDefault();
if (edge == null) break;
currentNode = edge.input.node as ProcessNode;
}
}
绘制一个按钮,执行此方法。
void OnEnable()
{
var graphView = new SampleGraphView()
{
style = { flexGrow = 1 }
};
rootVisualElement.Add(graphView);
rootVisualElement.Add(new Button(graphView.Execute) { text = "Execute" });
}
执行结果如下: