diff --git a/Examples/Nodes/BaseNode.cs b/Examples/Nodes/BaseNode.cs index 1a7bbd4..1338a36 100644 --- a/Examples/Nodes/BaseNode.cs +++ b/Examples/Nodes/BaseNode.cs @@ -1,16 +1,15 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class BaseNode : Node { - - // Use this for initialization - void Start () { - - } - - // Update is called once per frame - void Update () { - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class BaseNode : Node { + + protected override void Init() { + inputs = new NodePort[2]; + inputs[0] = CreateNodeInput("IntInput", typeof(int)); + inputs[1] = CreateNodeInput("StringInput", typeof(string)); + outputs = new NodePort[1]; + outputs[0] = CreateNodeOutput("StringOutput", typeof(string)); + } +} diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 3c2cbe4..a38e07f 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -12,7 +12,6 @@ public static class NodeEditorAction { public static void Controls(NodeEditorWindow window) { Event e = Event.current; - switch (e.type) { case EventType.ScrollWheel: @@ -22,8 +21,26 @@ public static class NodeEditorAction { case EventType.MouseDrag: if (e.button == 0) { if (window.activeNode != null) { - window.activeNode.position.position = window.WindowToGridPosition(e.mousePosition) + dragOffset; - window.Repaint(); + if (window.hoveredPort != null || window.tempConnection != null) { + if (window.tempConnection == null) { + if (window.hoveredPort.direction == NodePort.IO.Output) { + dragging = true; + int inputNodeId = window.graph.GetNodeId(window.activeNode); + int outputPortId = window.activeNode.GetOutputPortId(window.hoveredPort); + window.tempConnection = new NodeConnection(inputNodeId, outputPortId, -1,-1); + } + else { + Debug.Log("input"); + /*int outputNodeId = window.graph.GetNodeId(window.activeNode); + int outputPortId = window.activeNode.GetInputPortId(window.hoveredPort); + window.tempConnection = new NodeConnection(-1,-1,outputNodeId,outputPortId);*/ + } + } + } + else { + window.activeNode.position.position = window.WindowToGridPosition(e.mousePosition) + dragOffset; + window.Repaint(); + } } } else if (e.button == 1) { @@ -36,6 +53,7 @@ public static class NodeEditorAction { break; case EventType.MouseDown: dragging = false; + window.Repaint(); window.SelectNode(window.hoveredNode); if (window.hoveredNode != null) { dragOffset = window.hoveredNode.position.position - window.WindowToGridPosition(e.mousePosition); @@ -43,11 +61,15 @@ public static class NodeEditorAction { window.Repaint(); break; case EventType.MouseUp: + window.tempConnection = null; if (dragging) return; if (e.button == 1) { NodeEditorGUI.RightClickContextMenu(window); } + break; + case EventType.repaint: + break; } } diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index b897355..472ff83 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -8,6 +8,10 @@ namespace UNEC { /// Contains GUI methods public static class NodeEditorGUI { + public static void DrawConnection(Vector2 from, Vector2 to, Color col) { + Handles.DrawBezier(from, to, from, to, col, new Texture2D(2, 2), 2); + } + public static void BeginZoomed(Rect rect, float zoom) { GUI.EndClip(); diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs index ebee646..ec262cf 100644 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -1,5 +1,6 @@ using UnityEngine; using UnityEditor; +using System; namespace UNEC { public static class NodeEditorResources { @@ -15,6 +16,53 @@ namespace UNEC { private static Color arteryColor = new Color(0.34f, 0.34f, 0.34f); private static Color crossColor = new Color(0.45f, 0.45f, 0.45f); + public static Styles styles = new Styles(); + + public class Styles { + GUIStyle inputInt, inputString, inputFloat, inputObject, inputTexture, inputColor; + GUIStyle outputInt, outputString, outputFloat, outputObject, outputTexture, outputColor; + + public Styles() { + inputObject = new GUIStyle((GUIStyle)"flow shader in 0"); + inputString = new GUIStyle((GUIStyle)"flow shader in 1"); + inputInt = new GUIStyle((GUIStyle)"flow shader in 2"); + inputFloat = new GUIStyle((GUIStyle)"flow shader in 3"); + inputColor = new GUIStyle((GUIStyle)"flow shader in 4"); + inputTexture = new GUIStyle((GUIStyle)"flow shader in 5"); + outputObject = new GUIStyle((GUIStyle)"flow shader out 0"); + outputString = new GUIStyle((GUIStyle)"flow shader out 1"); + outputInt = new GUIStyle((GUIStyle)"flow shader out 2"); + outputFloat = new GUIStyle((GUIStyle)"flow shader out 3"); + outputColor = new GUIStyle((GUIStyle)"flow shader out 4"); + outputTexture = new GUIStyle((GUIStyle)"flow shader out 5"); + + foreach (GUIStyle style in new GUIStyle[] { inputInt, inputString, inputFloat, inputObject, inputTexture, inputColor, outputInt, outputString, outputFloat, outputObject, outputTexture, outputColor }) { + style.normal.textColor = Color.black; + style.fixedHeight = 18; + style.alignment = TextAnchor.MiddleLeft; + style.onHover.textColor = Color.red; + } + } + + public GUIStyle GetInputStyle(Type type) { + if (type == typeof(int)) return inputInt; + else if (type == typeof(string)) return inputString; + else if (type == typeof(Texture2D)) return inputTexture; + else if (type == typeof(float)) return inputFloat; + else if (type == typeof(Color)) return inputColor; + else return inputObject; + } + + public GUIStyle GetOutputStyle(Type type) { + if (type == typeof(int)) return outputInt; + else if (type == typeof(string)) return outputString; + else if (type == typeof(Texture2D)) return outputTexture; + else if (type == typeof(float)) return outputFloat; + else if (type == typeof(Color)) return outputColor; + else return outputObject; + } + } + public static Texture2D GenerateGridTexture() { Texture2D tex = new Texture2D(64,64); Color[] cols = new Color[64 * 64]; diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 75bc975..26517fd 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -6,10 +6,14 @@ using UNEC; public class NodeEditorWindow : EditorWindow { + private Dictionary portConnectionPoints = new Dictionary(); + public NodeGraph graph { get { return _graph != null ? _graph : _graph = new NodeGraph(); } } public NodeGraph _graph; public Node hoveredNode; public Node activeNode { get; private set; } + public NodePort hoveredPort; + public NodeConnection? tempConnection; public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } private Vector2 _panOffset; @@ -28,36 +32,92 @@ public class NodeEditorWindow : EditorWindow { Matrix4x4 m = GUI.matrix; NodeEditorAction.Controls(this); - NodeEditorGUI.DrawGrid(position, zoom, panOffset); DrawNodes(); + DrawTempConnection(); NodeEditorGUI.DrawToolbar(this); GUI.matrix = m; } + /// Draw a connection as we are dragging it + private void DrawTempConnection() { + if (tempConnection.HasValue) { + Node inputNode = graph.GetNode(tempConnection.Value.inputNodeId); + if (inputNode != null) { + NodePort outputPort = inputNode.GetOutput(tempConnection.Value.outputPortId); + Vector2 startPoint = GridToWindowPosition( portConnectionPoints[outputPort]); + Vector2 endPoint = Event.current.mousePosition; + Vector2 startTangent = startPoint; + startTangent.x = Mathf.Lerp(startPoint.x,endPoint.x,0.7f); + Vector2 endTangent = endPoint; + endTangent.x = Mathf.Lerp(endPoint.x, startPoint.x, 0.7f); + Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, Color.gray, null, 4); + Handles.DrawBezier(startPoint, endPoint, startTangent, endTangent, Color.white, null, 2); + Repaint(); + } + } + } private void DrawNodes() { + portConnectionPoints.Clear(); + Event e = Event.current; + BeginWindows(); NodeEditorGUI.BeginZoomed(position, zoom); - Event e = Event.current; + if (e.type == EventType.repaint) { + hoveredPort = null; + } hoveredNode = null; foreach (KeyValuePair kvp in graph.nodes) { Node node = kvp.Value; int id = kvp.Key; //Get node position - Vector2 windowPos = GridToWindowPositionNoClipped(node.position.position); + Vector2 nodePos = GridToWindowPositionNoClipped(node.position.position); - Rect windowRect = new Rect(windowPos, new Vector2(200, 200)); + Rect windowRect = new Rect(nodePos, new Vector2(200, 200)); if (windowRect.Contains(e.mousePosition)) hoveredNode = node; GUIStyle style = (node == activeNode) ? (GUIStyle)"flow node 0 on" : (GUIStyle)"flow node 0"; - GUI.Box(windowRect, node.ToString(), style); + GUILayout.BeginArea(windowRect, node.ToString(), style); + GUILayout.BeginHorizontal(); + + //Inputs + GUILayout.BeginVertical(); + for (int i = 0; i < node.InputCount; i++) { + NodePort input = node.GetInput(i); + Rect r = GUILayoutUtility.GetRect(new GUIContent(input.name), NodeEditorResources.styles.GetInputStyle(input.type)); + GUI.Label(r, input.name, NodeEditorResources.styles.GetInputStyle(input.type)); + if (e.type == EventType.repaint) { + if (r.Contains(e.mousePosition)) hoveredPort = input; + } + portConnectionPoints.Add(input, new Vector2(r.xMin, r.yMin + (r.height * 0.5f)) + node.position.position); + } + GUILayout.EndVertical(); + //Outputs + GUILayout.BeginVertical(); + for (int i = 0; i < node.OutputCount; i++) { + NodePort output = node.GetOutput(i); + Rect r = GUILayoutUtility.GetRect(new GUIContent(output.name), NodeEditorResources.styles.GetOutputStyle(output.type)); + GUI.Label(r, output.name, NodeEditorResources.styles.GetOutputStyle(output.type)); + if (e.type == EventType.repaint) { + if (r.Contains(e.mousePosition)) hoveredPort = output; + } + portConnectionPoints.Add(output, new Vector2(r.xMax, r.yMin + (r.height * 0.5f)) + node.position.position); + } + GUILayout.EndVertical(); - if (windowRect.position != windowPos) { - windowPos = windowRect.position; - node.position.position = WindowToGridPosition(windowPos); + GUILayout.EndHorizontal(); + + GUILayout.Label("More stuff"); + EditorGUILayout.Toggle("aDF",false); + + GUILayout.EndArea(); + + if (windowRect.position != nodePos) { + nodePos = windowRect.position; + node.position.position = WindowToGridPosition(nodePos); //Vector2 newPos = windowRect = } @@ -78,7 +138,8 @@ public class NodeEditorWindow : EditorWindow { } public Vector2 GridToWindowPosition(Vector2 gridPosition) { - return (position.size * 0.5f) + (panOffset / zoom) + gridPosition; + //Vector2 center = position.size * 0.5f; + return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition/zoom); } public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { @@ -91,4 +152,5 @@ public class NodeEditorWindow : EditorWindow { public void SelectNode(Node node) { activeNode = node; } + } \ No newline at end of file diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 8c8b74c..ec3612c 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -1,8 +1,51 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; +using System; +using UNEC; /// Base class for all nodes public abstract class Node { public Rect position = new Rect(0,0,200,200); + protected NodePort[] inputs = new NodePort[0]; + protected NodePort[] outputs = new NodePort[0]; + + public int InputCount { get { return inputs.Length; } } + public int OutputCount { get { return outputs.Length; } } + + protected Node() { + Init(); + } + + abstract protected void Init(); + + public int GetInputPortId(NodePort input) { + for (int i = 0; i < inputs.Length; i++) { + if (input == inputs[i]) return i; + + } + return -1; + } + public int GetOutputPortId(NodePort output) { + for (int i = 0; i < outputs.Length; i++) { + if (output == outputs[i]) return i; + + } + return -1; + } + + public NodePort GetInput(int portId) { + return inputs[portId]; + } + + public NodePort GetOutput(int portId) { + return outputs[portId]; + } + + public NodePort CreateNodeInput(string name, Type type, bool enabled = true) { + return new NodePort(name, type, this, enabled); + } + public NodePort CreateNodeOutput(string name, Type type, bool enabled = true) { + return new NodePort(name, type, this, enabled); + } } diff --git a/Scripts/NodeConnection.cs b/Scripts/NodeConnection.cs new file mode 100644 index 0000000..f5ef29c --- /dev/null +++ b/Scripts/NodeConnection.cs @@ -0,0 +1,22 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace UNEC { + /// Data travels from Input Node's Output port to Output Node's Input port + public struct NodeConnection { + public int inputNodeId { get { return _inputNodeId; } } + public int inputPortId { get { return _inputPortId; } } + public int outputNodeId { get { return _outputNodeId; } } + public int outputPortId { get { return _outputPortId; } } + [SerializeField] private int _inputNodeId, _inputPortId, _outputNodeId, _outputPortId; + + /// Data travels from Input Node's Output port to Output Node's Input port + public NodeConnection(int inputNodeId, int outputPortId, int outputNodeId, int inputPortId) { + _inputNodeId = inputNodeId; + _outputPortId = outputPortId; + _outputNodeId = outputNodeId; + _inputPortId = inputPortId; + } + } +} \ No newline at end of file diff --git a/Scripts/NodeConnection.cs.meta b/Scripts/NodeConnection.cs.meta new file mode 100644 index 0000000..819c7d8 --- /dev/null +++ b/Scripts/NodeConnection.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1f29a204e0cc2934e8f9cb2010723de9 +timeCreated: 1505747662 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index d902fb7..1a4df9b 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using UnityEngine; using System; +using UNEC; /// Base class for all node graphs public class NodeGraph { public Dictionary nodes = new Dictionary(); + private List connections = new List(); public T AddNode() where T : Node { T node = default(T); @@ -22,27 +24,37 @@ public class NodeGraph { } public void RemoveNode(Node node) { - int id = GetNodeID(node); + int id = GetNodeId(node); if (id != -1) nodes.Remove(id); else Debug.LogWarning("Node " + node.ToString() + " is not part of NodeGraph"); } - public void RemoveNode(int id) { - nodes.Remove(id); + public void RemoveNode(int nodeId) { + nodes.Remove(nodeId); } - public int GetNodeID(Node node) { + public int GetNodeId(Node node) { foreach(var kvp in nodes) { if (kvp.Value == node) return kvp.Key; } return -1; } - public Node GetNode(int id) { - if (nodes.ContainsKey(id)) return nodes[id]; + public Node GetNode(int nodeId) { + if (nodes.ContainsKey(nodeId)) return nodes[nodeId]; return null; } + public void AddConnection(NodePort input, NodePort output) { + int inputNodeId = GetNodeId(input.node); + int outputPortId = input.node.GetInputPortId(output); + + int outputNodeId = GetNodeId(output.node); + int inputPortId = output.node.GetInputPortId(input); + + NodeConnection connection = new NodeConnection(inputNodeId, inputPortId, outputNodeId, outputPortId); + } + private int GetUniqueID() { int id = 0; while (nodes.ContainsKey(id)) id++; @@ -51,5 +63,6 @@ public class NodeGraph { public void Clear() { nodes.Clear(); + connections.Clear(); } } diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs new file mode 100644 index 0000000..1a4e603 --- /dev/null +++ b/Scripts/NodePort.cs @@ -0,0 +1,45 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +public class NodePort { + public enum IO { None, Input, Output} + + public IO direction { + get { + for (int i = 0; i < node.InputCount; i++) { + if (node.GetInput(i) == this) return IO.Input; + } + for (int i = 0; i < node.OutputCount; i++) { + if (node.GetOutput(i) == this) return IO.Output; + } + return IO.None; + } + } + public Node node { get; private set; } + public string name { get { return _name; } set { _name = value; } } + [SerializeField] + private string _name; + public Type type { get; private set; } + [SerializeField] + private string _type; + public bool enabled { get { return _enabled; } set { _enabled = value; } } + [SerializeField] + private bool _enabled; + + public NodePort(string name, Type type, Node node, bool enabled) { + _name = name; + _enabled = enabled; + this.type = type; + _type = type.FullName; + this.node = node; + } + + public NodePort GetConnection() { + return null; + } + public NodePort[] GetConnections() { + return null; + } +} diff --git a/Scripts/NodePort.cs.meta b/Scripts/NodePort.cs.meta new file mode 100644 index 0000000..3863705 --- /dev/null +++ b/Scripts/NodePort.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7dd2f76ac25c6f44c9426dff3e7491a3 +timeCreated: 1505734054 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: