diff --git a/Example/ExampleNodeGraph.asset b/Example/ExampleNodeGraph.asset index d42f7ca..b09f280 100644 Binary files a/Example/ExampleNodeGraph.asset and b/Example/ExampleNodeGraph.asset differ diff --git a/Example/ExampleNodeGraph.asset.meta b/Example/ExampleNodeGraph.asset.meta index 6ec3d2f..12fe78e 100644 --- a/Example/ExampleNodeGraph.asset.meta +++ b/Example/ExampleNodeGraph.asset.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: e8c47bc953732464a9bf3a76273d99ef -timeCreated: 1507916591 +guid: 614b670274b902e44bba59ba861eb1bd +timeCreated: 1507988229 licenseType: Free NativeFormatImporter: mainObjectFileID: 11400000 diff --git a/Example/Nodes/ConstantValue.cs b/Example/Nodes/ConstantValue.cs deleted file mode 100644 index b0b99ce..0000000 --- a/Example/Nodes/ConstantValue.cs +++ /dev/null @@ -1,13 +0,0 @@ -[System.Serializable] -public class ConstantValue : ExampleNodeBase { - public float a; - [Output] public float value; - - protected override void Init() { - name = "Constant Value"; - } - - public override object GetValue(NodePort port) { - return a; - } -} diff --git a/Example/Nodes/DisplayValue.cs b/Example/Nodes/DisplayValue.cs index d19dd21..ef82b56 100644 --- a/Example/Nodes/DisplayValue.cs +++ b/Example/Nodes/DisplayValue.cs @@ -1,6 +1,6 @@ using UnityEngine; public class DisplayValue : ExampleNodeBase { - [Input] public float value; + [Input(false)] public float value; } diff --git a/Example/Nodes/ExampleNodeBase.cs b/Example/Nodes/ExampleNodeBase.cs index c47cff0..c7ad9ff 100644 --- a/Example/Nodes/ExampleNodeBase.cs +++ b/Example/Nodes/ExampleNodeBase.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using UnityEngine; -public class ExampleNodeBase : Node { +public abstract class ExampleNodeBase : Node { public float GetInputFloat(string fieldName) { float result = 0f; diff --git a/Example/Nodes/MathNode.cs b/Example/Nodes/MathNode.cs index 475698d..fef8659 100644 --- a/Example/Nodes/MathNode.cs +++ b/Example/Nodes/MathNode.cs @@ -2,9 +2,9 @@ [System.Serializable] public class MathNode : ExampleNodeBase { - [Input] public float c; - [Input] public float b; - [Output] public float result; + [Input(true)] public float a; + [Input(true)] public float b; + [Output(false)] public float result; public enum MathType { Add, Subtract, Multiply, Divide} public MathType mathType = MathType.Add; @@ -13,7 +13,7 @@ public class MathNode : ExampleNodeBase { } public override object GetValue(NodePort port) { - float a = GetInputFloat("c"); + float a = GetInputFloat("a"); float b = GetInputFloat("b"); switch(port.fieldName) { diff --git a/Example/Nodes/TestNode.cs b/Example/Nodes/TestNode.cs new file mode 100644 index 0000000..900ddf4 --- /dev/null +++ b/Example/Nodes/TestNode.cs @@ -0,0 +1,19 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class TestNode : Node { + + public int i; + public float f; + public double d; + public long l; + public bool b; + public string s; + public Rect r; + public Vector2 v2; + public Vector3 v3; + public Vector4 v4; + public Color col; + public AnimationCurve a; +} diff --git a/Example/Nodes/ConstantValue.cs.meta b/Example/Nodes/TestNode.cs.meta similarity index 76% rename from Example/Nodes/ConstantValue.cs.meta rename to Example/Nodes/TestNode.cs.meta index e836841..93b01d2 100644 --- a/Example/Nodes/ConstantValue.cs.meta +++ b/Example/Nodes/TestNode.cs.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: 707240ce8955a0240a7c0c4177d83bf5 -timeCreated: 1505937586 +guid: 47bfd6281f41a10459bcb45bb4cd03f3 +timeCreated: 1507990362 licenseType: Free MonoImporter: serializedVersion: 2 diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 8da947b..e1777eb 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -14,8 +14,7 @@ public class NodeEditor { /// Port handle positions need to be returned to the NodeEditorWindow public virtual void OnNodeGUI(out Dictionary portPositions) { DrawDefaultHeaderGUI(); - DrawDefaultNodePortsGUI(out portPositions); - DrawDefaultNodeBodyGUI(); + DrawDefaultNodeBodyGUI(out portPositions); } protected void DrawDefaultHeaderGUI() { @@ -23,40 +22,15 @@ public class NodeEditor { GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); } - /// Draws standard editors for all fields marked with or - protected void DrawDefaultNodePortsGUI(out Dictionary portPositions) { + /// Draws standard field editors for all public fields + protected void DrawDefaultNodeBodyGUI(out Dictionary portPositions) { portPositions = new Dictionary(); - Event e = Event.current; - - GUILayout.BeginHorizontal(); - - //Inputs - GUILayout.BeginVertical(); - for (int i = 0; i < target.InputCount; i++) { - Vector2 handlePoint = DrawNodePortGUI(target.inputs[i]); - portPositions.Add(target.inputs[i], handlePoint); - } - GUILayout.EndVertical(); - - //Outputs - GUILayout.BeginVertical(); - for (int i = 0; i < target.OutputCount; i++) { - Vector2 handlePoint = DrawNodePortGUI(target.outputs[i]); - portPositions.Add(target.outputs[i], handlePoint); - } - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - } - - /// Draws standard field editors for all public fields - protected void DrawDefaultNodeBodyGUI() { FieldInfo[] fields = GetInspectorFields(target); for (int i = 0; i < fields.Length; i++) { object[] fieldAttribs = fields[i].GetCustomAttributes(false); - if (NodeEditorUtilities.HasAttrib(fieldAttribs) || NodeEditorUtilities.HasAttrib(fieldAttribs)) continue; - DrawFieldInfoDrawerGUI(fields[i]); + if (fields[i].Name == "graph" || fields[i].Name == "rect") continue; + NodeEditorGUILayout.PropertyField(target, fields[i], portPositions); } } @@ -97,73 +71,6 @@ public class NodeEditor { return node.GetType().GetFields().Where(f => f.IsPublic || f.GetCustomAttributes(typeof(SerializeField), false) != null).ToArray(); } - private void DrawFieldInfoDrawerGUI(FieldInfo fieldInfo) { - Type fieldType = fieldInfo.FieldType; - string fieldName = fieldInfo.Name; - string fieldPrettyName = fieldName.PrettifyCamelCase(); - object fieldValue = fieldInfo.GetValue(target); - object[] fieldAttribs = fieldInfo.GetCustomAttributes(false); - - HeaderAttribute headerAttrib; - if (NodeEditorUtilities.GetAttrib(fieldAttribs, out headerAttrib)) { - EditorGUILayout.LabelField(headerAttrib.header); - } - - EditorGUI.BeginChangeCheck(); - if (fieldType == typeof(int)) { - fieldValue = NodeEditorGUILayout.IntField(fieldPrettyName, (int) fieldValue); - } else if (fieldType == typeof(bool)) { - fieldValue = EditorGUILayout.Toggle(fieldPrettyName, (bool) fieldValue); - } else if (fieldType.IsEnum) { - fieldValue = NodeEditorGUILayout.EnumField(fieldPrettyName, (Enum)fieldValue); - } else if (fieldType == typeof(string)) { - - if (fieldName == "name") return; //Ignore 'name' - TextAreaAttribute textAreaAttrib; - if (NodeEditorUtilities.GetAttrib(fieldAttribs, out textAreaAttrib)) { - fieldValue = EditorGUILayout.TextArea(fieldValue != null ? (string) fieldValue : ""); - } else - fieldValue = EditorGUILayout.TextField(fieldPrettyName, fieldValue != null ? (string) fieldValue : ""); - } else if (fieldType == typeof(Rect)) { - if (fieldName == "rect") return; //Ignore 'rect' - fieldValue = EditorGUILayout.RectField(fieldPrettyName, (Rect) fieldValue); - } else if (fieldType == typeof(float)) { - fieldValue = NodeEditorGUILayout.FloatField(fieldPrettyName, (float) fieldValue); - } else if (fieldType == typeof(double)) { - fieldValue = NodeEditorGUILayout.DoubleField(fieldPrettyName, (double) fieldValue); - } else if (fieldType == typeof(long)) { - fieldValue = NodeEditorGUILayout.LongField(fieldPrettyName, (long) fieldValue); - } else if (fieldType == typeof(Vector2)) { - fieldValue = EditorGUILayout.Vector2Field(fieldPrettyName, (Vector2) fieldValue); - } else if (fieldType == typeof(Vector3)) { - fieldValue = EditorGUILayout.Vector3Field(new GUIContent(fieldPrettyName), (Vector3) fieldValue); - } else if (fieldType == typeof(Vector4)) { - fieldValue = EditorGUILayout.Vector4Field(fieldPrettyName, (Vector4) fieldValue); - } else if (fieldType == typeof(Color)) { - Rect rect = EditorGUILayout.GetControlRect(); - rect.width *= 0.5f; - EditorGUI.LabelField(rect, fieldPrettyName); - rect.x += rect.width; - fieldValue = EditorGUI.ColorField(rect, (Color) fieldValue); - } else if (fieldType == typeof(AnimationCurve)) { - Rect rect = EditorGUILayout.GetControlRect(); - rect.width *= 0.5f; - EditorGUI.LabelField(rect, fieldPrettyName); - rect.x += rect.width; - - AnimationCurve curve = fieldValue != null ? (AnimationCurve) fieldValue : new AnimationCurve(); - curve = EditorGUI.CurveField(rect, curve); - if (fieldValue != curve) fieldInfo.SetValue(target, curve); - } else if (fieldType.IsSubclassOf(typeof(UnityEngine.Object)) || fieldType == typeof(UnityEngine.Object)) { - if (fieldName == "graph") return; //Ignore 'graph' - fieldValue = EditorGUILayout.ObjectField(fieldPrettyName, (UnityEngine.Object) fieldValue, fieldType, true); - } - if (EditorGUI.EndChangeCheck()) { - fieldInfo.SetValue(target, fieldValue); - EditorUtility.SetDirty(target); - } - } - public virtual int GetWidth() { return 200; } diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index aeed708..95b4600 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -137,6 +137,7 @@ public partial class NodeEditorWindow { Vector2 mousePos = Event.current.mousePosition; Node newHoverNode = null; foreach (Node node in graph.nodes) { + if (node == null) return; //Get node position Vector2 nodePos = GridToWindowPosition(node.rect.position); Rect windowRect = new Rect(nodePos, new Vector2(node.rect.size.x / zoom, node.rect.size.y / zoom)); diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs new file mode 100644 index 0000000..a1756f5 --- /dev/null +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +public class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor { + public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { + UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(path); + + if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete; + + UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; + System.Type scriptType = script.GetClass(); + + if (scriptType != typeof(Node) && !scriptType.IsSubclassOf(typeof(Node))) return AssetDeleteResult.DidNotDelete; + + //Find ScriptableObjects using this script + string[] guids = AssetDatabase.FindAssets("t:" + scriptType); + for (int i = 0; i < guids.Length; i++) { + string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); + Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); + for (int k = 0; k < objs.Length; k++) { + Node node = objs[k] as Node; + if (node.GetType() == scriptType) { + if (node != null && node.graph != null) { + Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); + node.graph.RemoveNode(node); + } + } + } + + } + return AssetDeleteResult.DidNotDelete; + } +} \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta b/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta new file mode 100644 index 0000000..057198b --- /dev/null +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e515e86efe8160243a68b7c06d730c9c +timeCreated: 1507982232 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 74b0a86..cc7fca0 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -116,6 +116,8 @@ public partial class NodeEditorWindow { /// Draws all connections public void DrawConnections() { foreach (Node node in graph.nodes) { + //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. + if (node == null) return; for (int i = 0; i < node.OutputCount; i++) { NodePort output = node.outputs[i]; @@ -126,6 +128,7 @@ public partial class NodeEditorWindow { NodePort input = output.GetConnection(k); if (input == null) return; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return. + if (!_portConnectionPoints.ContainsKey(input)) return; Vector2 to = _portConnectionPoints[input].center; DrawConnection(from, to, NodeEditorPreferences.GetTypeColor(output.type)); } @@ -148,6 +151,7 @@ public partial class NodeEditorWindow { BeginZoomed(position, zoom); foreach (Node node in graph.nodes) { + if (node == null) return; NodeEditor nodeEditor = GetNodeEditor(node.GetType()); nodeEditor.target = node; diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index e4d3b39..9683c70 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using UnityEditor; using UnityEngine; @@ -9,6 +11,115 @@ public class NodeEditorGUILayout { private static double tempValue; + public static object PortField(string label, object value, NodePort port, bool fallback, out Vector2 portPosition) { + if (fallback) value = PropertyField(label, value); + else EditorGUILayout.LabelField(label); + + Rect rect = GUILayoutUtility.GetLastRect(); + if (port.direction == NodePort.IO.Input) rect.position = rect.position - new Vector2(16, 0); + if (port.direction == NodePort.IO.Output) rect.position = rect.position + new Vector2(rect.width, 0); + rect.size = new Vector2(16, 16); + + Color col = GUI.color; + GUI.color = new Color32(90, 97, 105, 255); + GUI.DrawTexture(rect, NodeEditorResources.dotOuter); + GUI.color = NodeEditorPreferences.GetTypeColor(port.type); + GUI.DrawTexture(rect, NodeEditorResources.dot); + GUI.color = col; + portPosition = rect.center; + + return value; + } + + public static object PropertyField(Node target, FieldInfo fieldInfo, Dictionary portPositions) { + Type fieldType = fieldInfo.FieldType; + string fieldName = fieldInfo.Name; + string fieldPrettyName = fieldName.PrettifyCamelCase(); + object fieldValue = fieldInfo.GetValue(target); + object[] fieldAttribs = fieldInfo.GetCustomAttributes(false); + + HeaderAttribute headerAttrib; + if (NodeEditorUtilities.GetAttrib(fieldAttribs, out headerAttrib)) { + EditorGUILayout.LabelField(headerAttrib.header); + } + + Node.OutputAttribute outputAttrib; + Node.InputAttribute inputAttrib; + + EditorGUI.BeginChangeCheck(); + if (NodeEditorUtilities.GetAttrib(fieldAttribs, out inputAttrib)) { + NodePort port = target.GetPortByFieldName(fieldName); + Vector2 portPos; + fieldValue = PortField(fieldPrettyName, fieldValue, port, inputAttrib.fallback, out portPos); + portPositions.Add(port, portPos); + } else if (NodeEditorUtilities.GetAttrib(fieldAttribs, out outputAttrib)) { + NodePort port = target.GetPortByFieldName(fieldName); + Vector2 portPos; + fieldValue = PortField(fieldPrettyName, fieldValue, port, outputAttrib.fallback, out portPos); + portPositions.Add(port, portPos); + } else { + fieldValue = PropertyField(fieldPrettyName, fieldValue); + } + + if (EditorGUI.EndChangeCheck()) { + fieldInfo.SetValue(target, fieldValue); + EditorUtility.SetDirty(target); + } + return fieldValue; + } + + public static object PropertyField(string label, object value) { + if (value is int) return IntField(label, (int) value); + else if (value is float) return FloatField(label, (float) value); + else if (value is double) return DoubleField(label, (double) value); + else if (value is long) return LongField(label, (long) value); + else if (value is bool) return Toggle(label, (bool) value); + else if (value is Enum) return EnumField(label, (Enum) value); + else if (value is string) return TextField(label, (string) value); + else if (value is Rect) return RectField(label, (Rect) value); + else if (value is Vector2) return Vector2Field(label, (Vector2) value); + else if (value is Vector3) return Vector3Field(label, (Vector3) value); + else if (value is Vector4) return Vector4Field(label, (Vector4) value); + else if (value is Color) return ColorField(label, (Color) value); + else if (value is AnimationCurve) return CurveField(label, (AnimationCurve) value); + else if (value == null || value.GetType().IsSubclassOf(typeof(UnityEngine.Object)) || value.GetType() == typeof(UnityEngine.Object)) return ObjectField(label, (UnityEngine.Object) value); + else return value; + } + public static Rect GetRect(string label) { + Rect rect = EditorGUILayout.GetControlRect(); + rect.width *= 0.5f; + EditorGUI.LabelField(rect, label); + rect.x += rect.width; + return rect; + } + public static UnityEngine.Object ObjectField(string label, UnityEngine.Object value) { + return EditorGUI.ObjectField(GetRect(label), value, value.GetType(), true); + } + public static AnimationCurve CurveField(string label, AnimationCurve value) { + if (value == null) value = new AnimationCurve(); + return EditorGUI.CurveField(GetRect(label), value); + } + public static Color ColorField(string label, Color value) { + return EditorGUI.ColorField(GetRect(label), value); + } + public static Vector4 Vector4Field(string label, Vector4 value) { + return EditorGUILayout.Vector4Field(label, value); + } + public static Vector3 Vector3Field(string label, Vector3 value) { + return EditorGUILayout.Vector3Field(label, value); + } + public static Vector2 Vector2Field(string label, Vector2 value) { + return EditorGUILayout.Vector2Field(label, value); + } + public static Rect RectField(string label, Rect value) { + return EditorGUILayout.RectField(label, value); + } + public static string TextField(string label, string value) { + return EditorGUI.TextField(GetRect(label), value); + } + public static bool Toggle(string label, bool value) { + return EditorGUI.Toggle(GetRect(label), value); + } public static int IntField(string label, int value) { GUIUtility.GetControlID(FocusType.Passive); Rect rect = EditorGUILayout.GetControlRect(); @@ -119,10 +230,6 @@ public class NodeEditorGUILayout { } } public static Enum EnumField(string label, Enum value) { - Rect rect = EditorGUILayout.GetControlRect(); - rect.width *= 0.5f; - EditorGUI.LabelField(rect, label); - rect.x += rect.width; - return EditorGUI.EnumPopup(rect, value); + return EditorGUI.EnumPopup(GetRect(label), value); } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs index 0b3bf3b..7ec60ad 100644 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -48,7 +48,7 @@ public static class NodeEditorResources { nodeBody = new GUIStyle(); nodeBody.normal.background = NodeEditorResources.nodeBody; nodeBody.border = new RectOffset(32, 32, 32, 32); - nodeBody.padding = new RectOffset(10, 10, 2, 2); + nodeBody.padding = new RectOffset(16, 16, 4, 6); } } diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 69f39e3..fd6e23a 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -9,7 +9,7 @@ using UnityEngine; public abstract class Node : ScriptableObject { /// Name of the node - [NonSerialized] public NodeGraph graph; + [SerializeField] public NodeGraph graph; [SerializeField] public Rect rect = new Rect(0, 0, 200, 200); /// Input s. It is recommended not to modify these at hand. Instead, see [SerializeField] public List inputs = new List(); @@ -99,12 +99,14 @@ public abstract class Node : ScriptableObject { [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class InputAttribute : Attribute { - public InputAttribute() { } + public bool fallback; + public InputAttribute(bool fallback) { this.fallback = fallback; } } [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class OutputAttribute : Attribute { - public OutputAttribute() { } + public bool fallback; + public OutputAttribute(bool fallback) { this.fallback = fallback; } } private void GetPorts() {