diff --git a/Example/ExampleNodeGraph.asset b/Example/ExampleNodeGraph.asset index 01543d1..a50d83c 100644 --- a/Example/ExampleNodeGraph.asset +++ b/Example/ExampleNodeGraph.asset @@ -12,9 +12,64 @@ MonoBehaviour: m_Name: ExampleNodeGraph m_EditorClassIdentifier: nodes: - - {fileID: 114729867621534192} - {fileID: 114708853913061688} - {fileID: 114511978881715272} + - {fileID: 114509033286994848} + - {fileID: 114245496101350052} +--- !u!114 &114245496101350052 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 98f6f901f0da53142b79277ea3f42518, type: 3} + m_Name: DisplayValue + m_EditorClassIdentifier: + graph: {fileID: 11400000} + position: {x: -168, y: 8} + ports: + keys: + - input + values: + - _fieldName: input + _node: {fileID: 114245496101350052} + _typeQualifiedName: System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + connections: + - fieldName: result + node: {fileID: 114511978881715272} + _direction: 0 + _connectionType: 1 + _dynamic: 1 +--- !u!114 &114509033286994848 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 98f6f901f0da53142b79277ea3f42518, type: 3} + m_Name: DisplayValue + m_EditorClassIdentifier: + graph: {fileID: 11400000} + position: {x: 72, y: -72} + ports: + keys: + - input + values: + - _fieldName: input + _node: {fileID: 114509033286994848} + _typeQualifiedName: System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + connections: + - fieldName: vector + node: {fileID: 114708853913061688} + _direction: 0 + _connectionType: 1 + _dynamic: 1 --- !u!114 &114511978881715272 MonoBehaviour: m_ObjectHideFlags: 0 @@ -27,7 +82,7 @@ MonoBehaviour: m_Name: MathNode m_EditorClassIdentifier: graph: {fileID: 11400000} - position: {x: -401.5, y: -119} + position: {x: -472, y: -120} ports: keys: - a @@ -40,6 +95,7 @@ MonoBehaviour: PublicKeyToken=b77a5c561934e089 connections: [] _direction: 0 + _connectionType: 0 _dynamic: 0 - _fieldName: b _node: {fileID: 114511978881715272} @@ -47,6 +103,7 @@ MonoBehaviour: PublicKeyToken=b77a5c561934e089 connections: [] _direction: 0 + _connectionType: 0 _dynamic: 0 - _fieldName: result _node: {fileID: 114511978881715272} @@ -55,7 +112,10 @@ MonoBehaviour: connections: - fieldName: x node: {fileID: 114708853913061688} + - fieldName: input + node: {fileID: 114245496101350052} _direction: 1 + _connectionType: 0 _dynamic: 0 a: 6.48 b: 7.59 @@ -73,7 +133,7 @@ MonoBehaviour: m_Name: Vector m_EditorClassIdentifier: graph: {fileID: 11400000} - position: {x: -157.5, y: -115} + position: {x: -168, y: -120} ports: keys: - x @@ -89,6 +149,7 @@ MonoBehaviour: - fieldName: result node: {fileID: 114511978881715272} _direction: 0 + _connectionType: 0 _dynamic: 0 - _fieldName: y _node: {fileID: 114708853913061688} @@ -96,6 +157,7 @@ MonoBehaviour: PublicKeyToken=b77a5c561934e089 connections: [] _direction: 0 + _connectionType: 0 _dynamic: 0 - _fieldName: z _node: {fileID: 114708853913061688} @@ -103,6 +165,7 @@ MonoBehaviour: PublicKeyToken=b77a5c561934e089 connections: [] _direction: 0 + _connectionType: 0 _dynamic: 0 - _fieldName: vector _node: {fileID: 114708853913061688} @@ -110,44 +173,11 @@ MonoBehaviour: Culture=neutral, PublicKeyToken=null connections: - fieldName: input - node: {fileID: 114729867621534192} + node: {fileID: 114509033286994848} _direction: 1 + _connectionType: 0 _dynamic: 0 x: 0 y: 2.6412349 z: 14.33477 vector: {x: 14.07, y: 2.6412349, z: 14.33477} ---- !u!114 &114729867621534192 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 98f6f901f0da53142b79277ea3f42518, type: 3} - m_Name: DisplayValue - m_EditorClassIdentifier: - graph: {fileID: 11400000} - position: {x: 104.5, y: -82} - ports: - keys: - - value - - input - values: - - _fieldName: value - _node: {fileID: 114729867621534192} - _typeQualifiedName: System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - connections: [] - _direction: 0 - _dynamic: 0 - - _fieldName: input - _node: {fileID: 114729867621534192} - _typeQualifiedName: System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - connections: - - fieldName: vector - node: {fileID: 114708853913061688} - _direction: 0 - _dynamic: 1 diff --git a/Example/Nodes/DisplayValue.cs b/Example/Nodes/DisplayValue.cs index 5340a99..3f5a317 100644 --- a/Example/Nodes/DisplayValue.cs +++ b/Example/Nodes/DisplayValue.cs @@ -4,7 +4,7 @@ namespace BasicNodes { public class DisplayValue : XNode.Node { protected override void Init() { base.Init(); - if (!HasPort("input")) AddInstanceInput(typeof(object), "input"); + if (!HasPort("input")) AddInstanceInput(typeof(object), ConnectionType.Override ,"input"); } public override object GetValue(XNode.NodePort port) { diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 9cc7bb3..223e93b 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -15,6 +15,13 @@ namespace XNode { Always } + public enum ConnectionType { + /// Allow multiple connections + Multiple, + /// always override the current connection + Override, + } + /// 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. @@ -49,16 +56,16 @@ namespace XNode { #region Instance Ports /// Returns input port at index - public NodePort AddInstanceInput(Type type, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Input, fieldName); + public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) { + return AddInstancePort(type, NodePort.IO.Input, connectionType, fieldName); } /// Returns input port at index - public NodePort AddInstanceOutput(Type type, string fieldName = null) { - return AddInstancePort(type, NodePort.IO.Output, fieldName); + public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) { + return AddInstancePort(type, NodePort.IO.Output, connectionType, fieldName); } - private NodePort AddInstancePort(Type type, NodePort.IO direction, string fieldName = null) { + private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) { if (fieldName == null) { fieldName = "instanceInput_0"; int i = 0; @@ -67,7 +74,7 @@ namespace XNode { Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); return ports[fieldName]; } - NodePort port = new NodePort(fieldName, type, direction, this); + NodePort port = new NodePort(fieldName, type, direction, connectionType, this); ports.Add(fieldName, port); return port; } @@ -168,20 +175,30 @@ namespace XNode { [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class InputAttribute : Attribute { public ShowBackingValue backingValue; - + public ConnectionType connectionType; + /// 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; } + /// Should we allow multiple connections? + public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple) { + this.backingValue = backingValue; + this.connectionType = connectionType; + } } /// 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; + public ConnectionType connectionType; /// 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; } + /// Should we allow multiple connections? + public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple) { + this.backingValue = backingValue; + this.connectionType = connectionType; + } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index 8c9cea6..fe9f50d 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -13,6 +13,8 @@ namespace XNode { public NodePort Connection { get { return connections.Count > 0 ? connections[0].Port : null; } } public IO direction { get { return _direction; } } + public Node.ConnectionType connectionType { get { return _connectionType; } } + /// Is this port connected to anytihng? public bool IsConnected { get { return connections.Count != 0; } } public bool IsInput { get { return direction == IO.Input; } } @@ -39,6 +41,7 @@ namespace XNode { [SerializeField] private string _typeQualifiedName; [SerializeField] private List connections = new List(); [SerializeField] private IO _direction; + [SerializeField] private Node.ConnectionType _connectionType; [SerializeField] private bool _dynamic; /// Construct a static targetless nodeport. Used as a template. @@ -48,8 +51,14 @@ namespace XNode { _dynamic = false; var attribs = fieldInfo.GetCustomAttributes(false); for (int i = 0; i < attribs.Length; i++) { - if (attribs[i] is Node.InputAttribute) _direction = IO.Input; - else if (attribs[i] is Node.OutputAttribute) _direction = IO.Output; + if (attribs[i] is Node.InputAttribute) { + _direction = IO.Input; + _connectionType = (attribs[i] as Node.InputAttribute).connectionType; + } + else if (attribs[i] is Node.OutputAttribute) { + _direction = IO.Output; + _connectionType = (attribs[i] as Node.OutputAttribute).connectionType; + } } } @@ -59,16 +68,18 @@ namespace XNode { ValueType = nodePort.valueType; _direction = nodePort.direction; _dynamic = nodePort._dynamic; + _connectionType = nodePort._connectionType; _node = node; } /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. - public NodePort(string fieldName, Type type, IO direction, Node node) { + public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node node) { _fieldName = fieldName; this.ValueType = type; _direction = direction; _node = node; _dynamic = true; + _connectionType = connectionType; } /// Checks all connections for invalid references, and removes them. @@ -176,6 +187,8 @@ namespace XNode { if (port == this) { Debug.LogWarning("Attempting to connect port to self."); return; } if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } + if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } + if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } connections.Add(new PortConnection(port)); if (port.connections == null) port.connections = new List(); if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this));