1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-20 01:06:01 +08:00

Big update.

Removal of scripts now also clears dependant nodes, to avoid null objects.
NodePorts now support fallback values.
UI Changes.
node.graph is now serialized as well.
This commit is contained in:
Thor Brigsted 2017-10-14 16:19:24 +02:00
parent c1db2d9e4b
commit 5e68b6bcdc
16 changed files with 204 additions and 130 deletions

Binary file not shown.

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: e8c47bc953732464a9bf3a76273d99ef
timeCreated: 1507916591
guid: 614b670274b902e44bba59ba861eb1bd
timeCreated: 1507988229
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 11400000

View File

@ -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;
}
}

View File

@ -1,6 +1,6 @@
using UnityEngine;
public class DisplayValue : ExampleNodeBase {
[Input] public float value;
[Input(false)] public float value;
}

View File

@ -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;

View File

@ -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) {

19
Example/Nodes/TestNode.cs Normal file
View File

@ -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;
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: 707240ce8955a0240a7c0c4177d83bf5
timeCreated: 1505937586
guid: 47bfd6281f41a10459bcb45bb4cd03f3
timeCreated: 1507990362
licenseType: Free
MonoImporter:
serializedVersion: 2

View File

@ -14,8 +14,7 @@ public class NodeEditor {
/// <param name="portPositions">Port handle positions need to be returned to the NodeEditorWindow </param>
public virtual void OnNodeGUI(out Dictionary<NodePort, Vector2> 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));
}
/// <summary> Draws standard editors for all fields marked with <see cref="Node.InputAttribute"/> or <see cref="Node.OutputAttribute"/> </summary>
protected void DrawDefaultNodePortsGUI(out Dictionary<NodePort, Vector2> portPositions) {
/// <summary> Draws standard field editors for all public fields </summary>
protected void DrawDefaultNodeBodyGUI(out Dictionary<NodePort, Vector2> portPositions) {
portPositions = new Dictionary<NodePort, Vector2>();
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();
}
/// <summary> Draws standard field editors for all public fields </summary>
protected void DrawDefaultNodeBodyGUI() {
FieldInfo[] fields = GetInspectorFields(target);
for (int i = 0; i < fields.Length; i++) {
object[] fieldAttribs = fields[i].GetCustomAttributes(false);
if (NodeEditorUtilities.HasAttrib<Node.InputAttribute>(fieldAttribs) || NodeEditorUtilities.HasAttrib<Node.OutputAttribute>(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;
}

View File

@ -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));

View File

@ -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<UnityEngine.Object>(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;
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e515e86efe8160243a68b7c06d730c9c
timeCreated: 1507982232
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -116,6 +116,8 @@ public partial class NodeEditorWindow {
/// <summary> Draws all connections </summary>
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;

View File

@ -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<NodePort, Vector2> 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);
}
}

View File

@ -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);
}
}

View File

@ -9,7 +9,7 @@ using UnityEngine;
public abstract class Node : ScriptableObject {
/// <summary> Name of the node </summary>
[NonSerialized] public NodeGraph graph;
[SerializeField] public NodeGraph graph;
[SerializeField] public Rect rect = new Rect(0, 0, 200, 200);
/// <summary> Input <see cref="NodePort"/>s. It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> </summary>
[SerializeField] public List<NodePort> inputs = new List<NodePort>();
@ -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() {