summaryrefslogtreecommitdiff
path: root/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2022-06-28 09:40:37 +0800
committerchai <chaifix@163.com>2022-06-28 09:40:37 +0800
commit49b25e755b70ec412feaaf0b898d6f7e09d2bea6 (patch)
tree3c5f4260f30d1c2d7196db93153700d7ddec3157 /Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs
parentc92269331692feca2c276649f6c4ee8911f1f859 (diff)
+node example
Diffstat (limited to 'Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs')
-rw-r--r--Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs414
1 files changed, 414 insertions, 0 deletions
diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs
new file mode 100644
index 00000000..8e7a20a1
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs
@@ -0,0 +1,414 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace XNode {
+ /// <summary>
+ /// Base class for all nodes
+ /// </summary>
+ /// <example>
+ /// Classes extending this class will be considered as valid nodes by xNode.
+ /// <code>
+ /// [System.Serializable]
+ /// public class Adder : Node {
+ /// [Input] public float a;
+ /// [Input] public float b;
+ /// [Output] public float result;
+ ///
+ /// // GetValue should be overridden to return a value for any specified output port
+ /// public override object GetValue(NodePort port) {
+ /// return a + b;
+ /// }
+ /// }
+ /// </code>
+ /// </example>
+ [Serializable]
+ public abstract class Node : ScriptableObject {
+ /// <summary> Used by <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> to determine when to display the field value associated with a <see cref="NodePort"/> </summary>
+ public enum ShowBackingValue {
+ /// <summary> Never show the backing value </summary>
+ Never,
+ /// <summary> Show the backing value only when the port does not have any active connections </summary>
+ Unconnected,
+ /// <summary> Always show the backing value </summary>
+ Always
+ }
+
+ public enum ConnectionType {
+ /// <summary> Allow multiple connections</summary>
+ Multiple,
+ /// <summary> always override the current connection </summary>
+ Override,
+ }
+
+ /// <summary> Tells which types of input to allow </summary>
+ public enum TypeConstraint {
+ /// <summary> Allow all types of input</summary>
+ None,
+ /// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary>
+ Inherited,
+ /// <summary> Allow only similar types </summary>
+ Strict,
+ /// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
+ InheritedInverse,
+ }
+
+#region Obsolete
+ [Obsolete("Use DynamicPorts instead")]
+ public IEnumerable<NodePort> InstancePorts { get { return DynamicPorts; } }
+
+ [Obsolete("Use DynamicOutputs instead")]
+ public IEnumerable<NodePort> InstanceOutputs { get { return DynamicOutputs; } }
+
+ [Obsolete("Use DynamicInputs instead")]
+ public IEnumerable<NodePort> InstanceInputs { get { return DynamicInputs; } }
+
+ [Obsolete("Use AddDynamicInput instead")]
+ public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddDynamicInput(type, connectionType, typeConstraint, fieldName);
+ }
+
+ [Obsolete("Use AddDynamicOutput instead")]
+ public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddDynamicOutput(type, connectionType, typeConstraint, fieldName);
+ }
+
+ [Obsolete("Use AddDynamicPort instead")]
+ private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
+ }
+
+ [Obsolete("Use RemoveDynamicPort instead")]
+ public void RemoveInstancePort(string fieldName) {
+ RemoveDynamicPort(fieldName);
+ }
+
+ [Obsolete("Use RemoveDynamicPort instead")]
+ public void RemoveInstancePort(NodePort port) {
+ RemoveDynamicPort(port);
+ }
+
+ [Obsolete("Use ClearDynamicPorts instead")]
+ public void ClearInstancePorts() {
+ ClearDynamicPorts();
+ }
+#endregion
+
+ /// <summary> Iterate over all ports on this node. </summary>
+ public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
+ /// <summary> Iterate over all outputs on this node. </summary>
+ public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
+ /// <summary> Iterate over all inputs on this node. </summary>
+ public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
+ /// <summary> Iterate over all dynamic ports on this node. </summary>
+ public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
+ /// <summary> Iterate over all dynamic outputs on this node. </summary>
+ public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
+ /// <summary> Iterate over all dynamic inputs on this node. </summary>
+ public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
+ /// <summary> Parent <see cref="NodeGraph"/> </summary>
+ [SerializeField] public NodeGraph graph;
+ /// <summary> Position on the <see cref="NodeGraph"/> </summary>
+ [SerializeField] public Vector2 position;
+ /// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary>
+ [SerializeField] private NodePortDictionary ports = new NodePortDictionary();
+
+ /// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
+ public static NodeGraph graphHotfix;
+
+ protected void OnEnable() {
+ if (graphHotfix != null) graph = graphHotfix;
+ graphHotfix = null;
+ UpdatePorts();
+ Init();
+ }
+
+ /// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. </summary>
+ public void UpdatePorts() {
+ NodeDataCache.UpdatePorts(this, ports);
+ }
+
+ /// <summary> Initialize node. Called on enable. </summary>
+ protected virtual void Init() { }
+
+ /// <summary> Checks all connections for invalid references, and removes them. </summary>
+ public void VerifyConnections() {
+ foreach (NodePort port in Ports) port.VerifyConnections();
+ }
+
+#region Dynamic Ports
+ /// <summary> Convenience function. </summary>
+ /// <seealso cref="AddInstancePort"/>
+ /// <seealso cref="AddInstanceOutput"/>
+ public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
+ }
+
+ /// <summary> Convenience function. </summary>
+ /// <seealso cref="AddInstancePort"/>
+ /// <seealso cref="AddInstanceInput"/>
+ public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
+ }
+
+ /// <summary> Add a dynamic, serialized port to this node. </summary>
+ /// <seealso cref="AddDynamicInput"/>
+ /// <seealso cref="AddDynamicOutput"/>
+ private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
+ if (fieldName == null) {
+ fieldName = "dynamicInput_0";
+ int i = 0;
+ while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
+ } else if (HasPort(fieldName)) {
+ Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
+ return ports[fieldName];
+ }
+ NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
+ ports.Add(fieldName, port);
+ return port;
+ }
+
+ /// <summary> Remove an dynamic port from the node </summary>
+ public void RemoveDynamicPort(string fieldName) {
+ NodePort dynamicPort = GetPort(fieldName);
+ if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
+ RemoveDynamicPort(GetPort(fieldName));
+ }
+
+ /// <summary> Remove an dynamic port from the node </summary>
+ public void RemoveDynamicPort(NodePort port) {
+ if (port == null) throw new ArgumentNullException("port");
+ else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
+ port.ClearConnections();
+ ports.Remove(port.fieldName);
+ }
+
+ /// <summary> Removes all dynamic ports from the node </summary>
+ [ContextMenu("Clear Dynamic Ports")]
+ public void ClearDynamicPorts() {
+ List<NodePort> dynamicPorts = new List<NodePort>(DynamicPorts);
+ foreach (NodePort port in dynamicPorts) {
+ RemoveDynamicPort(port);
+ }
+ }
+#endregion
+
+#region Ports
+ /// <summary> Returns output port which matches fieldName </summary>
+ public NodePort GetOutputPort(string fieldName) {
+ NodePort port = GetPort(fieldName);
+ if (port == null || port.direction != NodePort.IO.Output) return null;
+ else return port;
+ }
+
+ /// <summary> Returns input port which matches fieldName </summary>
+ public NodePort GetInputPort(string fieldName) {
+ NodePort port = GetPort(fieldName);
+ if (port == null || port.direction != NodePort.IO.Input) return null;
+ else return port;
+ }
+
+ /// <summary> Returns port which matches fieldName </summary>
+ public NodePort GetPort(string fieldName) {
+ NodePort port;
+ if (ports.TryGetValue(fieldName, out port)) return port;
+ else return null;
+ }
+
+ public bool HasPort(string fieldName) {
+ return ports.ContainsKey(fieldName);
+ }
+#endregion
+
+#region Inputs/Outputs
+ /// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
+ /// <param name="fieldName">Field name of requested input port</param>
+ /// <param name="fallback">If no ports are connected, this value will be returned</param>
+ 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;
+ }
+
+ /// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
+ /// <param name="fieldName">Field name of requested input port</param>
+ /// <param name="fallback">If no ports are connected, this value will be returned</param>
+ 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;
+ }
+
+ /// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary>
+ /// <param name="port">The requested port.</param>
+ public virtual object GetValue(NodePort port) {
+ Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
+ return null;
+ }
+#endregion
+
+ /// <summary> Called after a connection between two <see cref="NodePort"/>s is created </summary>
+ /// <param name="from">Output</param> <param name="to">Input</param>
+ public virtual void OnCreateConnection(NodePort from, NodePort to) { }
+
+ /// <summary> Called after a connection is removed from this port </summary>
+ /// <param name="port">Output or Input</param>
+ public virtual void OnRemoveConnection(NodePort port) { }
+
+ /// <summary> Disconnect everything from this node </summary>
+ public void ClearConnections() {
+ foreach (NodePort port in Ports) port.ClearConnections();
+ }
+
+#region Attributes
+ /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
+ [AttributeUsage(AttributeTargets.Field)]
+ 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;
+
+ /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
+ /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
+ /// <param name="connectionType">Should we allow multiple connections? </param>
+ /// <param name="typeConstraint">Constrains which input connections can be made to this port </param>
+ /// <param name="dynamicPortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param>
+ 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;
+ }
+ }
+
+ /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
+ [AttributeUsage(AttributeTargets.Field)]
+ 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;
+
+ /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
+ /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
+ /// <param name="connectionType">Should we allow multiple connections? </param>
+ /// <param name="typeConstraint">Constrains which input connections can be made from this port </param>
+ /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
+ 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;
+ }
+
+ /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
+ /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
+ /// <param name="connectionType">Should we allow multiple connections? </param>
+ /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
+ [Obsolete("Use constructor with TypeConstraint")]
+ public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { }
+ }
+
+ /// <summary> Manually supply node class with a context menu path </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public class CreateNodeMenuAttribute : Attribute {
+ public string menuName;
+ public int order;
+ /// <summary> Manually supply node class with a context menu path </summary>
+ /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
+ public CreateNodeMenuAttribute(string menuName) {
+ this.menuName = menuName;
+ this.order = 0;
+ }
+
+ /// <summary> Manually supply node class with a context menu path </summary>
+ /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
+ /// <param name="order"> The order by which the menu items are displayed. </param>
+ public CreateNodeMenuAttribute(string menuName, int order) {
+ this.menuName = menuName;
+ this.order = order;
+ }
+ }
+
+ /// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public class DisallowMultipleNodesAttribute : Attribute {
+ // TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node
+ // while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both.
+ public int max;
+ /// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
+ /// <param name="max"> How many nodes to allow. Defaults to 1. </param>
+ public DisallowMultipleNodesAttribute(int max = 1) {
+ this.max = max;
+ }
+ }
+
+ /// <summary> Specify a color for this node type </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public class NodeTintAttribute : Attribute {
+ public Color color;
+ /// <summary> Specify a color for this node type </summary>
+ /// <param name="r"> Red [0.0f .. 1.0f] </param>
+ /// <param name="g"> Green [0.0f .. 1.0f] </param>
+ /// <param name="b"> Blue [0.0f .. 1.0f] </param>
+ public NodeTintAttribute(float r, float g, float b) {
+ color = new Color(r, g, b);
+ }
+
+ /// <summary> Specify a color for this node type </summary>
+ /// <param name="hex"> HEX color value </param>
+ public NodeTintAttribute(string hex) {
+ ColorUtility.TryParseHtmlString(hex, out color);
+ }
+
+ /// <summary> Specify a color for this node type </summary>
+ /// <param name="r"> Red [0 .. 255] </param>
+ /// <param name="g"> Green [0 .. 255] </param>
+ /// <param name="b"> Blue [0 .. 255] </param>
+ public NodeTintAttribute(byte r, byte g, byte b) {
+ color = new Color32(r, g, b, byte.MaxValue);
+ }
+ }
+
+ /// <summary> Specify a width for this node type </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+ public class NodeWidthAttribute : Attribute {
+ public int width;
+ /// <summary> Specify a width for this node type </summary>
+ /// <param name="width"> Width </param>
+ public NodeWidthAttribute(int width) {
+ this.width = width;
+ }
+ }
+#endregion
+
+ [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]);
+ }
+ }
+ }
+}