diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs
index edf66d61..36c3a6e3 100644
--- a/Scripts/Editor/NodeEditor.cs
+++ b/Scripts/Editor/NodeEditor.cs
@@ -67,7 +67,7 @@ public virtual void OnBodyGUI() {
serializedObject.ApplyModifiedProperties();
#if ODIN_INSPECTOR
- // Call repaint so that the graph window elements respond properly to layout changes coming from Odin
+ // Call repaint so that the graph window elements respond properly to layout changes coming from Odin
if (GUIHelper.RepaintRequested) {
GUIHelper.ClearRepaintRequest();
window.Repaint();
@@ -106,17 +106,22 @@ public virtual GUIStyle GetBodyHighlightStyle() {
/// Add items for the context menu when right-clicking this node. Override to add custom menu items.
public virtual void AddContextMenuItems(GenericMenu menu) {
+ bool canRemove = true;
// Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
+
+ canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node);
}
// Add actions to any number of selected nodes
menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
- menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
+
+ if (canRemove) menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
+ else menu.AddItem(new GUIContent("Remove"), false, null);
// Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
@@ -132,11 +137,10 @@ public void Rename(string newName) {
OnRename();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
}
-
+
/// Called after this node's name has changed.
public virtual void OnRename() { }
-
-
+
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib {
@@ -152,4 +156,4 @@ public Type GetInspectedType() {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs
index 12b9ba15..01de70e4 100644
--- a/Scripts/Editor/NodeGraphEditor.cs
+++ b/Scripts/Editor/NodeGraphEditor.cs
@@ -187,8 +187,25 @@ public virtual XNode.Node CopyNode(XNode.Node original) {
return node;
}
+ /// Return false for nodes that can't be removed
+ public virtual bool CanRemove(XNode.Node node) {
+ // Check graph attributes to see if this node is required
+ Type graphType = target.GetType();
+ XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
+ graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute);
+ if (attribs.Any(x => x.Requires(node.GetType()))) {
+ if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/// Safely remove a node and all its connections.
public virtual void RemoveNode(XNode.Node node) {
+ if (!CanRemove(node)) return;
+
+ // Remove the node
Undo.RecordObject(node, "Delete Node");
Undo.RecordObject(target, "Delete Node");
foreach (var port in node.Ports)
diff --git a/Scripts/Editor/NodeGraphImporter.cs b/Scripts/Editor/NodeGraphImporter.cs
new file mode 100644
index 00000000..3faf54fb
--- /dev/null
+++ b/Scripts/Editor/NodeGraphImporter.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using System.Linq;
+using UnityEditor;
+using UnityEditor.Experimental.AssetImporters;
+using UnityEngine;
+using XNode;
+
+namespace XNodeEditor {
+ /// Deals with modified assets
+ class NodeGraphImporter : AssetPostprocessor {
+ private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
+ foreach (string path in importedAssets) {
+ // Skip processing anything without the .asset extension
+ if (Path.GetExtension(path) != ".asset") continue;
+
+ // Get the object that is requested for deletion
+ NodeGraph graph = AssetDatabase.LoadAssetAtPath(path);
+ if (graph == null) continue;
+
+ // Get attributes
+ Type graphType = graph.GetType();
+ NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
+ graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute);
+
+ Vector2 position = Vector2.zero;
+ foreach (NodeGraph.RequireNodeAttribute attrib in attribs) {
+ if (attrib.type0 != null) AddRequired(graph, attrib.type0, ref position);
+ if (attrib.type1 != null) AddRequired(graph, attrib.type1, ref position);
+ if (attrib.type2 != null) AddRequired(graph, attrib.type2, ref position);
+ }
+ }
+ }
+
+ private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) {
+ if (!graph.nodes.Any(x => x.GetType() == type)) {
+ XNode.Node node = graph.AddNode(type);
+ node.position = position;
+ position.x += 200;
+ if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
+ if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(graph))) AssetDatabase.AddObjectToAsset(node, graph);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scripts/Editor/NodeGraphImporter.cs.meta b/Scripts/Editor/NodeGraphImporter.cs.meta
new file mode 100644
index 00000000..b3dd1fee
--- /dev/null
+++ b/Scripts/Editor/NodeGraphImporter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7a816f2790bf3da48a2d6d0035ebc9a0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs
index 6a0cead2..d928f946 100644
--- a/Scripts/NodeGraph.cs
+++ b/Scripts/NodeGraph.cs
@@ -81,5 +81,44 @@ protected virtual void OnDestroy() {
// Remove all nodes prior to graph destruction
Clear();
}
+
+#region Attributes
+ /// Automatically ensures the existance of a certain node type, and prevents it from being deleted.
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ public class RequireNodeAttribute : Attribute {
+ public Type type0;
+ public Type type1;
+ public Type type2;
+
+ /// Automatically ensures the existance of a certain node type, and prevents it from being deleted
+ public RequireNodeAttribute(Type type) {
+ this.type0 = type;
+ this.type1 = null;
+ this.type2 = null;
+ }
+
+ /// Automatically ensures the existance of a certain node type, and prevents it from being deleted
+ public RequireNodeAttribute(Type type, Type type2) {
+ this.type0 = type;
+ this.type1 = type2;
+ this.type2 = null;
+ }
+
+ /// Automatically ensures the existance of a certain node type, and prevents it from being deleted
+ public RequireNodeAttribute(Type type, Type type2, Type type3) {
+ this.type0 = type;
+ this.type1 = type2;
+ this.type2 = type3;
+ }
+
+ public bool Requires(Type type) {
+ if (type == null) return false;
+ if (type == type0) return true;
+ else if (type == type1) return true;
+ else if (type == type2) return true;
+ return false;
+ }
+ }
+#endregion
}
}
\ No newline at end of file