using System; using System.Collections.Generic; using UnityEngine; namespace XNode { /// Base class for all nodes [Serializable] public abstract class Node : ScriptableObject { 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 } /// 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 instane ports on this node. public IEnumerable InstancePorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } /// Iterate over all instance outputs on this node. public IEnumerable InstanceOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } /// Iterate over all instance inputs on this node. public IEnumerable InstanceInputs { 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; /// Input s. It is recommended not to modify these at hand. Instead, see [SerializeField] private NodePortDictionary ports = new NodePortDictionary(); protected void OnEnable() { NodeDataCache.UpdatePorts(this, ports); Init(); } /// Initialize node. Called on creation. protected virtual void Init() { name = GetType().Name; } /// Checks all connections for invalid references, and removes them. public void VerifyConnections() { foreach (NodePort port in Ports) port.VerifyConnections(); } #region Instance Ports /// Returns input port at index public NodePort AddInstanceInput(Type type, string fieldName = null) { return AddInstancePort(type, NodePort.IO.Input, fieldName); } /// Returns input port at index public NodePort AddInstanceOutput(Type type, string fieldName = null) { return AddInstancePort(type, NodePort.IO.Output, fieldName); } private NodePort AddInstancePort(Type type, NodePort.IO direction, string fieldName = null) { if (fieldName == null) { fieldName = "instanceInput_0"; int i = 0; while (HasPort(fieldName)) fieldName = "instanceInput_" + (++i); } else if (HasPort(fieldName)) { Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); return ports[fieldName]; } NodePort port = new NodePort(fieldName, type, direction, this); ports.Add(fieldName, port); return port; } /// Remove an instance port from the node public void RemoveInstancePort(string fieldName) { RemoveInstancePort(GetPort(fieldName)); } /// Remove an instance port from the node public void RemoveInstancePort(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 instance ports from the node [ContextMenu("Clear instance ports")] public void ClearInstancePorts() { foreach (NodePort port in InstancePorts) { RemoveInstancePort(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) { if (ports.ContainsKey(fieldName)) return ports[fieldName]; 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 before used. /// 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 Input public virtual void OnRemoveConnection(NodePort port) { } /// Disconnect everything from this node public void ClearConnections() { foreach (NodePort port in Ports) port.ClearConnections(); } public override int GetHashCode() { return JsonUtility.ToJson(this).GetHashCode(); } /// Mark a serializable field as an input port. You can access this through [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class InputAttribute : Attribute { public ShowBackingValue backingValue; /// 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? public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected) { this.backingValue = backingValue; } } /// Mark a serializable field as an output port. You can access this through [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class OutputAttribute : Attribute { public ShowBackingValue backingValue; /// 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? public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never) { this.backingValue = backingValue; } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class CreateNodeMenuAttribute : Attribute { public string menuName; /// Manually supply node class with a context menu path /// Path to this node in the context menu public CreateNodeMenuAttribute(string menuName) { this.menuName = menuName; } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class NodeTint : 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 NodeTint(float r, float g, float b) { color = new Color(r, g, b); } /// Specify a color for this node type /// HEX color value public NodeTint(string hex) { ColorUtility.TryParseHtmlString(hex, out color); } /// Specify a color for this node type /// Red [0 .. 255] /// Green [0 .. 255] /// Blue [0 .. 255] public NodeTint(byte r, byte g, byte b) { color = new Color32(r, g, b, byte.MaxValue); } } [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]); } } } }