diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index d173abf..cb7136c 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Collections.Generic; using UnityEditor; using UnityEngine; @@ -136,5 +137,29 @@ namespace XNodeEditor { GUI.DrawTexture(rect, NodeEditorResources.dot); GUI.color = col; } + + /// Make a popup with all variable ids. + public static string VariablePopup(XNode.NodeGraph graph, string variableId, params GUILayoutOption[] options) + { + if (graph == null) return variableId; + + List variablesStrings = new List(); + + foreach (var item in graph.variables) + variablesStrings.Add(item.id); + + int originalIdx = variablesStrings.IndexOf(variableId); + int newIdx = EditorGUILayout.Popup(originalIdx, variablesStrings.ToArray(), options); + + if (originalIdx == -1 && newIdx == -1) + { + EditorGUILayout.HelpBox("Looks like the variable that used to be here, doesn't exist anymore. \n id:" + variableId, MessageType.Error); + return variableId; + } + + if (newIdx >= variablesStrings.Count) + return variableId; + return variablesStrings[newIdx]; + } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorUtilities.cs b/Scripts/Editor/NodeEditorUtilities.cs index d0cbc7b..67d18d0 100644 --- a/Scripts/Editor/NodeEditorUtilities.cs +++ b/Scripts/Editor/NodeEditorUtilities.cs @@ -48,8 +48,8 @@ namespace XNodeEditor { ); return methods.Count() > 0; } - - /// Return a prettiefied type name. + + /// Return a prettified type name. public static string PrettyName(this Type type) { if (type == null) return "null"; if (type == typeof(System.Object)) return "object"; diff --git a/Scripts/Editor/NodeGraphInspector.cs b/Scripts/Editor/NodeGraphInspector.cs new file mode 100644 index 0000000..32a5b24 --- /dev/null +++ b/Scripts/Editor/NodeGraphInspector.cs @@ -0,0 +1,160 @@ +using System; +using System.Reflection; +using System.Linq; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using XNode; + +namespace XNodeEditor +{ + [CustomEditor(typeof(XNode.NodeGraph), true)] + public class NodeGraphInspector : Editor + { + SerializedProperty variablesProp; + + List _knownTypes = new List(); + void OnEnable() + { + variablesProp = serializedObject.FindProperty("variables"); + NodeGraph graph = target as NodeGraph; + + foreach (var node in graph.nodes) + { + foreach (var port in node.Ports) + { + if (!_knownTypes.Contains(port.ValueType)) + _knownTypes.Add(port.ValueType); + } + } + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + DrawVariables(); + + serializedObject.ApplyModifiedProperties(); + } + + void DrawVariables() + { + EditorGUILayout.LabelField("Variables"); + EditorGUILayout.Space(); + for (int i = 0; i < variablesProp.arraySize; i++) + DrawVariable(i); + DrawVariablesActions(); + } + + void DrawVariable(int index) + { + var variableProp = variablesProp.GetArrayElementAtIndex(index); + + var idProp = variableProp.FindPropertyRelative("id"); + var typeProp = variableProp.FindPropertyRelative("typeString"); + + + DrawVariableId(idProp); + DrawVariableType(typeProp); + DrawVariableValue(variableProp, typeProp.stringValue); + DrawVariableActions(index); + + EditorGUILayout.Space(); + } + + void DrawVariableId(SerializedProperty idProp) + { + EditorGUILayout.PropertyField(idProp); + } + + void DrawVariableType(SerializedProperty typeProp) + { + List options = new List(); + options.Add(typeProp.stringValue); + int idx = 0; + + List additionalTypes = new List(); + additionalTypes.Add(typeof(float).AssemblyQualifiedName); + additionalTypes.Add(typeof(int).AssemblyQualifiedName); + additionalTypes.Add(typeof(bool).AssemblyQualifiedName); + additionalTypes.Add(typeof(string).AssemblyQualifiedName); + additionalTypes.Add(typeof(Vector3).AssemblyQualifiedName); + + foreach (var type in _knownTypes) + { + if (additionalTypes.Contains(type.AssemblyQualifiedName)) + continue; + additionalTypes.Add(type.AssemblyQualifiedName); + } + + foreach (var addType in additionalTypes) + { + if (!options.Contains(addType)) + options.Add(addType); + } + + List prettyOptions = new List(); + + foreach (var option in options) + { + prettyOptions.Add(System.Type.GetType(option, false).PrettyName()); + } + + + idx = EditorGUILayout.Popup(idx, prettyOptions.ToArray()); + + typeProp.stringValue = options[idx]; + } + + void DrawVariableValue(SerializedProperty variableProp, string type) + { + if (type != "") + { + type = System.Type.GetType(type, false).PrettyName(); + type = NodeGraph.GetSafeType(type); + + var valprop = variableProp.FindPropertyRelative(type + "Value"); + + if (valprop == null && type != "object") + { + type = "object"; + valprop = variableProp.FindPropertyRelative(type + "Value"); + } + + if (valprop != null) + EditorGUILayout.PropertyField(valprop); + else + EditorGUILayout.LabelField("Value"); + } + else + EditorGUILayout.LabelField("Value"); + } + + void DrawVariableActions(int index) + { + if (GUILayout.Button("Remove variable", GUILayout.Width(120))) + { + variablesProp.DeleteArrayElementAtIndex(index); + } + } + + void DrawVariablesActions() + { + GUILayout.BeginHorizontal(); + + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Add new variable", GUILayout.Width(120))) + { + variablesProp.InsertArrayElementAtIndex(variablesProp.arraySize); + var newVarProp = variablesProp.GetArrayElementAtIndex(variablesProp.arraySize -1); + newVarProp.FindPropertyRelative("id").stringValue = (target as NodeGraph).GetSafeId("new_variable"); + newVarProp.FindPropertyRelative("typeString").stringValue = typeof(float).AssemblyQualifiedName; + } + + GUILayout.EndHorizontal(); + } + } +} diff --git a/Scripts/Editor/NodeGraphInspector.cs.meta b/Scripts/Editor/NodeGraphInspector.cs.meta new file mode 100644 index 0000000..58f2bd6 --- /dev/null +++ b/Scripts/Editor/NodeGraphInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16edf05454c66b04ba4ebd0bf5b0af6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeInspector.cs b/Scripts/Editor/NodeInspector.cs new file mode 100644 index 0000000..49602aa --- /dev/null +++ b/Scripts/Editor/NodeInspector.cs @@ -0,0 +1,16 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using XNode; + +namespace XNodeEditor +{ + [CustomEditor(typeof(Node), true)] + public class NodeInspector : Editor + { + public override void OnInspectorGUI() { /*hides unneeded info*/ } + } +} + diff --git a/Scripts/Editor/NodeInspector.cs.meta b/Scripts/Editor/NodeInspector.cs.meta new file mode 100644 index 0000000..0faa43e --- /dev/null +++ b/Scripts/Editor/NodeInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a079842175cdba54a8c36ad52f983502 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Node.cs b/Scripts/Node.cs index ddc43ea..3f6c51a 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -62,7 +62,6 @@ namespace XNode { protected void OnEnable() { UpdateStaticPorts(); - Init(); } /// Update static ports to reflect class fields. This happens automatically on enable. @@ -71,7 +70,7 @@ namespace XNode { } /// Initialize node. Called on creation. - protected virtual void Init() { name = GetType().Name; } + public virtual void Init() { name = GetType().Name; } /// Checks all connections for invalid references, and removes them. public void VerifyConnections() { diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index c2092e0..f17d1f4 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Text.RegularExpressions; using System.Collections.Generic; using UnityEngine; @@ -10,6 +12,9 @@ namespace XNode { /// All nodes in the graph. /// See: [SerializeField] public List nodes = new List(); + + /// All variables in the graph. + [SerializeField] public List variables = new List(); /// Add a node to the graph by type public T AddNode() where T : Node { @@ -28,6 +33,7 @@ namespace XNode { #endif nodes.Add(node); node.graph = this; + node.Init(); return node; } @@ -44,6 +50,7 @@ namespace XNode { #endif nodes.Add(node); node.graph = this; + node.Init(); return node; } @@ -60,9 +67,10 @@ namespace XNode { nodes.Remove(node); } - /// Remove all nodes and connections from the graph + /// Remove all nodes, connections and variables from the graph public void Clear() { nodes.Clear(); + variables.Clear(); } /// Create a new deep copy of this graph @@ -74,6 +82,7 @@ namespace XNode { Node node = Instantiate(nodes[i]) as Node; node.graph = graph; graph.nodes[i] = node; + node.Init(); } // Redirect all connections @@ -85,5 +94,60 @@ namespace XNode { return graph; } + + /// Add a variable to the graph + /// the new variable data + /// the actual id used, avoiding duplicates + public string AddVariable(Variable newVariable) + { + newVariable.id = GetSafeId(newVariable.id); + newVariable.typeString = GetSafeType(newVariable.typeString); + variables.Add(newVariable); + return newVariable.id; + } + + /// Remove a variable from the graph + public void RemoveVariable(string id) + { + variables.Remove(GetVariable(id)); + } + + /// Get a variable from the graph + public Variable GetVariable(string id) + { + return variables.Find((Variable v) => v.id == id); + } + + /// Get a duplication safe id + public string GetSafeId(string id) + { + id = id.ToLower(); + id.Trim(); + var rx = new Regex(@"\s"); + rx.Replace(id, new MatchEvaluator((Match m) => "_")); + + var existingVariable = variables.Find((Variable v) => v.id == id); + if (existingVariable == null) + return id; + + int index = 1; + + while (variables.Find((Variable v) => v.id == id + "_" + index.ToString()) != null) + index++; + return id + "_" + index.ToString(); + } + + /// Get a variable safe type + public static string GetSafeType(string type) + { + const string UE = "UnityEngine."; + if (type.Contains(UE)) + { + type = type.Substring(UE.Length); + type = type.Substring(0,1).ToLower() + type.Substring(1); + } + + return type; + } } } \ No newline at end of file diff --git a/Scripts/Variable.cs b/Scripts/Variable.cs new file mode 100644 index 0000000..474a4ff --- /dev/null +++ b/Scripts/Variable.cs @@ -0,0 +1,55 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace XNode +{ + [System.Serializable] + public class Variable + { + public string id; + public string typeString; + public System.Type type + { + get + { + if (typeString == typeof(float).AssemblyQualifiedName) return typeof(float); + if (typeString == typeof(int).AssemblyQualifiedName) return typeof(int); + if (typeString == typeof(string).AssemblyQualifiedName) return typeof(string); + if (typeString == typeof(bool).AssemblyQualifiedName) return typeof(bool); + if (typeString == typeof(Vector3).AssemblyQualifiedName) return typeof(Vector3); + return typeof(Object); + } + } + + public object Value + { + get + { + if (typeString == typeof(float).AssemblyQualifiedName) return floatValue; + if (typeString == typeof(int).AssemblyQualifiedName) return intValue; + if (typeString == typeof(string).AssemblyQualifiedName) return stringValue; + if (typeString == typeof(bool).AssemblyQualifiedName) return boolValue; + if (typeString == typeof(Vector3).AssemblyQualifiedName) return vector3Value; + return objectValue; + } + } + + public string stringValue; + public float floatValue; + public int intValue; + public bool boolValue; + public Vector3 vector3Value; + + // more here + + public Object objectValue; + + + public Variable() + { + id = "new_variable"; + typeString = typeof(float).AssemblyQualifiedName; + } + } +} diff --git a/Scripts/Variable.cs.meta b/Scripts/Variable.cs.meta new file mode 100644 index 0000000..a68dc79 --- /dev/null +++ b/Scripts/Variable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 81f7d4c8803ebee4c903b0976f780d95 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: