Skip to content

Commit

Permalink
Add [RequireNode] attribute to graphs (Siccity#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
Siccity authored Apr 24, 2020
1 parent c298b5e commit d9d90f0
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 6 deletions.
16 changes: 10 additions & 6 deletions Scripts/Editor/NodeEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -106,17 +106,22 @@ public virtual GUIStyle GetBodyHighlightStyle() {

/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary>
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) {
Expand All @@ -132,11 +137,10 @@ public void Rename(string newName) {
OnRename();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
}

/// <summary> Called after this node's name has changed. </summary>
public virtual void OnRename() { }



[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib {
Expand All @@ -152,4 +156,4 @@ public Type GetInspectedType() {
}
}
}
}
}
17 changes: 17 additions & 0 deletions Scripts/Editor/NodeGraphEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,25 @@ public virtual XNode.Node CopyNode(XNode.Node original) {
return node;
}

/// <summary> Return false for nodes that can't be removed </summary>
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;
}

/// <summary> Safely remove a node and all its connections. </summary>
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)
Expand Down
45 changes: 45 additions & 0 deletions Scripts/Editor/NodeGraphImporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
using XNode;

namespace XNodeEditor {
/// <summary> Deals with modified assets </summary>
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<NodeGraph>(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);
}
}
}
}
11 changes: 11 additions & 0 deletions Scripts/Editor/NodeGraphImporter.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions Scripts/NodeGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,44 @@ protected virtual void OnDestroy() {
// Remove all nodes prior to graph destruction
Clear();
}

#region Attributes
/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted. </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireNodeAttribute : Attribute {
public Type type0;
public Type type1;
public Type type2;

/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary>
public RequireNodeAttribute(Type type) {
this.type0 = type;
this.type1 = null;
this.type2 = null;
}

/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary>
public RequireNodeAttribute(Type type, Type type2) {
this.type0 = type;
this.type1 = type2;
this.type2 = null;
}

/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary>
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
}
}

0 comments on commit d9d90f0

Please sign in to comment.