自定义节点编辑器xNode——Node介绍(三)

本文中的项目来源于 https://github.com/Siccity/xNode

1.5 Node介绍

在上一章我们终于了解了NodePort,那么这一章让我们来看看Node的构成

1.5.1 Node - Enum

这一部分的内容在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;

自定义节点编辑器xNode——Node介绍(三)_第1张图片
配上图应该很容易就看出来了吧:

  • a即使连线了也不显示
  • b即使不连线也会显示
  • 而c和d表示了不在连线状态才显示
	///表示该节点连接的类型
    public enum ConnectionType {
        ///可以接受多个节点
        Multiple,
        ///只接受一个节点
        Override,
    }

    ///表示Input能够接受的类型
    public enum TypeConstraint {
        //能够接受所有类型
        None,
        ///能够接受相同和继承类
        Inherited,
        ///只能接受同一类型
        Strict,
    }

这两个就很容易理解,就不多解释了,这一部分的枚举会在特性中用到

1.5.2 Node - Iterate & Dictionary

        ///所有接口的迭代器
        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]);
    }
}

接下来我们就需要函数来获取这些容器中的内容,分为三部分:

  1. Ports
  2. Inputs/Outputs
  3. Dynamic Ports
1.5.2.1 Ports

很简单,字面意思

///返回符合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);
}
1.5.2.2 Inputs/Outputs

在这里就能返回输入输出值

///返回指定接口的输入值,如果接口没有连接就返回默认值
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;
}
1.5.2.3 Dynamic Ports

这些函数是哦那个做运行时使用的,不是作编辑器使用而是做UI等使用,暂且不做介绍

1.5.3 Node - Attributes

这一部分是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;
    }
}

1.5.4 Node - Other

这些函数偶很简单

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)函数我们会在下一张进行介绍,来看一下究竟是如何更新的

你可能感兴趣的:(工具研究)