本文中的项目来源于 https://github.com/Siccity/xNode
在上一章我们终于了解了NodePort,那么这一章让我们来看看Node的构成
这一部分的内容在NodePort中我们已经看到过了,我们来具体了解一下
[Serializable]
public abstract class Node : ScriptableObject {
///这两个枚举是在[Input]和[Output]中使用的,用来决定节点(NodePort)被连接后的显示方式
public enum ShowBackingValue {
///永远不显示
Never,
///只有在不连接的时候才显示
Unconnected,
///永远显示
Always
}
在这里给大家举个栗子,现在有四个节点abcd,如下:
//默认是ShowBackingValue.Unconnected
[Input(backingValue = ShowBackingValue.Never)] public float a;
[Input(backingValue = ShowBackingValue.Always)] public float b;
[Input] public float c;
[Input] public float d;
///表示该节点连接的类型
public enum ConnectionType {
///可以接受多个节点
Multiple,
///只接受一个节点
Override,
}
///表示Input能够接受的类型
public enum TypeConstraint {
//能够接受所有类型
None,
///能够接受相同和继承类
Inherited,
///只能接受同一类型
Strict,
}
这两个就很容易理解,就不多解释了,这一部分的枚举会在特性中用到
///所有接口的迭代器
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
///所有输出接口的迭代器
public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
///所有输入接口的迭代器
public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
///所有动态接口的迭代器
public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
///所有动态输出接口的迭代器
public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
///所有动态输入接口的迭代器
public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
///所属的NodeGraph
[SerializeField] public NodeGraph graph;
///在NodeGraph中的位置
[SerializeField] public Vector2 position;
///建议不要着手修改这些东西,相反,可以查看InputAttribute和OutputAttribute
[SerializeField] private NodePortDictionary ports = new NodePortDictionary();
///用于在Node在被初始化调用OnEnable/Init时修复空值/配置错误的问题
///在实例化节点之前设置它,将在OnEnable后自动取消设置
public static NodeGraph graphHotfix;
这一部分提供了一堆的迭代器让我们获取之前我们所说的NodePorts,而这些NodePorts被保存在一个字典中,如下:
它继承了Dictionary类并实现了ISerializationCallbackReceiver序列化的接口(我学到了)
[Serializable]
private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver
{
[SerializeField] private List<string> keys = new List<string>();
[SerializeField] private List<NodePort> values = new List<NodePort>();
public void OnBeforeSerialize()
{
keys.Clear();
values.Clear();
foreach (KeyValuePair<string, NodePort> pair in this)
{
keys.Add(pair.Key);
values.Add(pair.Value);
}
}
public void OnAfterDeserialize()
{
this.Clear();
if (keys.Count != values.Count)
throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
for (int i = 0; i < keys.Count; i++)
this.Add(keys[i], values[i]);
}
}
接下来我们就需要函数来获取这些容器中的内容,分为三部分:
很简单,字面意思
///返回符合fieldName的输出接口
public NodePort GetOutputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Output) return null;
else return port;
}
///返回符合fieldName的输入接口
public NodePort GetInputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Input) return null;
else return port;
}
///返回符合fieldName的接口
public NodePort GetPort(string fieldName) {
NodePort port;
if (ports.TryGetValue(fieldName, out port)) return port;
else return null;
}
///判断是否有符合fieldName的接口
public bool HasPort(string fieldName) {
return ports.ContainsKey(fieldName);
}
在这里就能返回输入输出值
///返回指定接口的输入值,如果接口没有连接就返回默认值
public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValue<T>();
else return fallback;
}
//////返回所有指定接口的输入值,如果接口没有连接就返回默认值
public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValues<T>();
else return fallback;
}
///根据请求的端口输出返回值,应在所有具有输出的派生节点中重写
public virtual object GetValue(NodePort port) {
Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
return null;
}
这些函数是哦那个做运行时使用的,不是作编辑器使用而是做UI等使用,暂且不做介绍
这一部分是xNode在使用时会有用的特性
///标记一个SerializableField作为一个input接口,你可以通过GetInputPort(string)来访问它
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class InputAttribute : Attribute {
public ShowBackingValue backingValue;
public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")]
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList;//如果为真,将显示可重排序的输入列表,而不是单个端口。将自动添加和显示列表和数组的值
public TypeConstraint typeConstraint;
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
}
}
///标记一个SerializableField作为一个output接口,你可以通过GetOutputPort(string)来访问它
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class OutputAttribute : Attribute {
public ShowBackingValue backingValue;
public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")]
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList;
public TypeConstraint typeConstraint;
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
}
[Obsolete("Use constructor with TypeConstraint")]
public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CreateNodeMenuAttribute : Attribute {
public string menuName;
///可以通过菜单来创建节点
public CreateNodeMenuAttribute(string menuName) {
this.menuName = menuName;
}
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeTintAttribute : Attribute {
public Color color;
///使用rgb(0-1)指定节点的颜色
public NodeTintAttribute(float r, float g, float b) {
color = new Color(r, g, b);
}
///使用十六进制指定节点颜色
public NodeTintAttribute(string hex) {
ColorUtility.TryParseHtmlString(hex, out color);
}
///使用rgb(0-255)指定节点的颜色
public NodeTintAttribute(byte r, byte g, byte b) {
color = new Color32(r, g, b, byte.MaxValue);
}
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeWidthAttribute : Attribute {
public int width;
///指定节点宽度
public NodeWidthAttribute(int width) {
this.width = width;
}
}
这些函数偶很简单
protected void OnEnable() {
if (graphHotfix != null) graph = graphHotfix;
graphHotfix = null;
UpdateStaticPorts();
Init();
}
///更新静态端口以反射类的字段,启用时自动发生
public void UpdateStaticPorts() {
NodeDataCache.UpdatePorts(this, ports);
}
///初始化函数
protected virtual void Init() { }
///检查所有的连接,移除不合适的连接
public void VerifyConnections() {
foreach (NodePort port in Ports) port.VerifyConnections();
}
///在NodePort被创建时调用
public virtual void OnCreateConnection(NodePort from, NodePort to) { }
///在NodePort被移除时调用
public virtual void OnRemoveConnection(NodePort port) { }
///清空连接
public void ClearConnections() {
foreach (NodePort port in Ports) port.ClearConnections();
}
这里的NodeDataCache.UpdatePorts(this, ports)函数我们会在下一张进行介绍,来看一下究竟是如何更新的