From 49b25e755b70ec412feaaf0b898d6f7e09d2bea6 Mon Sep 17 00:00:00 2001 From: chai Date: Tue, 28 Jun 2022 09:40:37 +0800 Subject: +node example --- .../Assets/xNode-examples/Scripts/Node.cs | 414 +++++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs (limited to 'Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Node.cs') 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 { + /// + /// Base class for all nodes + /// + /// + /// Classes extending this class will be considered as valid nodes by xNode. + /// + /// [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; + /// } + /// } + /// + /// + [Serializable] + public abstract class Node : ScriptableObject { + /// Used by and to determine when to display the field value associated with a + public enum ShowBackingValue { + /// Never show the backing value + Never, + /// Show the backing value only when the port does not have any active connections + Unconnected, + /// Always show the backing value + Always + } + + public enum ConnectionType { + /// Allow multiple connections + Multiple, + /// always override the current connection + Override, + } + + /// Tells which types of input to allow + public enum TypeConstraint { + /// Allow all types of input + None, + /// Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object) + Inherited, + /// Allow only similar types + Strict, + /// Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject) + InheritedInverse, + } + +#region Obsolete + [Obsolete("Use DynamicPorts instead")] + public IEnumerable InstancePorts { get { return DynamicPorts; } } + + [Obsolete("Use DynamicOutputs instead")] + public IEnumerable InstanceOutputs { get { return DynamicOutputs; } } + + [Obsolete("Use DynamicInputs instead")] + public IEnumerable 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 + + /// Iterate over all ports on this node. + public IEnumerable Ports { get { foreach (NodePort port in ports.Values) yield return port; } } + /// Iterate over all outputs on this node. + public IEnumerable Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } + /// Iterate over all inputs on this node. + public IEnumerable Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } + /// Iterate over all dynamic ports on this node. + public IEnumerable DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } + /// Iterate over all dynamic outputs on this node. + public IEnumerable DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } + /// Iterate over all dynamic inputs on this node. + public IEnumerable DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } + /// Parent + [SerializeField] public NodeGraph graph; + /// Position on the + [SerializeField] public Vector2 position; + /// It is recommended not to modify these at hand. Instead, see and + [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); + + /// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable + public static NodeGraph graphHotfix; + + protected void OnEnable() { + if (graphHotfix != null) graph = graphHotfix; + graphHotfix = null; + UpdatePorts(); + Init(); + } + + /// 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. + public void UpdatePorts() { + NodeDataCache.UpdatePorts(this, ports); + } + + /// Initialize node. Called on enable. + protected virtual void Init() { } + + /// Checks all connections for invalid references, and removes them. + public void VerifyConnections() { + foreach (NodePort port in Ports) port.VerifyConnections(); + } + +#region Dynamic Ports + /// Convenience function. + /// + /// + 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); + } + + /// Convenience function. + /// + /// + 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); + } + + /// Add a dynamic, serialized port to this node. + /// + /// + 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; + } + + /// Remove an dynamic port from the node + public void RemoveDynamicPort(string fieldName) { + NodePort dynamicPort = GetPort(fieldName); + if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); + RemoveDynamicPort(GetPort(fieldName)); + } + + /// Remove an dynamic port from the node + 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); + } + + /// Removes all dynamic ports from the node + [ContextMenu("Clear Dynamic Ports")] + public void ClearDynamicPorts() { + List dynamicPorts = new List(DynamicPorts); + foreach (NodePort port in dynamicPorts) { + RemoveDynamicPort(port); + } + } +#endregion + +#region Ports + /// Returns output port which matches fieldName + public NodePort GetOutputPort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.direction != NodePort.IO.Output) return null; + else return port; + } + + /// Returns input port which matches fieldName + public NodePort GetInputPort(string fieldName) { + NodePort port = GetPort(fieldName); + if (port == null || port.direction != NodePort.IO.Input) return null; + else return port; + } + + /// Returns port which matches fieldName + 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 + /// Return input value for a specified port. Returns fallback value if no ports are connected + /// Field name of requested input port + /// If no ports are connected, this value will be returned + public T GetInputValue(string fieldName, T fallback = default(T)) { + NodePort port = GetPort(fieldName); + if (port != null && port.IsConnected) return port.GetInputValue(); + else return fallback; + } + + /// Return all input values for a specified port. Returns fallback value if no ports are connected + /// Field name of requested input port + /// If no ports are connected, this value will be returned + public T[] GetInputValues(string fieldName, params T[] fallback) { + NodePort port = GetPort(fieldName); + if (port != null && port.IsConnected) return port.GetInputValues(); + else return fallback; + } + + /// Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. + /// The requested port. + public virtual object GetValue(NodePort port) { + Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType()); + return null; + } +#endregion + + /// Called after a connection between two s is created + /// Output Input + public virtual void OnCreateConnection(NodePort from, NodePort to) { } + + /// Called after a connection is removed from this port + /// Output or Input + public virtual void OnRemoveConnection(NodePort port) { } + + /// Disconnect everything from this node + public void ClearConnections() { + foreach (NodePort port in Ports) port.ClearConnections(); + } + +#region Attributes + /// Mark a serializable field as an input port. You can access this through + [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; + + /// Mark a serializable field as an input port. You can access this through + /// Should we display the backing value for this port as an editor field? + /// Should we allow multiple connections? + /// Constrains which input connections can be made to this port + /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays + 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; + } + } + + /// Mark a serializable field as an output port. You can access this through + [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; + + /// Mark a serializable field as an output port. You can access this through + /// Should we display the backing value for this port as an editor field? + /// Should we allow multiple connections? + /// Constrains which input connections can be made from this port + /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays + 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; + } + + /// Mark a serializable field as an output port. You can access this through + /// Should we display the backing value for this port as an editor field? + /// Should we allow multiple connections? + /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays + [Obsolete("Use constructor with TypeConstraint")] + public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { } + } + + /// Manually supply node class with a context menu path + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CreateNodeMenuAttribute : Attribute { + public string menuName; + public int order; + /// Manually supply node class with a context menu path + /// Path to this node in the context menu. Null or empty hides it. + public CreateNodeMenuAttribute(string menuName) { + this.menuName = menuName; + this.order = 0; + } + + /// Manually supply node class with a context menu path + /// Path to this node in the context menu. Null or empty hides it. + /// The order by which the menu items are displayed. + public CreateNodeMenuAttribute(string menuName, int order) { + this.menuName = menuName; + this.order = order; + } + } + + /// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph + [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; + /// Prevents Node of the same type to be added more than once (configurable) to a NodeGraph + /// How many nodes to allow. Defaults to 1. + public DisallowMultipleNodesAttribute(int max = 1) { + this.max = max; + } + } + + /// Specify a color for this node type + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class NodeTintAttribute : Attribute { + public Color color; + /// Specify a color for this node type + /// Red [0.0f .. 1.0f] + /// Green [0.0f .. 1.0f] + /// Blue [0.0f .. 1.0f] + public NodeTintAttribute(float r, float g, float b) { + color = new Color(r, g, b); + } + + /// Specify a color for this node type + /// HEX color value + public NodeTintAttribute(string hex) { + ColorUtility.TryParseHtmlString(hex, out color); + } + + /// Specify a color for this node type + /// Red [0 .. 255] + /// Green [0 .. 255] + /// Blue [0 .. 255] + public NodeTintAttribute(byte r, byte g, byte b) { + color = new Color32(r, g, b, byte.MaxValue); + } + } + + /// Specify a width for this node type + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class NodeWidthAttribute : Attribute { + public int width; + /// Specify a width for this node type + /// Width + public NodeWidthAttribute(int width) { + this.width = width; + } + } +#endregion + + [Serializable] private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver { + [SerializeField] private List keys = new List(); + [SerializeField] private List values = new List(); + + public void OnBeforeSerialize() { + keys.Clear(); + values.Clear(); + foreach (KeyValuePair 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]); + } + } + } +} -- cgit v1.1-26-g67d0