1
0
mirror of https://github.com/Siccity/xNode.git synced 2026-02-04 14:24:54 +08:00

Restructure files

This commit is contained in:
Oli 2023-01-09 19:45:50 +00:00
parent 5967cef277
commit d2b4b55872
88 changed files with 3116 additions and 3293 deletions

View File

@ -1,40 +0,0 @@
## Contributing to xNode
💙Thank you for taking the time to contribute💙
If you haven't already, join our [Discord channel](https://discord.gg/qgPrHv4)!
## Pull Requests
Try to keep your pull requests relevant, neat, and manageable. If you are adding multiple features, split them into separate PRs.
These are the main points to follow:
1) Use formatting which is consistent with the rest of xNode base (see below)
2) Keep _one feature_ per PR (see below)
3) xNode aims to be compatible with C# 4.x, do not use new language features
4) Avoid including irellevant whitespace or formatting changes
5) Comment your code
6) Spell check your code / comments
7) Use concrete types, not *var*
8) Use english language
## New features
xNode aims to be simple and extendible, not trying to fix all of Unity's shortcomings.
Approved changes might be rejected if bundled with rejected changes, so keep PRs as separate as possible.
If your feature aims to cover something not related to editing nodes, it generally won't be accepted. If in doubt, ask on the Discord channel.
## Coding conventions
Using consistent formatting is key to having a clean git history. Skim through the code and you'll get the hang of it quickly.
* Methods, Types and properties PascalCase
* Variables camelCase
* Public methods XML commented. Params described if not obvious
* Explicit usage of brackets when doing multiple math operations on the same line
## Formatting
I use VSCode with the C# FixFormat extension and the following setting overrides:
```json
"csharpfixformat.style.spaces.beforeParenthesis": false,
"csharpfixformat.style.indent.regionIgnored": true
```
* Open braces on same line as condition
* 4 spaces for indentation.

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: bc1db8b29c76d44648c9c86c2dfade6d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 94d4fd78d9120634ebe0e8717610c412 guid: 94d4fd78d9120634ebe0e8717610c412
folderAsset: yes folderAsset: yes
timeCreated: 1505418345
licenseType: Free
DefaultImporter: DefaultImporter:
externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@ -1,187 +1,187 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor; using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities; using Sirenix.Utilities;
using Sirenix.Utilities.Editor; using Sirenix.Utilities.Editor;
#endif #endif
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU #if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu; using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif #endif
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary> /// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary>
[CustomNodeEditor(typeof(XNode.Node))] [CustomNodeEditor(typeof(XNode.Node))]
public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node> { public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node> {
/// <summary> Fires every whenever a node was modified through the editor </summary> /// <summary> Fires every whenever a node was modified through the editor </summary>
public static Action<XNode.Node> onUpdateNode; public static Action<XNode.Node> onUpdateNode;
public readonly static Dictionary<XNode.NodePort, Vector2> portPositions = new Dictionary<XNode.NodePort, Vector2>(); public readonly static Dictionary<XNode.NodePort, Vector2> portPositions = new Dictionary<XNode.NodePort, Vector2>();
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
protected internal static bool inNodeEditor = false; protected internal static bool inNodeEditor = false;
#endif #endif
public virtual void OnHeaderGUI() { public virtual void OnHeaderGUI() {
GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
} }
/// <summary> Draws standard field editors for all public fields </summary> /// <summary> Draws standard field editors for all public fields </summary>
public virtual void OnBodyGUI() { public virtual void OnBodyGUI() {
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
inNodeEditor = true; inNodeEditor = true;
#endif #endif
// Unity specifically requires this to save/update any serial object. // Unity specifically requires this to save/update any serial object.
// serializedObject.Update(); must go at the start of an inspector gui, and // serializedObject.Update(); must go at the start of an inspector gui, and
// serializedObject.ApplyModifiedProperties(); goes at the end. // serializedObject.ApplyModifiedProperties(); goes at the end.
serializedObject.Update(); serializedObject.Update();
string[] excludes = { "m_Script", "graph", "position", "ports" }; string[] excludes = { "m_Script", "graph", "position", "ports" };
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
try try
{ {
#if ODIN_INSPECTOR_3 #if ODIN_INSPECTOR_3
objectTree.BeginDraw( true ); objectTree.BeginDraw( true );
#else #else
InspectorUtilities.BeginDrawPropertyTree(objectTree, true); InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
#endif #endif
} }
catch ( ArgumentNullException ) catch ( ArgumentNullException )
{ {
#if ODIN_INSPECTOR_3 #if ODIN_INSPECTOR_3
objectTree.EndDraw(); objectTree.EndDraw();
#else #else
InspectorUtilities.EndDrawPropertyTree(objectTree); InspectorUtilities.EndDrawPropertyTree(objectTree);
#endif #endif
NodeEditor.DestroyEditor(this.target); NodeEditor.DestroyEditor(this.target);
return; return;
} }
GUIHelper.PushLabelWidth( 84 ); GUIHelper.PushLabelWidth( 84 );
objectTree.Draw( true ); objectTree.Draw( true );
#if ODIN_INSPECTOR_3 #if ODIN_INSPECTOR_3
objectTree.EndDraw(); objectTree.EndDraw();
#else #else
InspectorUtilities.EndDrawPropertyTree(objectTree); InspectorUtilities.EndDrawPropertyTree(objectTree);
#endif #endif
GUIHelper.PopLabelWidth(); GUIHelper.PopLabelWidth();
#else #else
// Iterate through serialized properties and draw them like the Inspector (But with ports) // Iterate through serialized properties and draw them like the Inspector (But with ports)
SerializedProperty iterator = serializedObject.GetIterator(); SerializedProperty iterator = serializedObject.GetIterator();
bool enterChildren = true; bool enterChildren = true;
while (iterator.NextVisible(enterChildren)) { while (iterator.NextVisible(enterChildren)) {
enterChildren = false; enterChildren = false;
if (excludes.Contains(iterator.name)) continue; if (excludes.Contains(iterator.name)) continue;
NodeEditorGUILayout.PropertyField(iterator, true); NodeEditorGUILayout.PropertyField(iterator, true);
} }
#endif #endif
// Iterate through dynamic ports and draw them in the order in which they are serialized // Iterate through dynamic ports and draw them in the order in which they are serialized
foreach (XNode.NodePort dynamicPort in target.DynamicPorts) { foreach (XNode.NodePort dynamicPort in target.DynamicPorts) {
if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue; if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue;
NodeEditorGUILayout.PortField(dynamicPort); NodeEditorGUILayout.PortField(dynamicPort);
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
#if ODIN_INSPECTOR #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) { if (GUIHelper.RepaintRequested) {
GUIHelper.ClearRepaintRequest(); GUIHelper.ClearRepaintRequest();
window.Repaint(); window.Repaint();
} }
#endif #endif
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
inNodeEditor = false; inNodeEditor = false;
#endif #endif
} }
public virtual int GetWidth() { public virtual int GetWidth() {
Type type = target.GetType(); Type type = target.GetType();
int width; int width;
if (type.TryGetAttributeWidth(out width)) return width; if (type.TryGetAttributeWidth(out width)) return width;
else return 208; else return 208;
} }
/// <summary> Returns color for target node </summary> /// <summary> Returns color for target node </summary>
public virtual Color GetTint() { public virtual Color GetTint() {
// Try get color from [NodeTint] attribute // Try get color from [NodeTint] attribute
Type type = target.GetType(); Type type = target.GetType();
Color color; Color color;
if (type.TryGetAttributeTint(out color)) return color; if (type.TryGetAttributeTint(out color)) return color;
// Return default color (grey) // Return default color (grey)
else return NodeEditorPreferences.GetSettings().tintColor; else return NodeEditorPreferences.GetSettings().tintColor;
} }
public virtual GUIStyle GetBodyStyle() { public virtual GUIStyle GetBodyStyle() {
return NodeEditorResources.styles.nodeBody; return NodeEditorResources.styles.nodeBody;
} }
public virtual GUIStyle GetBodyHighlightStyle() { public virtual GUIStyle GetBodyHighlightStyle() {
return NodeEditorResources.styles.nodeHighlight; return NodeEditorResources.styles.nodeHighlight;
} }
/// <summary> Override to display custom node header tooltips </summary> /// <summary> Override to display custom node header tooltips </summary>
public virtual string GetHeaderTooltip() { public virtual string GetHeaderTooltip() {
return null; return null;
} }
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> /// <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) { public virtual void AddContextMenuItems(GenericMenu menu) {
bool canRemove = true; bool canRemove = true;
// Actions if only one node is selected // Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as 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("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode); menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node); canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node);
} }
// Add actions to any number of selected nodes // Add actions to any number of selected nodes
menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes); menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes); menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
if (canRemove) 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); else menu.AddItem(new GUIContent("Remove"), false, null);
// Custom sctions if only one node is selected // Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node; XNode.Node node = Selection.activeObject as XNode.Node;
menu.AddCustomContextMenuItems(node); menu.AddCustomContextMenuItems(node);
} }
} }
/// <summary> Rename the node asset. This will trigger a reimport of the node. </summary> /// <summary> Rename the node asset. This will trigger a reimport of the node. </summary>
public void Rename(string newName) { public void Rename(string newName) {
if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType());
target.name = newName; target.name = newName;
OnRename(); OnRename();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
} }
/// <summary> Called after this node's name has changed. </summary> /// <summary> Called after this node's name has changed. </summary>
public virtual void OnRename() { } public virtual void OnRename() { }
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute, public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib { XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib {
private Type inspectedType; private Type inspectedType;
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary> /// <summary> Tells a NodeEditor which Node type it is an editor for </summary>
/// <param name="inspectedType">Type that this editor can edit</param> /// <param name="inspectedType">Type that this editor can edit</param>
public CustomNodeEditorAttribute(Type inspectedType) { public CustomNodeEditorAttribute(Type inspectedType) {
this.inspectedType = inspectedType; this.inspectedType = inspectedType;
} }
public Type GetInspectedType() { public Type GetInspectedType() {
return inspectedType; return inspectedType;
} }
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,183 +1,183 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU #if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu; using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif #endif
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Contains reflection-related extensions built for xNode </summary> /// <summary> Contains reflection-related extensions built for xNode </summary>
public static class NodeEditorReflection { public static class NodeEditorReflection {
[NonSerialized] private static Dictionary<Type, Color> nodeTint; [NonSerialized] private static Dictionary<Type, Color> nodeTint;
[NonSerialized] private static Dictionary<Type, int> nodeWidth; [NonSerialized] private static Dictionary<Type, int> nodeWidth;
/// <summary> All available node types </summary> /// <summary> All available node types </summary>
public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } }
[NonSerialized] private static Type[] _nodeTypes = null; [NonSerialized] private static Type[] _nodeTypes = null;
/// <summary> Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. </summary> /// <summary> Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. </summary>
public static Func<bool> GetIsDockedDelegate(this EditorWindow window) { public static Func<bool> GetIsDockedDelegate(this EditorWindow window) {
BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true); MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true);
return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod); return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod);
} }
public static Type[] GetNodeTypes() { public static Type[] GetNodeTypes() {
//Get all classes deriving from Node via reflection //Get all classes deriving from Node via reflection
return GetDerivedTypes(typeof(XNode.Node)); return GetDerivedTypes(typeof(XNode.Node));
} }
/// <summary> Custom node tint colors defined with [NodeColor(r, g, b)] </summary> /// <summary> Custom node tint colors defined with [NodeColor(r, g, b)] </summary>
public static bool TryGetAttributeTint(this Type nodeType, out Color tint) { public static bool TryGetAttributeTint(this Type nodeType, out Color tint) {
if (nodeTint == null) { if (nodeTint == null) {
CacheAttributes<Color, XNode.Node.NodeTintAttribute>(ref nodeTint, x => x.color); CacheAttributes<Color, XNode.Node.NodeTintAttribute>(ref nodeTint, x => x.color);
} }
return nodeTint.TryGetValue(nodeType, out tint); return nodeTint.TryGetValue(nodeType, out tint);
} }
/// <summary> Get custom node widths defined with [NodeWidth(width)] </summary> /// <summary> Get custom node widths defined with [NodeWidth(width)] </summary>
public static bool TryGetAttributeWidth(this Type nodeType, out int width) { public static bool TryGetAttributeWidth(this Type nodeType, out int width) {
if (nodeWidth == null) { if (nodeWidth == null) {
CacheAttributes<int, XNode.Node.NodeWidthAttribute>(ref nodeWidth, x => x.width); CacheAttributes<int, XNode.Node.NodeWidthAttribute>(ref nodeWidth, x => x.width);
} }
return nodeWidth.TryGetValue(nodeType, out width); return nodeWidth.TryGetValue(nodeType, out width);
} }
private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute { private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute {
dict = new Dictionary<Type, V>(); dict = new Dictionary<Type, V>();
for (int i = 0; i < nodeTypes.Length; i++) { for (int i = 0; i < nodeTypes.Length; i++) {
object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true); object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true);
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0) continue;
A attrib = attribs[0] as A; A attrib = attribs[0] as A;
dict.Add(nodeTypes[i], getter(attrib)); dict.Add(nodeTypes[i], getter(attrib));
} }
} }
/// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary> /// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary>
public static FieldInfo GetFieldInfo(this Type type, string fieldName) { public static FieldInfo GetFieldInfo(this Type type, string fieldName) {
// If we can't find field in the first run, it's probably a private field in a base class. // If we can't find field in the first run, it's probably a private field in a base class.
FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// Search base classes for private fields only. Public fields are found above // Search base classes for private fields only. Public fields are found above
while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
return field; return field;
} }
/// <summary> Get all classes deriving from baseType via reflection </summary> /// <summary> Get all classes deriving from baseType via reflection </summary>
public static Type[] GetDerivedTypes(this Type baseType) { public static Type[] GetDerivedTypes(this Type baseType) {
List<System.Type> types = new List<System.Type>(); List<System.Type> types = new List<System.Type>();
System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies) { foreach (Assembly assembly in assemblies) {
try { try {
types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
} catch (ReflectionTypeLoadException) { } } catch (ReflectionTypeLoadException) { }
} }
return types.ToArray(); return types.ToArray();
} }
/// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary> /// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary>
public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) { public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) {
KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj); KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj);
if (items.Length != 0) { if (items.Length != 0) {
contextMenu.AddSeparator(""); contextMenu.AddSeparator("");
List<string> invalidatedEntries = new List<string>(); List<string> invalidatedEntries = new List<string>();
foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) { foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) {
if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) { if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) {
invalidatedEntries.Add(checkValidate.Key.menuItem); invalidatedEntries.Add(checkValidate.Key.menuItem);
} }
} }
for (int i = 0; i < items.Length; i++) { for (int i = 0; i < items.Length; i++) {
KeyValuePair<ContextMenu, MethodInfo> kvp = items[i]; KeyValuePair<ContextMenu, MethodInfo> kvp = items[i];
if (invalidatedEntries.Contains(kvp.Key.menuItem)) { if (invalidatedEntries.Contains(kvp.Key.menuItem)) {
contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem)); contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem));
} else { } else {
contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null)); contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null));
} }
} }
} }
} }
/// <summary> Call OnValidate on target </summary> /// <summary> Call OnValidate on target </summary>
public static void TriggerOnValidate(this UnityEngine.Object target) { public static void TriggerOnValidate(this UnityEngine.Object target) {
System.Reflection.MethodInfo onValidate = null; System.Reflection.MethodInfo onValidate = null;
if (target != null) { if (target != null) {
onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (onValidate != null) onValidate.Invoke(target, null); if (onValidate != null) onValidate.Invoke(target, null);
} }
} }
public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) { public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) {
Type type = obj.GetType(); Type type = obj.GetType();
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
List<KeyValuePair<ContextMenu, MethodInfo>> kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>(); List<KeyValuePair<ContextMenu, MethodInfo>> kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>();
for (int i = 0; i < methods.Length; i++) { for (int i = 0; i < methods.Length; i++) {
ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray(); ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray();
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0) continue;
if (methods[i].GetParameters().Length != 0) { if (methods[i].GetParameters().Length != 0) {
Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands."); Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands.");
continue; continue;
} }
if (methods[i].IsStatic) { if (methods[i].IsStatic) {
Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " is static and cannot be used for context menu commands."); Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " is static and cannot be used for context menu commands.");
continue; continue;
} }
for (int k = 0; k < attribs.Length; k++) { for (int k = 0; k < attribs.Length; k++) {
kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i])); kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i]));
} }
} }
#if UNITY_5_5_OR_NEWER #if UNITY_5_5_OR_NEWER
//Sort menu items //Sort menu items
kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority)); kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority));
#endif #endif
return kvp.ToArray(); return kvp.ToArray();
} }
/// <summary> Very crude. Uses a lot of reflection. </summary> /// <summary> Very crude. Uses a lot of reflection. </summary>
public static void OpenPreferences() { public static void OpenPreferences() {
try { try {
#if UNITY_2018_3_OR_NEWER #if UNITY_2018_3_OR_NEWER
SettingsService.OpenUserPreferences("Preferences/Node Editor"); SettingsService.OpenUserPreferences("Preferences/Node Editor");
#else #else
//Open preferences window //Open preferences window
Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow)); Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow));
Type type = assembly.GetType("UnityEditor.PreferencesWindow"); Type type = assembly.GetType("UnityEditor.PreferencesWindow");
type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null); type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
//Get the window //Get the window
EditorWindow window = EditorWindow.GetWindow(type); EditorWindow window = EditorWindow.GetWindow(type);
//Make sure custom sections are added (because waiting for it to happen automatically is too slow) //Make sure custom sections are added (because waiting for it to happen automatically is too slow)
FieldInfo refreshField = type.GetField("m_RefreshCustomPreferences", BindingFlags.NonPublic | BindingFlags.Instance); FieldInfo refreshField = type.GetField("m_RefreshCustomPreferences", BindingFlags.NonPublic | BindingFlags.Instance);
if ((bool) refreshField.GetValue(window)) { if ((bool) refreshField.GetValue(window)) {
type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null); type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null);
refreshField.SetValue(window, false); refreshField.SetValue(window, false);
} }
//Get sections //Get sections
FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic);
IList sections = sectionsField.GetValue(window) as IList; IList sections = sectionsField.GetValue(window) as IList;
//Iterate through sections and check contents //Iterate through sections and check contents
Type sectionType = sectionsField.FieldType.GetGenericArguments() [0]; Type sectionType = sectionsField.FieldType.GetGenericArguments() [0];
FieldInfo sectionContentField = sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public); FieldInfo sectionContentField = sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public);
for (int i = 0; i < sections.Count; i++) { for (int i = 0; i < sections.Count; i++) {
GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent; GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent;
if (sectionContent.text == "Node Editor") { if (sectionContent.text == "Node Editor") {
//Found contents - Set index //Found contents - Set index
FieldInfo sectionIndexField = type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo sectionIndexField = type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic);
sectionIndexField.SetValue(window, i); sectionIndexField.SetValue(window, i);
return; return;
} }
} }
#endif #endif
} catch (Exception e) { } catch (Exception e) {
Debug.LogError(e); Debug.LogError(e);
Debug.LogWarning("Unity has changed around internally. Can't open properties through reflection. Please contact xNode developer and supply unity version number."); Debug.LogWarning("Unity has changed around internally. Can't open properties through reflection. Please contact xNode developer and supply unity version number.");
} }
} }
} }
} }

View File

@ -1,95 +1,95 @@
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
namespace XNodeEditor { namespace XNodeEditor {
public static class NodeEditorResources { public static class NodeEditorResources {
// Textures // Textures
public static Texture2D dot { get { return _dot != null ? _dot : _dot = Resources.Load<Texture2D>("xnode_dot"); } } public static Texture2D dot { get { return _dot != null ? _dot : _dot = Resources.Load<Texture2D>("xnode_dot"); } }
private static Texture2D _dot; private static Texture2D _dot;
public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("xnode_dot_outer"); } } public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("xnode_dot_outer"); } }
private static Texture2D _dotOuter; private static Texture2D _dotOuter;
public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("xnode_node"); } } public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("xnode_node"); } }
private static Texture2D _nodeBody; private static Texture2D _nodeBody;
public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load<Texture2D>("xnode_node_highlight"); } } public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load<Texture2D>("xnode_node_highlight"); } }
private static Texture2D _nodeHighlight; private static Texture2D _nodeHighlight;
// Styles // Styles
public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } } public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } }
public static Styles _styles = null; public static Styles _styles = null;
public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } } public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } }
public class Styles { public class Styles {
public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
public Styles() { public Styles() {
GUIStyle baseStyle = new GUIStyle("Label"); GUIStyle baseStyle = new GUIStyle("Label");
baseStyle.fixedHeight = 18; baseStyle.fixedHeight = 18;
inputPort = new GUIStyle(baseStyle); inputPort = new GUIStyle(baseStyle);
inputPort.alignment = TextAnchor.UpperLeft; inputPort.alignment = TextAnchor.UpperLeft;
inputPort.padding.left = 0; inputPort.padding.left = 0;
inputPort.active.background = dot; inputPort.active.background = dot;
inputPort.normal.background = dotOuter; inputPort.normal.background = dotOuter;
outputPort = new GUIStyle(baseStyle); outputPort = new GUIStyle(baseStyle);
outputPort.alignment = TextAnchor.UpperRight; outputPort.alignment = TextAnchor.UpperRight;
outputPort.padding.right = 0; outputPort.padding.right = 0;
outputPort.active.background = dot; outputPort.active.background = dot;
outputPort.normal.background = dotOuter; outputPort.normal.background = dotOuter;
nodeHeader = new GUIStyle(); nodeHeader = new GUIStyle();
nodeHeader.alignment = TextAnchor.MiddleCenter; nodeHeader.alignment = TextAnchor.MiddleCenter;
nodeHeader.fontStyle = FontStyle.Bold; nodeHeader.fontStyle = FontStyle.Bold;
nodeHeader.normal.textColor = Color.white; nodeHeader.normal.textColor = Color.white;
nodeBody = new GUIStyle(); nodeBody = new GUIStyle();
nodeBody.normal.background = NodeEditorResources.nodeBody; nodeBody.normal.background = NodeEditorResources.nodeBody;
nodeBody.border = new RectOffset(32, 32, 32, 32); nodeBody.border = new RectOffset(32, 32, 32, 32);
nodeBody.padding = new RectOffset(16, 16, 4, 16); nodeBody.padding = new RectOffset(16, 16, 4, 16);
nodeHighlight = new GUIStyle(); nodeHighlight = new GUIStyle();
nodeHighlight.normal.background = NodeEditorResources.nodeHighlight; nodeHighlight.normal.background = NodeEditorResources.nodeHighlight;
nodeHighlight.border = new RectOffset(32, 32, 32, 32); nodeHighlight.border = new RectOffset(32, 32, 32, 32);
tooltip = new GUIStyle("helpBox"); tooltip = new GUIStyle("helpBox");
tooltip.alignment = TextAnchor.MiddleCenter; tooltip.alignment = TextAnchor.MiddleCenter;
} }
} }
public static Texture2D GenerateGridTexture(Color line, Color bg) { public static Texture2D GenerateGridTexture(Color line, Color bg) {
Texture2D tex = new Texture2D(64, 64); Texture2D tex = new Texture2D(64, 64);
Color[] cols = new Color[64 * 64]; Color[] cols = new Color[64 * 64];
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (int x = 0; x < 64; x++) {
Color col = bg; Color col = bg;
if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f); if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f);
if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f); if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f);
cols[(y * 64) + x] = col; cols[(y * 64) + x] = col;
} }
} }
tex.SetPixels(cols); tex.SetPixels(cols);
tex.wrapMode = TextureWrapMode.Repeat; tex.wrapMode = TextureWrapMode.Repeat;
tex.filterMode = FilterMode.Bilinear; tex.filterMode = FilterMode.Bilinear;
tex.name = "Grid"; tex.name = "Grid";
tex.Apply(); tex.Apply();
return tex; return tex;
} }
public static Texture2D GenerateCrossTexture(Color line) { public static Texture2D GenerateCrossTexture(Color line) {
Texture2D tex = new Texture2D(64, 64); Texture2D tex = new Texture2D(64, 64);
Color[] cols = new Color[64 * 64]; Color[] cols = new Color[64 * 64];
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (int x = 0; x < 64; x++) {
Color col = line; Color col = line;
if (y != 31 && x != 31) col.a = 0; if (y != 31 && x != 31) col.a = 0;
cols[(y * 64) + x] = col; cols[(y * 64) + x] = col;
} }
} }
tex.SetPixels(cols); tex.SetPixels(cols);
tex.wrapMode = TextureWrapMode.Clamp; tex.wrapMode = TextureWrapMode.Clamp;
tex.filterMode = FilterMode.Bilinear; tex.filterMode = FilterMode.Bilinear;
tex.name = "Grid"; tex.name = "Grid";
tex.Apply(); tex.Apply();
return tex; return tex;
} }
} }
} }

View File

@ -1,317 +1,317 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> A set of editor-only utilities and extensions for xNode </summary> /// <summary> A set of editor-only utilities and extensions for xNode </summary>
public static class NodeEditorUtilities { public static class NodeEditorUtilities {
/// <summary>C#'s Script Icon [The one MonoBhevaiour Scripts have].</summary> /// <summary>C#'s Script Icon [The one MonoBhevaiour Scripts have].</summary>
private static Texture2D scriptIcon = (EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D); private static Texture2D scriptIcon = (EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D);
/// Saves Attribute from Type+Field for faster lookup. Resets on recompiles. /// Saves Attribute from Type+Field for faster lookup. Resets on recompiles.
private static Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>> typeAttributes = new Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>>(); private static Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>> typeAttributes = new Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>>();
/// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles. /// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles.
private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>(); private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>();
public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute { public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
object[] attribs = classType.GetCustomAttributes(typeof(T), false); object[] attribs = classType.GetCustomAttributes(typeof(T), false);
return GetAttrib(attribs, out attribOut); return GetAttrib(attribs, out attribOut);
} }
public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute { public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute {
for (int i = 0; i < attribs.Length; i++) { for (int i = 0; i < attribs.Length; i++) {
if (attribs[i] is T) { if (attribs[i] is T) {
attribOut = attribs[i] as T; attribOut = attribs[i] as T;
return true; return true;
} }
} }
attribOut = null; attribOut = null;
return false; return false;
} }
public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute { public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
// If we can't find field in the first run, it's probably a private field in a base class. // If we can't find field in the first run, it's probably a private field in a base class.
FieldInfo field = classType.GetFieldInfo(fieldName); FieldInfo field = classType.GetFieldInfo(fieldName);
// This shouldn't happen. Ever. // This shouldn't happen. Ever.
if (field == null) { if (field == null) {
Debug.LogWarning("Field " + fieldName + " couldnt be found"); Debug.LogWarning("Field " + fieldName + " couldnt be found");
attribOut = null; attribOut = null;
return false; return false;
} }
object[] attribs = field.GetCustomAttributes(typeof(T), true); object[] attribs = field.GetCustomAttributes(typeof(T), true);
return GetAttrib(attribs, out attribOut); return GetAttrib(attribs, out attribOut);
} }
public static bool HasAttrib<T>(object[] attribs) where T : Attribute { public static bool HasAttrib<T>(object[] attribs) where T : Attribute {
for (int i = 0; i < attribs.Length; i++) { for (int i = 0; i < attribs.Length; i++) {
if (attribs[i].GetType() == typeof(T)) { if (attribs[i].GetType() == typeof(T)) {
return true; return true;
} }
} }
return false; return false;
} }
public static bool GetCachedAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute { public static bool GetCachedAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
Dictionary<string, Dictionary<Type, Attribute>> typeFields; Dictionary<string, Dictionary<Type, Attribute>> typeFields;
if (!typeAttributes.TryGetValue(classType, out typeFields)) { if (!typeAttributes.TryGetValue(classType, out typeFields)) {
typeFields = new Dictionary<string, Dictionary<Type, Attribute>>(); typeFields = new Dictionary<string, Dictionary<Type, Attribute>>();
typeAttributes.Add(classType, typeFields); typeAttributes.Add(classType, typeFields);
} }
Dictionary<Type, Attribute> typeTypes; Dictionary<Type, Attribute> typeTypes;
if (!typeFields.TryGetValue(fieldName, out typeTypes)) { if (!typeFields.TryGetValue(fieldName, out typeTypes)) {
typeTypes = new Dictionary<Type, Attribute>(); typeTypes = new Dictionary<Type, Attribute>();
typeFields.Add(fieldName, typeTypes); typeFields.Add(fieldName, typeTypes);
} }
Attribute attr; Attribute attr;
if (!typeTypes.TryGetValue(typeof(T), out attr)) { if (!typeTypes.TryGetValue(typeof(T), out attr)) {
if (GetAttrib<T>(classType, fieldName, out attribOut)) { if (GetAttrib<T>(classType, fieldName, out attribOut)) {
typeTypes.Add(typeof(T), attribOut); typeTypes.Add(typeof(T), attribOut);
return true; return true;
} else typeTypes.Add(typeof(T), null); } else typeTypes.Add(typeof(T), null);
} }
if (attr == null) { if (attr == null) {
attribOut = null; attribOut = null;
return false; return false;
} }
attribOut = attr as T; attribOut = attr as T;
return true; return true;
} }
public static List<PropertyAttribute> GetCachedPropertyAttribs(Type classType, string fieldName) { public static List<PropertyAttribute> GetCachedPropertyAttribs(Type classType, string fieldName) {
Dictionary<string, List<PropertyAttribute>> typeFields; Dictionary<string, List<PropertyAttribute>> typeFields;
if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) { if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) {
typeFields = new Dictionary<string, List<PropertyAttribute>>(); typeFields = new Dictionary<string, List<PropertyAttribute>>();
typeOrderedPropertyAttributes.Add(classType, typeFields); typeOrderedPropertyAttributes.Add(classType, typeFields);
} }
List<PropertyAttribute> typeAttributes; List<PropertyAttribute> typeAttributes;
if (!typeFields.TryGetValue(fieldName, out typeAttributes)) { if (!typeFields.TryGetValue(fieldName, out typeAttributes)) {
FieldInfo field = classType.GetFieldInfo(fieldName); FieldInfo field = classType.GetFieldInfo(fieldName);
object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true); object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true);
typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse
typeFields.Add(fieldName, typeAttributes); typeFields.Add(fieldName, typeAttributes);
} }
return typeAttributes; return typeAttributes;
} }
public static bool IsMac() { public static bool IsMac() {
#if UNITY_2017_1_OR_NEWER #if UNITY_2017_1_OR_NEWER
return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX; return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX;
#else #else
return SystemInfo.operatingSystem.StartsWith("Mac"); return SystemInfo.operatingSystem.StartsWith("Mac");
#endif #endif
} }
/// <summary> Returns true if this can be casted to <see cref="Type"/></summary> /// <summary> Returns true if this can be casted to <see cref="Type"/></summary>
public static bool IsCastableTo(this Type from, Type to) { public static bool IsCastableTo(this Type from, Type to) {
if (to.IsAssignableFrom(from)) return true; if (to.IsAssignableFrom(from)) return true;
var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static) var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where( .Where(
m => m.ReturnType == to && m => m.ReturnType == to &&
(m.Name == "op_Implicit" || (m.Name == "op_Implicit" ||
m.Name == "op_Explicit") m.Name == "op_Explicit")
); );
return methods.Count() > 0; return methods.Count() > 0;
} }
/// <summary> /// <summary>
/// Looking for ports with value Type compatible with a given type. /// Looking for ports with value Type compatible with a given type.
/// </summary> /// </summary>
/// <param name="nodeType">Node to search</param> /// <param name="nodeType">Node to search</param>
/// <param name="compatibleType">Type to find compatiblities</param> /// <param name="compatibleType">Type to find compatiblities</param>
/// <param name="direction"></param> /// <param name="direction"></param>
/// <returns>True if NodeType has some port with value type compatible</returns> /// <returns>True if NodeType has some port with value type compatible</returns>
public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
Type findType = typeof(XNode.Node.InputAttribute); Type findType = typeof(XNode.Node.InputAttribute);
if (direction == XNode.NodePort.IO.Output) if (direction == XNode.NodePort.IO.Output)
findType = typeof(XNode.Node.OutputAttribute); findType = typeof(XNode.Node.OutputAttribute);
//Get All fields from node type and we go filter only field with portAttribute. //Get All fields from node type and we go filter only field with portAttribute.
//This way is possible to know the values of the all ports and if have some with compatible value tue //This way is possible to know the values of the all ports and if have some with compatible value tue
foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) { foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) {
var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault(); var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault();
if (portAttribute != null) { if (portAttribute != null) {
if (IsCastableTo(f.FieldType, compatibleType)) { if (IsCastableTo(f.FieldType, compatibleType)) {
return true; return true;
} }
} }
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Filter only node types that contains some port value type compatible with an given type /// Filter only node types that contains some port value type compatible with an given type
/// </summary> /// </summary>
/// <param name="nodeTypes">List with all nodes type to filter</param> /// <param name="nodeTypes">List with all nodes type to filter</param>
/// <param name="compatibleType">Compatible Type to Filter</param> /// <param name="compatibleType">Compatible Type to Filter</param>
/// <returns>Return Only Node Types with ports compatible, or an empty list</returns> /// <returns>Return Only Node Types with ports compatible, or an empty list</returns>
public static List<Type> GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { public static List<Type> GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
//Result List //Result List
List<Type> filteredTypes = new List<Type>(); List<Type> filteredTypes = new List<Type>();
//Return empty list //Return empty list
if (nodeTypes == null) { return filteredTypes; } if (nodeTypes == null) { return filteredTypes; }
if (compatibleType == null) { return filteredTypes; } if (compatibleType == null) { return filteredTypes; }
//Find compatiblity //Find compatiblity
foreach (Type findType in nodeTypes) { foreach (Type findType in nodeTypes) {
if (HasCompatiblePortType(findType, compatibleType, direction)) { if (HasCompatiblePortType(findType, compatibleType, direction)) {
filteredTypes.Add(findType); filteredTypes.Add(findType);
} }
} }
return filteredTypes; return filteredTypes;
} }
/// <summary> Return a prettiefied type name. </summary> /// <summary> Return a prettiefied type name. </summary>
public static string PrettyName(this Type type) { public static string PrettyName(this Type type) {
if (type == null) return "null"; if (type == null) return "null";
if (type == typeof(System.Object)) return "object"; if (type == typeof(System.Object)) return "object";
if (type == typeof(float)) return "float"; if (type == typeof(float)) return "float";
else if (type == typeof(int)) return "int"; else if (type == typeof(int)) return "int";
else if (type == typeof(long)) return "long"; else if (type == typeof(long)) return "long";
else if (type == typeof(double)) return "double"; else if (type == typeof(double)) return "double";
else if (type == typeof(string)) return "string"; else if (type == typeof(string)) return "string";
else if (type == typeof(bool)) return "bool"; else if (type == typeof(bool)) return "bool";
else if (type.IsGenericType) { else if (type.IsGenericType) {
string s = ""; string s = "";
Type genericType = type.GetGenericTypeDefinition(); Type genericType = type.GetGenericTypeDefinition();
if (genericType == typeof(List<>)) s = "List"; if (genericType == typeof(List<>)) s = "List";
else s = type.GetGenericTypeDefinition().ToString(); else s = type.GetGenericTypeDefinition().ToString();
Type[] types = type.GetGenericArguments(); Type[] types = type.GetGenericArguments();
string[] stypes = new string[types.Length]; string[] stypes = new string[types.Length];
for (int i = 0; i < types.Length; i++) { for (int i = 0; i < types.Length; i++) {
stypes[i] = types[i].PrettyName(); stypes[i] = types[i].PrettyName();
} }
return s + "<" + string.Join(", ", stypes) + ">"; return s + "<" + string.Join(", ", stypes) + ">";
} else if (type.IsArray) { } else if (type.IsArray) {
string rank = ""; string rank = "";
for (int i = 1; i < type.GetArrayRank(); i++) { for (int i = 1; i < type.GetArrayRank(); i++) {
rank += ","; rank += ",";
} }
Type elementType = type.GetElementType(); Type elementType = type.GetElementType();
if (!elementType.IsArray) return elementType.PrettyName() + "[" + rank + "]"; if (!elementType.IsArray) return elementType.PrettyName() + "[" + rank + "]";
else { else {
string s = elementType.PrettyName(); string s = elementType.PrettyName();
int i = s.IndexOf('['); int i = s.IndexOf('[');
return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i); return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i);
} }
} else return type.ToString(); } else return type.ToString();
} }
/// <summary> Returns the default name for the node type. </summary> /// <summary> Returns the default name for the node type. </summary>
public static string NodeDefaultName(Type type) { public static string NodeDefaultName(Type type) {
string typeName = type.Name; string typeName = type.Name;
// Automatically remove redundant 'Node' postfix // Automatically remove redundant 'Node' postfix
if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node"));
typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName); typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName);
return typeName; return typeName;
} }
/// <summary> Returns the default creation path for the node type. </summary> /// <summary> Returns the default creation path for the node type. </summary>
public static string NodeDefaultPath(Type type) { public static string NodeDefaultPath(Type type) {
string typePath = type.ToString().Replace('.', '/'); string typePath = type.ToString().Replace('.', '/');
// Automatically remove redundant 'Node' postfix // Automatically remove redundant 'Node' postfix
if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node")); if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node"));
typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath); typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath);
return typePath; return typePath;
} }
/// <summary>Creates a new C# Class.</summary> /// <summary>Creates a new C# Class.</summary>
[MenuItem("Assets/Create/xNode/Node C# Script", false, 89)] [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)]
private static void CreateNode() { private static void CreateNode() {
string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs"); string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs");
if (guids.Length == 0) { if (guids.Length == 0) {
Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database"); Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database");
return; return;
} }
string path = AssetDatabase.GUIDToAssetPath(guids[0]); string path = AssetDatabase.GUIDToAssetPath(guids[0]);
CreateFromTemplate( CreateFromTemplate(
"NewNode.cs", "NewNode.cs",
path path
); );
} }
/// <summary>Creates a new C# Class.</summary> /// <summary>Creates a new C# Class.</summary>
[MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)] [MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)]
private static void CreateGraph() { private static void CreateGraph() {
string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs"); string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs");
if (guids.Length == 0) { if (guids.Length == 0) {
Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database"); Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database");
return; return;
} }
string path = AssetDatabase.GUIDToAssetPath(guids[0]); string path = AssetDatabase.GUIDToAssetPath(guids[0]);
CreateFromTemplate( CreateFromTemplate(
"NewNodeGraph.cs", "NewNodeGraph.cs",
path path
); );
} }
public static void CreateFromTemplate(string initialName, string templatePath) { public static void CreateFromTemplate(string initialName, string templatePath) {
ProjectWindowUtil.StartNameEditingIfProjectWindowExists( ProjectWindowUtil.StartNameEditingIfProjectWindowExists(
0, 0,
ScriptableObject.CreateInstance<DoCreateCodeFile>(), ScriptableObject.CreateInstance<DoCreateCodeFile>(),
initialName, initialName,
scriptIcon, scriptIcon,
templatePath templatePath
); );
} }
/// Inherits from EndNameAction, must override EndNameAction.Action /// Inherits from EndNameAction, must override EndNameAction.Action
public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction { public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction {
public override void Action(int instanceId, string pathName, string resourceFile) { public override void Action(int instanceId, string pathName, string resourceFile) {
Object o = CreateScript(pathName, resourceFile); Object o = CreateScript(pathName, resourceFile);
ProjectWindowUtil.ShowCreatedAsset(o); ProjectWindowUtil.ShowCreatedAsset(o);
} }
} }
/// <summary>Creates Script from Template's path.</summary> /// <summary>Creates Script from Template's path.</summary>
internal static UnityEngine.Object CreateScript(string pathName, string templatePath) { internal static UnityEngine.Object CreateScript(string pathName, string templatePath) {
string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty); string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty);
string templateText = string.Empty; string templateText = string.Empty;
UTF8Encoding encoding = new UTF8Encoding(true, false); UTF8Encoding encoding = new UTF8Encoding(true, false);
if (File.Exists(templatePath)) { if (File.Exists(templatePath)) {
/// Read procedures. /// Read procedures.
StreamReader reader = new StreamReader(templatePath); StreamReader reader = new StreamReader(templatePath);
templateText = reader.ReadToEnd(); templateText = reader.ReadToEnd();
reader.Close(); reader.Close();
templateText = templateText.Replace("#SCRIPTNAME#", className); templateText = templateText.Replace("#SCRIPTNAME#", className);
templateText = templateText.Replace("#NOTRIM#", string.Empty); templateText = templateText.Replace("#NOTRIM#", string.Empty);
/// You can replace as many tags you make on your templates, just repeat Replace function /// You can replace as many tags you make on your templates, just repeat Replace function
/// e.g.: /// e.g.:
/// templateText = templateText.Replace("#NEWTAG#", "MyText"); /// templateText = templateText.Replace("#NEWTAG#", "MyText");
/// Write procedures. /// Write procedures.
StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding); StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding);
writer.Write(templateText); writer.Write(templateText);
writer.Close(); writer.Close();
AssetDatabase.ImportAsset(pathName); AssetDatabase.ImportAsset(pathName);
return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object)); return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object));
} else { } else {
Debug.LogError(string.Format("The template file was not found: {0}", templatePath)); Debug.LogError(string.Format("The template file was not found: {0}", templatePath));
return null; return null;
} }
} }
} }
} }

View File

@ -1,216 +1,216 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEditor.Callbacks; using UnityEditor.Callbacks;
using UnityEngine; using UnityEngine;
using System; using System;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace XNodeEditor { namespace XNodeEditor {
[InitializeOnLoad] [InitializeOnLoad]
public partial class NodeEditorWindow : EditorWindow { public partial class NodeEditorWindow : EditorWindow {
public static NodeEditorWindow current; public static NodeEditorWindow current;
/// <summary> Stores node positions for all nodePorts. </summary> /// <summary> Stores node positions for all nodePorts. </summary>
public Dictionary<XNode.NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } } public Dictionary<XNode.NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } }
private Dictionary<XNode.NodePort, Rect> _portConnectionPoints = new Dictionary<XNode.NodePort, Rect>(); private Dictionary<XNode.NodePort, Rect> _portConnectionPoints = new Dictionary<XNode.NodePort, Rect>();
[SerializeField] private NodePortReference[] _references = new NodePortReference[0]; [SerializeField] private NodePortReference[] _references = new NodePortReference[0];
[SerializeField] private Rect[] _rects = new Rect[0]; [SerializeField] private Rect[] _rects = new Rect[0];
private Func<bool> isDocked { private Func<bool> isDocked {
get { get {
if (_isDocked == null) _isDocked = this.GetIsDockedDelegate(); if (_isDocked == null) _isDocked = this.GetIsDockedDelegate();
return _isDocked; return _isDocked;
} }
} }
private Func<bool> _isDocked; private Func<bool> _isDocked;
[System.Serializable] private class NodePortReference { [System.Serializable] private class NodePortReference {
[SerializeField] private XNode.Node _node; [SerializeField] private XNode.Node _node;
[SerializeField] private string _name; [SerializeField] private string _name;
public NodePortReference(XNode.NodePort nodePort) { public NodePortReference(XNode.NodePort nodePort) {
_node = nodePort.node; _node = nodePort.node;
_name = nodePort.fieldName; _name = nodePort.fieldName;
} }
public XNode.NodePort GetNodePort() { public XNode.NodePort GetNodePort() {
if (_node == null) { if (_node == null) {
return null; return null;
} }
return _node.GetPort(_name); return _node.GetPort(_name);
} }
} }
private void OnDisable() { private void OnDisable() {
// Cache portConnectionPoints before serialization starts // Cache portConnectionPoints before serialization starts
int count = portConnectionPoints.Count; int count = portConnectionPoints.Count;
_references = new NodePortReference[count]; _references = new NodePortReference[count];
_rects = new Rect[count]; _rects = new Rect[count];
int index = 0; int index = 0;
foreach (var portConnectionPoint in portConnectionPoints) { foreach (var portConnectionPoint in portConnectionPoints) {
_references[index] = new NodePortReference(portConnectionPoint.Key); _references[index] = new NodePortReference(portConnectionPoint.Key);
_rects[index] = portConnectionPoint.Value; _rects[index] = portConnectionPoint.Value;
index++; index++;
} }
} }
private void OnEnable() { private void OnEnable() {
// Reload portConnectionPoints if there are any // Reload portConnectionPoints if there are any
int length = _references.Length; int length = _references.Length;
if (length == _rects.Length) { if (length == _rects.Length) {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
XNode.NodePort nodePort = _references[i].GetNodePort(); XNode.NodePort nodePort = _references[i].GetNodePort();
if (nodePort != null) if (nodePort != null)
_portConnectionPoints.Add(nodePort, _rects[i]); _portConnectionPoints.Add(nodePort, _rects[i]);
} }
} }
} }
public Dictionary<XNode.Node, Vector2> nodeSizes { get { return _nodeSizes; } } public Dictionary<XNode.Node, Vector2> nodeSizes { get { return _nodeSizes; } }
private Dictionary<XNode.Node, Vector2> _nodeSizes = new Dictionary<XNode.Node, Vector2>(); private Dictionary<XNode.Node, Vector2> _nodeSizes = new Dictionary<XNode.Node, Vector2>();
public XNode.NodeGraph graph; public XNode.NodeGraph graph;
public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } }
private Vector2 _panOffset; private Vector2 _panOffset;
public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } } public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } }
private float _zoom = 1; private float _zoom = 1;
void OnFocus() { void OnFocus() {
current = this; current = this;
ValidateGraphEditor(); ValidateGraphEditor();
if (graphEditor != null) { if (graphEditor != null) {
graphEditor.OnWindowFocus(); graphEditor.OnWindowFocus();
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} }
dragThreshold = Math.Max(1f, Screen.width / 1000f); dragThreshold = Math.Max(1f, Screen.width / 1000f);
} }
void OnLostFocus() { void OnLostFocus() {
if (graphEditor != null) graphEditor.OnWindowFocusLost(); if (graphEditor != null) graphEditor.OnWindowFocusLost();
} }
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
private static void OnLoad() { private static void OnLoad() {
Selection.selectionChanged -= OnSelectionChanged; Selection.selectionChanged -= OnSelectionChanged;
Selection.selectionChanged += OnSelectionChanged; Selection.selectionChanged += OnSelectionChanged;
} }
/// <summary> Handle Selection Change events</summary> /// <summary> Handle Selection Change events</summary>
private static void OnSelectionChanged() { private static void OnSelectionChanged() {
XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph;
if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) {
if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph); if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph);
} }
} }
/// <summary> Make sure the graph editor is assigned and to the right object </summary> /// <summary> Make sure the graph editor is assigned and to the right object </summary>
private void ValidateGraphEditor() { private void ValidateGraphEditor() {
NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this); NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this);
if (this.graphEditor != graphEditor && graphEditor != null) { if (this.graphEditor != graphEditor && graphEditor != null) {
this.graphEditor = graphEditor; this.graphEditor = graphEditor;
graphEditor.OnOpen(); graphEditor.OnOpen();
} }
} }
/// <summary> Create editor window </summary> /// <summary> Create editor window </summary>
public static NodeEditorWindow Init() { public static NodeEditorWindow Init() {
NodeEditorWindow w = CreateInstance<NodeEditorWindow>(); NodeEditorWindow w = CreateInstance<NodeEditorWindow>();
w.titleContent = new GUIContent("xNode"); w.titleContent = new GUIContent("xNode");
w.wantsMouseMove = true; w.wantsMouseMove = true;
w.Show(); w.Show();
return w; return w;
} }
public void Save() { public void Save() {
if (AssetDatabase.Contains(graph)) { if (AssetDatabase.Contains(graph)) {
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else SaveAs(); } else SaveAs();
} }
public void SaveAs() { public void SaveAs() {
string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", "");
if (string.IsNullOrEmpty(path)) return; if (string.IsNullOrEmpty(path)) return;
else { else {
XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath<XNode.NodeGraph>(path); XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath<XNode.NodeGraph>(path);
if (existingGraph != null) AssetDatabase.DeleteAsset(path); if (existingGraph != null) AssetDatabase.DeleteAsset(path);
AssetDatabase.CreateAsset(graph, path); AssetDatabase.CreateAsset(graph, path);
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} }
} }
private void DraggableWindow(int windowID) { private void DraggableWindow(int windowID) {
GUI.DragWindow(); GUI.DragWindow();
} }
public Vector2 WindowToGridPosition(Vector2 windowPosition) { public Vector2 WindowToGridPosition(Vector2 windowPosition) {
return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom; return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom;
} }
public Vector2 GridToWindowPosition(Vector2 gridPosition) { public Vector2 GridToWindowPosition(Vector2 gridPosition) {
return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom);
} }
public Rect GridToWindowRectNoClipped(Rect gridRect) { public Rect GridToWindowRectNoClipped(Rect gridRect) {
gridRect.position = GridToWindowPositionNoClipped(gridRect.position); gridRect.position = GridToWindowPositionNoClipped(gridRect.position);
return gridRect; return gridRect;
} }
public Rect GridToWindowRect(Rect gridRect) { public Rect GridToWindowRect(Rect gridRect) {
gridRect.position = GridToWindowPosition(gridRect.position); gridRect.position = GridToWindowPosition(gridRect.position);
gridRect.size /= zoom; gridRect.size /= zoom;
return gridRect; return gridRect;
} }
public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) {
Vector2 center = position.size * 0.5f; Vector2 center = position.size * 0.5f;
// UI Sharpness complete fix - Round final offset not panOffset // UI Sharpness complete fix - Round final offset not panOffset
float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x)); float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x));
float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y)); float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y));
return new Vector2(xOffset, yOffset); return new Vector2(xOffset, yOffset);
} }
public void SelectNode(XNode.Node node, bool add) { public void SelectNode(XNode.Node node, bool add) {
if (add) { if (add) {
List<Object> selection = new List<Object>(Selection.objects); List<Object> selection = new List<Object>(Selection.objects);
selection.Add(node); selection.Add(node);
Selection.objects = selection.ToArray(); Selection.objects = selection.ToArray();
} else Selection.objects = new Object[] { node }; } else Selection.objects = new Object[] { node };
} }
public void DeselectNode(XNode.Node node) { public void DeselectNode(XNode.Node node) {
List<Object> selection = new List<Object>(Selection.objects); List<Object> selection = new List<Object>(Selection.objects);
selection.Remove(node); selection.Remove(node);
Selection.objects = selection.ToArray(); Selection.objects = selection.ToArray();
} }
[OnOpenAsset(0)] [OnOpenAsset(0)]
public static bool OnOpen(int instanceID, int line) { public static bool OnOpen(int instanceID, int line) {
XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph;
if (nodeGraph != null) { if (nodeGraph != null) {
Open(nodeGraph); Open(nodeGraph);
return true; return true;
} }
return false; return false;
} }
/// <summary>Open the provided graph in the NodeEditor</summary> /// <summary>Open the provided graph in the NodeEditor</summary>
public static NodeEditorWindow Open(XNode.NodeGraph graph) { public static NodeEditorWindow Open(XNode.NodeGraph graph) {
if (!graph) return null; if (!graph) return null;
NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow;
w.wantsMouseMove = true; w.wantsMouseMove = true;
w.graph = graph; w.graph = graph;
return w; return w;
} }
/// <summary> Repaint all open NodeEditorWindows. </summary> /// <summary> Repaint all open NodeEditorWindows. </summary>
public static void RepaintAll() { public static void RepaintAll() {
NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>(); NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>();
for (int i = 0; i < windows.Length; i++) { for (int i = 0; i < windows.Length; i++) {
windows[i].Repaint(); windows[i].Repaint();
} }
} }
} }
} }

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

121
README.md
View File

@ -1,121 +0,0 @@
<img align="right" width="100" height="100" src="https://user-images.githubusercontent.com/37786733/41541140-71602302-731a-11e8-9434-79b3a57292b6.png">
[![Discord](https://img.shields.io/discord/361769369404964864.svg)](https://discord.gg/qgPrHv4)
[![GitHub issues](https://img.shields.io/github/issues/Siccity/xNode.svg)](https://github.com/Siccity/xNode/issues)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Siccity/xNode/master/LICENSE.md)
[![GitHub Wiki](https://img.shields.io/badge/wiki-available-brightgreen.svg)](https://github.com/Siccity/xNode/wiki)
[![openupm](https://img.shields.io/npm/v/com.github.siccity.xnode?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.github.siccity.xnode/)
[Downloads](https://github.com/Siccity/xNode/releases) / [Asset Store](http://u3d.as/108S) / [Documentation](https://github.com/Siccity/xNode/wiki)
Support xNode on [Ko-fi](https://ko-fi.com/Z8Z5DYWA) or [Patreon](https://www.patreon.com/thorbrigsted)
For full Odin support, consider using [KAJed82's fork](https://github.com/KAJed82/xNode)
### xNode
Thinking of developing a node-based plugin? Then this is for you. You can download it as an archive and unpack to a new unity project, or connect it as git submodule.
xNode is super userfriendly, intuitive and will help you reap the benefits of node graphs in no time.
With a minimal footprint, it is ideal as a base for custom state machines, dialogue systems, decision makers etc.
<p align="center">
<img src="https://user-images.githubusercontent.com/6402525/53689100-3821e680-3d4e-11e9-8440-e68bd802bfd9.png">
</p>
### Key features
* Lightweight in runtime
* Very little boilerplate code
* Strong separation of editor and runtime code
* No runtime reflection (unless you need to edit/build node graphs at runtime. In this case, all reflection is cached.)
* Does not rely on any 3rd party plugins
* Custom node inspector code is very similar to regular custom inspector code
* Supported from Unity 5.3 and up
### Wiki
* [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph
* [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects
### Installation
<details><summary>Instructions</summary>
### Installing with Unity Package Manager
***Via Git URL***
*(Requires Unity version 2018.3.0b7 or above)*
To install this project as a [Git dependency](https://docs.unity3d.com/Manual/upm-git.html) using the Unity Package Manager,
add the following line to your project's `manifest.json`:
```
"com.github.siccity.xnode": "https://github.com/siccity/xNode.git"
```
You will need to have Git installed and available in your system's PATH.
If you are using [Assembly Definitions](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html) in your project, you will need to add `XNode` and/or `XNodeEditor` as Assembly Definition References.
***Via OpenUPM***
The package is available on the [openupm registry](https://openupm.com). It's recommended to install it via [openupm-cli](https://github.com/openupm/openupm-cli).
```
openupm add com.github.siccity.xnode
```
### Installing with git
***Via Git Submodule***
To add xNode as a [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) in your existing git project,
run the following git command from your project root:
```
git submodule add git@github.com:Siccity/xNode.git Assets/Submodules/xNode
```
### Installing 'the old way'
If no source control or package manager is available to you, you can simply copy/paste the source files into your assets folder.
</details>
### Node example:
```csharp
// public classes deriving from Node are registered as nodes for use within a graph
public class MathNode : Node {
// Adding [Input] or [Output] is all you need to do to register a field as a valid port on your node
[Input] public float a;
[Input] public float b;
// The value of an output node field is not used for anything, but could be used for caching output results
[Output] public float result;
[Output] public float sum;
// The value of 'mathType' will be displayed on the node in an editable format, similar to the inspector
public MathType mathType = MathType.Add;
public enum MathType { Add, Subtract, Multiply, Divide}
// GetValue should be overridden to return a value for any specified output port
public override object GetValue(NodePort port) {
// Get new a and b values from input connections. Fallback to field values if input is not connected
float a = GetInputValue<float>("a", this.a);
float b = GetInputValue<float>("b", this.b);
// After you've gotten your input values, you can perform your calculations and return a value
if (port.fieldName == "result")
switch(mathType) {
case MathType.Add: default: return a + b;
case MathType.Subtract: return a - b;
case MathType.Multiply: return a * b;
case MathType.Divide: return a / b;
}
else if (port.fieldName == "sum") return a + b;
else return 0f;
}
}
```
### Plugins
Plugins are repositories that add functionality to xNode
* [xNodeGroups](https://github.com/Siccity/xNodeGroups): adds resizable groups
### Community
Join the [Discord](https://discord.gg/qgPrHv4 "Join Discord server") server to leave feedback or get support.
Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page.

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 243efae3a6b7941ad8f8e54dcf38ce8c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 657b15cb3ec32a24ca80faebf094d0f4 guid: ed1a0425bcd8ed24db55f26e9fb193ec
folderAsset: yes folderAsset: yes
timeCreated: 1505418321
licenseType: Free
DefaultImporter: DefaultImporter:
externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@ -1,421 +1,421 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace XNode { namespace XNode {
/// <summary> /// <summary>
/// Base class for all nodes /// Base class for all nodes
/// </summary> /// </summary>
/// <example> /// <example>
/// Classes extending this class will be considered as valid nodes by xNode. /// Classes extending this class will be considered as valid nodes by xNode.
/// <code> /// <code>
/// [System.Serializable] /// [System.Serializable]
/// public class Adder : Node { /// public class Adder : Node {
/// [Input] public float a; /// [Input] public float a;
/// [Input] public float b; /// [Input] public float b;
/// [Output] public float result; /// [Output] public float result;
/// ///
/// // GetValue should be overridden to return a value for any specified output port /// // GetValue should be overridden to return a value for any specified output port
/// public override object GetValue(NodePort port) { /// public override object GetValue(NodePort port) {
/// return a + b; /// return a + b;
/// } /// }
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
[Serializable] [Serializable]
public abstract class Node : ScriptableObject { public abstract class Node : ScriptableObject {
/// <summary> Used by <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> to determine when to display the field value associated with a <see cref="NodePort"/> </summary> /// <summary> Used by <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> to determine when to display the field value associated with a <see cref="NodePort"/> </summary>
public enum ShowBackingValue { public enum ShowBackingValue {
/// <summary> Never show the backing value </summary> /// <summary> Never show the backing value </summary>
Never, Never,
/// <summary> Show the backing value only when the port does not have any active connections </summary> /// <summary> Show the backing value only when the port does not have any active connections </summary>
Unconnected, Unconnected,
/// <summary> Always show the backing value </summary> /// <summary> Always show the backing value </summary>
Always Always
} }
public enum ConnectionType { public enum ConnectionType {
/// <summary> Allow multiple connections</summary> /// <summary> Allow multiple connections</summary>
Multiple, Multiple,
/// <summary> always override the current connection </summary> /// <summary> always override the current connection </summary>
Override, Override,
} }
/// <summary> Tells which types of input to allow </summary> /// <summary> Tells which types of input to allow </summary>
public enum TypeConstraint { public enum TypeConstraint {
/// <summary> Allow all types of input</summary> /// <summary> Allow all types of input</summary>
None, None,
/// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary> /// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary>
Inherited, Inherited,
/// <summary> Allow only similar types </summary> /// <summary> Allow only similar types </summary>
Strict, Strict,
/// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary> /// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
InheritedInverse, InheritedInverse,
/// <summary> Allow connections where output value type is assignable from input value or input value type is assignable from output value type</summary> /// <summary> Allow connections where output value type is assignable from input value or input value type is assignable from output value type</summary>
InheritedAny InheritedAny
} }
#region Obsolete #region Obsolete
[Obsolete("Use DynamicPorts instead")] [Obsolete("Use DynamicPorts instead")]
public IEnumerable<NodePort> InstancePorts { get { return DynamicPorts; } } public IEnumerable<NodePort> InstancePorts { get { return DynamicPorts; } }
[Obsolete("Use DynamicOutputs instead")] [Obsolete("Use DynamicOutputs instead")]
public IEnumerable<NodePort> InstanceOutputs { get { return DynamicOutputs; } } public IEnumerable<NodePort> InstanceOutputs { get { return DynamicOutputs; } }
[Obsolete("Use DynamicInputs instead")] [Obsolete("Use DynamicInputs instead")]
public IEnumerable<NodePort> InstanceInputs { get { return DynamicInputs; } } public IEnumerable<NodePort> InstanceInputs { get { return DynamicInputs; } }
[Obsolete("Use AddDynamicInput instead")] [Obsolete("Use AddDynamicInput instead")]
public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicInput(type, connectionType, typeConstraint, fieldName); return AddDynamicInput(type, connectionType, typeConstraint, fieldName);
} }
[Obsolete("Use AddDynamicOutput instead")] [Obsolete("Use AddDynamicOutput instead")]
public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicOutput(type, connectionType, typeConstraint, fieldName); return AddDynamicOutput(type, connectionType, typeConstraint, fieldName);
} }
[Obsolete("Use AddDynamicPort instead")] [Obsolete("Use AddDynamicPort instead")]
private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName); return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
} }
[Obsolete("Use RemoveDynamicPort instead")] [Obsolete("Use RemoveDynamicPort instead")]
public void RemoveInstancePort(string fieldName) { public void RemoveInstancePort(string fieldName) {
RemoveDynamicPort(fieldName); RemoveDynamicPort(fieldName);
} }
[Obsolete("Use RemoveDynamicPort instead")] [Obsolete("Use RemoveDynamicPort instead")]
public void RemoveInstancePort(NodePort port) { public void RemoveInstancePort(NodePort port) {
RemoveDynamicPort(port); RemoveDynamicPort(port);
} }
[Obsolete("Use ClearDynamicPorts instead")] [Obsolete("Use ClearDynamicPorts instead")]
public void ClearInstancePorts() { public void ClearInstancePorts() {
ClearDynamicPorts(); ClearDynamicPorts();
} }
#endregion #endregion
/// <summary> Iterate over all ports on this node. </summary> /// <summary> Iterate over all ports on this node. </summary>
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } } public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
/// <summary> Iterate over all outputs on this node. </summary> /// <summary> Iterate over all outputs on this node. </summary>
public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } }
/// <summary> Iterate over all inputs on this node. </summary> /// <summary> Iterate over all inputs on this node. </summary>
public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } }
/// <summary> Iterate over all dynamic ports on this node. </summary> /// <summary> Iterate over all dynamic ports on this node. </summary>
public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } }
/// <summary> Iterate over all dynamic outputs on this node. </summary> /// <summary> Iterate over all dynamic outputs on this node. </summary>
public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } }
/// <summary> Iterate over all dynamic inputs on this node. </summary> /// <summary> Iterate over all dynamic inputs on this node. </summary>
public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } }
/// <summary> Parent <see cref="NodeGraph"/> </summary> /// <summary> Parent <see cref="NodeGraph"/> </summary>
[SerializeField] public NodeGraph graph; [SerializeField] public NodeGraph graph;
/// <summary> Position on the <see cref="NodeGraph"/> </summary> /// <summary> Position on the <see cref="NodeGraph"/> </summary>
[SerializeField] public Vector2 position; [SerializeField] public Vector2 position;
/// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary> /// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary>
[SerializeField] private NodePortDictionary ports = new NodePortDictionary(); [SerializeField] private NodePortDictionary ports = new NodePortDictionary();
/// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary> /// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
public static NodeGraph graphHotfix; public static NodeGraph graphHotfix;
protected void OnEnable() { protected void OnEnable() {
if (graphHotfix != null) graph = graphHotfix; if (graphHotfix != null) graph = graphHotfix;
graphHotfix = null; graphHotfix = null;
UpdatePorts(); UpdatePorts();
Init(); Init();
} }
/// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. </summary> /// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. </summary>
public void UpdatePorts() { public void UpdatePorts() {
NodeDataCache.UpdatePorts(this, ports); NodeDataCache.UpdatePorts(this, ports);
} }
/// <summary> Initialize node. Called on enable. </summary> /// <summary> Initialize node. Called on enable. </summary>
protected virtual void Init() { } protected virtual void Init() { }
/// <summary> Checks all connections for invalid references, and removes them. </summary> /// <summary> Checks all connections for invalid references, and removes them. </summary>
public void VerifyConnections() { public void VerifyConnections() {
foreach (NodePort port in Ports) port.VerifyConnections(); foreach (NodePort port in Ports) port.VerifyConnections();
} }
#region Dynamic Ports #region Dynamic Ports
/// <summary> Convenience function. </summary> /// <summary> Convenience function. </summary>
/// <seealso cref="AddInstancePort"/> /// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceOutput"/> /// <seealso cref="AddInstanceOutput"/>
public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { public NodePort AddDynamicInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName); return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
} }
/// <summary> Convenience function. </summary> /// <summary> Convenience function. </summary>
/// <seealso cref="AddInstancePort"/> /// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceInput"/> /// <seealso cref="AddInstanceInput"/>
public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName); return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
} }
/// <summary> Add a dynamic, serialized port to this node. </summary> /// <summary> Add a dynamic, serialized port to this node. </summary>
/// <seealso cref="AddDynamicInput"/> /// <seealso cref="AddDynamicInput"/>
/// <seealso cref="AddDynamicOutput"/> /// <seealso cref="AddDynamicOutput"/>
private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
if (fieldName == null) { if (fieldName == null) {
fieldName = "dynamicInput_0"; fieldName = "dynamicInput_0";
int i = 0; int i = 0;
while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i); while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
} else if (HasPort(fieldName)) { } else if (HasPort(fieldName)) {
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
return ports[fieldName]; return ports[fieldName];
} }
NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this); NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
ports.Add(fieldName, port); ports.Add(fieldName, port);
return port; return port;
} }
/// <summary> Remove an dynamic port from the node </summary> /// <summary> Remove an dynamic port from the node </summary>
public void RemoveDynamicPort(string fieldName) { public void RemoveDynamicPort(string fieldName) {
NodePort dynamicPort = GetPort(fieldName); NodePort dynamicPort = GetPort(fieldName);
if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
RemoveDynamicPort(GetPort(fieldName)); RemoveDynamicPort(GetPort(fieldName));
} }
/// <summary> Remove an dynamic port from the node </summary> /// <summary> Remove an dynamic port from the node </summary>
public void RemoveDynamicPort(NodePort port) { public void RemoveDynamicPort(NodePort port) {
if (port == null) throw new ArgumentNullException("port"); if (port == null) throw new ArgumentNullException("port");
else if (port.IsStatic) throw new ArgumentException("cannot remove static port"); else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
port.ClearConnections(); port.ClearConnections();
ports.Remove(port.fieldName); ports.Remove(port.fieldName);
} }
/// <summary> Removes all dynamic ports from the node </summary> /// <summary> Removes all dynamic ports from the node </summary>
[ContextMenu("Clear Dynamic Ports")] [ContextMenu("Clear Dynamic Ports")]
public void ClearDynamicPorts() { public void ClearDynamicPorts() {
List<NodePort> dynamicPorts = new List<NodePort>(DynamicPorts); List<NodePort> dynamicPorts = new List<NodePort>(DynamicPorts);
foreach (NodePort port in dynamicPorts) { foreach (NodePort port in dynamicPorts) {
RemoveDynamicPort(port); RemoveDynamicPort(port);
} }
} }
#endregion #endregion
#region Ports #region Ports
/// <summary> Returns output port which matches fieldName </summary> /// <summary> Returns output port which matches fieldName </summary>
public NodePort GetOutputPort(string fieldName) { public NodePort GetOutputPort(string fieldName) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Output) return null; if (port == null || port.direction != NodePort.IO.Output) return null;
else return port; else return port;
} }
/// <summary> Returns input port which matches fieldName </summary> /// <summary> Returns input port which matches fieldName </summary>
public NodePort GetInputPort(string fieldName) { public NodePort GetInputPort(string fieldName) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Input) return null; if (port == null || port.direction != NodePort.IO.Input) return null;
else return port; else return port;
} }
/// <summary> Returns port which matches fieldName </summary> /// <summary> Returns port which matches fieldName </summary>
public NodePort GetPort(string fieldName) { public NodePort GetPort(string fieldName) {
NodePort port; NodePort port;
if (ports.TryGetValue(fieldName, out port)) return port; if (ports.TryGetValue(fieldName, out port)) return port;
else return null; else return null;
} }
public bool HasPort(string fieldName) { public bool HasPort(string fieldName) {
return ports.ContainsKey(fieldName); return ports.ContainsKey(fieldName);
} }
#endregion #endregion
#region Inputs/Outputs #region Inputs/Outputs
/// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary> /// <summary> Return input value for a specified port. Returns fallback value if no ports are connected </summary>
/// <param name="fieldName">Field name of requested input port</param> /// <param name="fieldName">Field name of requested input port</param>
/// <param name="fallback">If no ports are connected, this value will be returned</param> /// <param name="fallback">If no ports are connected, this value will be returned</param>
public T GetInputValue<T>(string fieldName, T fallback = default(T)) { public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValue<T>(); if (port != null && port.IsConnected) return port.GetInputValue<T>();
else return fallback; else return fallback;
} }
/// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary> /// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
/// <param name="fieldName">Field name of requested input port</param> /// <param name="fieldName">Field name of requested input port</param>
/// <param name="fallback">If no ports are connected, this value will be returned</param> /// <param name="fallback">If no ports are connected, this value will be returned</param>
public T[] GetInputValues<T>(string fieldName, params T[] fallback) { public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValues<T>(); if (port != null && port.IsConnected) return port.GetInputValues<T>();
else return fallback; else return fallback;
} }
/// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary> /// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary>
/// <param name="port">The requested port.</param> /// <param name="port">The requested port.</param>
public virtual object GetValue(NodePort port) { public virtual object GetValue(NodePort port) {
Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType()); Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
return null; return null;
} }
#endregion #endregion
/// <summary> Called after a connection between two <see cref="NodePort"/>s is created </summary> /// <summary> Called after a connection between two <see cref="NodePort"/>s is created </summary>
/// <param name="from">Output</param> <param name="to">Input</param> /// <param name="from">Output</param> <param name="to">Input</param>
public virtual void OnCreateConnection(NodePort from, NodePort to) { } public virtual void OnCreateConnection(NodePort from, NodePort to) { }
/// <summary> Called after a connection is removed from this port </summary> /// <summary> Called after a connection is removed from this port </summary>
/// <param name="port">Output or Input</param> /// <param name="port">Output or Input</param>
public virtual void OnRemoveConnection(NodePort port) { } public virtual void OnRemoveConnection(NodePort port) { }
/// <summary> Disconnect everything from this node </summary> /// <summary> Disconnect everything from this node </summary>
public void ClearConnections() { public void ClearConnections() {
foreach (NodePort port in Ports) port.ClearConnections(); foreach (NodePort port in Ports) port.ClearConnections();
} }
#region Attributes #region Attributes
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary> /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public class InputAttribute : Attribute { public class InputAttribute : Attribute {
public ShowBackingValue backingValue; public ShowBackingValue backingValue;
public ConnectionType connectionType; public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")] [Obsolete("Use dynamicPortList instead")]
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList; public bool dynamicPortList;
public TypeConstraint typeConstraint; public TypeConstraint typeConstraint;
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary> /// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param> /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
/// <param name="connectionType">Should we allow multiple connections? </param> /// <param name="connectionType">Should we allow multiple connections? </param>
/// <param name="typeConstraint">Constrains which input connections can be made to this port </param> /// <param name="typeConstraint">Constrains which input connections can be made to this port </param>
/// <param name="dynamicPortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param> /// <param name="dynamicPortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param>
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
this.backingValue = backingValue; this.backingValue = backingValue;
this.connectionType = connectionType; this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList; this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint; this.typeConstraint = typeConstraint;
} }
} }
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary> /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public class OutputAttribute : Attribute { public class OutputAttribute : Attribute {
public ShowBackingValue backingValue; public ShowBackingValue backingValue;
public ConnectionType connectionType; public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")] [Obsolete("Use dynamicPortList instead")]
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } } public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList; public bool dynamicPortList;
public TypeConstraint typeConstraint; public TypeConstraint typeConstraint;
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary> /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param> /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
/// <param name="connectionType">Should we allow multiple connections? </param> /// <param name="connectionType">Should we allow multiple connections? </param>
/// <param name="typeConstraint">Constrains which input connections can be made from this port </param> /// <param name="typeConstraint">Constrains which input connections can be made from this port </param>
/// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param> /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) { public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
this.backingValue = backingValue; this.backingValue = backingValue;
this.connectionType = connectionType; this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList; this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint; this.typeConstraint = typeConstraint;
} }
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary> /// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param> /// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
/// <param name="connectionType">Should we allow multiple connections? </param> /// <param name="connectionType">Should we allow multiple connections? </param>
/// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param> /// <param name="dynamicPortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
[Obsolete("Use constructor with TypeConstraint")] [Obsolete("Use constructor with TypeConstraint")]
public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { } public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList) { }
} }
/// <summary> Manually supply node class with a context menu path </summary> /// <summary> Manually supply node class with a context menu path </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CreateNodeMenuAttribute : Attribute { public class CreateNodeMenuAttribute : Attribute {
public string menuName; public string menuName;
public int order; public int order;
/// <summary> Manually supply node class with a context menu path </summary> /// <summary> Manually supply node class with a context menu path </summary>
/// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param> /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
public CreateNodeMenuAttribute(string menuName) { public CreateNodeMenuAttribute(string menuName) {
this.menuName = menuName; this.menuName = menuName;
this.order = 0; this.order = 0;
} }
/// <summary> Manually supply node class with a context menu path </summary> /// <summary> Manually supply node class with a context menu path </summary>
/// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param> /// <param name="menuName"> Path to this node in the context menu. Null or empty hides it. </param>
/// <param name="order"> The order by which the menu items are displayed. </param> /// <param name="order"> The order by which the menu items are displayed. </param>
public CreateNodeMenuAttribute(string menuName, int order) { public CreateNodeMenuAttribute(string menuName, int order) {
this.menuName = menuName; this.menuName = menuName;
this.order = order; this.order = order;
} }
} }
/// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary> /// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DisallowMultipleNodesAttribute : Attribute { public class DisallowMultipleNodesAttribute : Attribute {
// TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node // TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node
// while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both. // while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both.
public int max; public int max;
/// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary> /// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
/// <param name="max"> How many nodes to allow. Defaults to 1. </param> /// <param name="max"> How many nodes to allow. Defaults to 1. </param>
public DisallowMultipleNodesAttribute(int max = 1) { public DisallowMultipleNodesAttribute(int max = 1) {
this.max = max; this.max = max;
} }
} }
/// <summary> Specify a color for this node type </summary> /// <summary> Specify a color for this node type </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeTintAttribute : Attribute { public class NodeTintAttribute : Attribute {
public Color color; public Color color;
/// <summary> Specify a color for this node type </summary> /// <summary> Specify a color for this node type </summary>
/// <param name="r"> Red [0.0f .. 1.0f] </param> /// <param name="r"> Red [0.0f .. 1.0f] </param>
/// <param name="g"> Green [0.0f .. 1.0f] </param> /// <param name="g"> Green [0.0f .. 1.0f] </param>
/// <param name="b"> Blue [0.0f .. 1.0f] </param> /// <param name="b"> Blue [0.0f .. 1.0f] </param>
public NodeTintAttribute(float r, float g, float b) { public NodeTintAttribute(float r, float g, float b) {
color = new Color(r, g, b); color = new Color(r, g, b);
} }
/// <summary> Specify a color for this node type </summary> /// <summary> Specify a color for this node type </summary>
/// <param name="hex"> HEX color value </param> /// <param name="hex"> HEX color value </param>
public NodeTintAttribute(string hex) { public NodeTintAttribute(string hex) {
ColorUtility.TryParseHtmlString(hex, out color); ColorUtility.TryParseHtmlString(hex, out color);
} }
/// <summary> Specify a color for this node type </summary> /// <summary> Specify a color for this node type </summary>
/// <param name="r"> Red [0 .. 255] </param> /// <param name="r"> Red [0 .. 255] </param>
/// <param name="g"> Green [0 .. 255] </param> /// <param name="g"> Green [0 .. 255] </param>
/// <param name="b"> Blue [0 .. 255] </param> /// <param name="b"> Blue [0 .. 255] </param>
public NodeTintAttribute(byte r, byte g, byte b) { public NodeTintAttribute(byte r, byte g, byte b) {
color = new Color32(r, g, b, byte.MaxValue); color = new Color32(r, g, b, byte.MaxValue);
} }
} }
/// <summary> Specify a width for this node type </summary> /// <summary> Specify a width for this node type </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeWidthAttribute : Attribute { public class NodeWidthAttribute : Attribute {
public int width; public int width;
/// <summary> Specify a width for this node type </summary> /// <summary> Specify a width for this node type </summary>
/// <param name="width"> Width </param> /// <param name="width"> Width </param>
public NodeWidthAttribute(int width) { public NodeWidthAttribute(int width) {
this.width = width; this.width = width;
} }
} }
#endregion #endregion
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver { [Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
[SerializeField] private List<string> keys = new List<string>(); [SerializeField] private List<string> keys = new List<string>();
[SerializeField] private List<NodePort> values = new List<NodePort>(); [SerializeField] private List<NodePort> values = new List<NodePort>();
public void OnBeforeSerialize() { public void OnBeforeSerialize() {
keys.Clear(); keys.Clear();
values.Clear(); values.Clear();
keys.Capacity = this.Count; keys.Capacity = this.Count;
values.Capacity = this.Count; values.Capacity = this.Count;
foreach (KeyValuePair<string, NodePort> pair in this) { foreach (KeyValuePair<string, NodePort> pair in this) {
keys.Add(pair.Key); keys.Add(pair.Key);
values.Add(pair.Value); values.Add(pair.Value);
} }
} }
public void OnAfterDeserialize() { public void OnAfterDeserialize() {
this.Clear(); this.Clear();
#if UNITY_2021_3_OR_NEWER #if UNITY_2021_3_OR_NEWER
this.EnsureCapacity(keys.Count); this.EnsureCapacity(keys.Count);
#endif #endif
if (keys.Count != values.Count) if (keys.Count != values.Count)
throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable."); throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
for (int i = 0; i < keys.Count; i++) for (int i = 0; i < keys.Count; i++)
this.Add(keys[i], values[i]); this.Add(keys[i], values[i]);
} }
} }
} }
} }

View File

@ -1,124 +1,124 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
namespace XNode { namespace XNode {
/// <summary> Base class for all node graphs </summary> /// <summary> Base class for all node graphs </summary>
[Serializable] [Serializable]
public abstract class NodeGraph : ScriptableObject { public abstract class NodeGraph : ScriptableObject {
/// <summary> All nodes in the graph. <para/> /// <summary> All nodes in the graph. <para/>
/// See: <see cref="AddNode{T}"/> </summary> /// See: <see cref="AddNode{T}"/> </summary>
[SerializeField] public List<Node> nodes = new List<Node>(); [SerializeField] public List<Node> nodes = new List<Node>();
/// <summary> Add a node to the graph by type (convenience method - will call the System.Type version) </summary> /// <summary> Add a node to the graph by type (convenience method - will call the System.Type version) </summary>
public T AddNode<T>() where T : Node { public T AddNode<T>() where T : Node {
return AddNode(typeof(T)) as T; return AddNode(typeof(T)) as T;
} }
/// <summary> Add a node to the graph by type </summary> /// <summary> Add a node to the graph by type </summary>
public virtual Node AddNode(Type type) { public virtual Node AddNode(Type type) {
Node.graphHotfix = this; Node.graphHotfix = this;
Node node = ScriptableObject.CreateInstance(type) as Node; Node node = ScriptableObject.CreateInstance(type) as Node;
node.graph = this; node.graph = this;
nodes.Add(node); nodes.Add(node);
return node; return node;
} }
/// <summary> Creates a copy of the original node in the graph </summary> /// <summary> Creates a copy of the original node in the graph </summary>
public virtual Node CopyNode(Node original) { public virtual Node CopyNode(Node original) {
Node.graphHotfix = this; Node.graphHotfix = this;
Node node = ScriptableObject.Instantiate(original); Node node = ScriptableObject.Instantiate(original);
node.graph = this; node.graph = this;
node.ClearConnections(); node.ClearConnections();
nodes.Add(node); nodes.Add(node);
return node; return node;
} }
/// <summary> Safely remove a node and all its connections </summary> /// <summary> Safely remove a node and all its connections </summary>
/// <param name="node"> The node to remove </param> /// <param name="node"> The node to remove </param>
public virtual void RemoveNode(Node node) { public virtual void RemoveNode(Node node) {
node.ClearConnections(); node.ClearConnections();
nodes.Remove(node); nodes.Remove(node);
if (Application.isPlaying) Destroy(node); if (Application.isPlaying) Destroy(node);
} }
/// <summary> Remove all nodes and connections from the graph </summary> /// <summary> Remove all nodes and connections from the graph </summary>
public virtual void Clear() { public virtual void Clear() {
if (Application.isPlaying) { if (Application.isPlaying) {
for (int i = 0; i < nodes.Count; i++) { for (int i = 0; i < nodes.Count; i++) {
if (nodes[i] != null) Destroy(nodes[i]); if (nodes[i] != null) Destroy(nodes[i]);
} }
} }
nodes.Clear(); nodes.Clear();
} }
/// <summary> Create a new deep copy of this graph </summary> /// <summary> Create a new deep copy of this graph </summary>
public virtual XNode.NodeGraph Copy() { public virtual XNode.NodeGraph Copy() {
// Instantiate a new nodegraph instance // Instantiate a new nodegraph instance
NodeGraph graph = Instantiate(this); NodeGraph graph = Instantiate(this);
// Instantiate all nodes inside the graph // Instantiate all nodes inside the graph
for (int i = 0; i < nodes.Count; i++) { for (int i = 0; i < nodes.Count; i++) {
if (nodes[i] == null) continue; if (nodes[i] == null) continue;
Node.graphHotfix = graph; Node.graphHotfix = graph;
Node node = Instantiate(nodes[i]) as Node; Node node = Instantiate(nodes[i]) as Node;
node.graph = graph; node.graph = graph;
graph.nodes[i] = node; graph.nodes[i] = node;
} }
// Redirect all connections // Redirect all connections
for (int i = 0; i < graph.nodes.Count; i++) { for (int i = 0; i < graph.nodes.Count; i++) {
if (graph.nodes[i] == null) continue; if (graph.nodes[i] == null) continue;
foreach (NodePort port in graph.nodes[i].Ports) { foreach (NodePort port in graph.nodes[i].Ports) {
port.Redirect(nodes, graph.nodes); port.Redirect(nodes, graph.nodes);
} }
} }
return graph; return graph;
} }
protected virtual void OnDestroy() { protected virtual void OnDestroy() {
// Remove all nodes prior to graph destruction // Remove all nodes prior to graph destruction
Clear(); Clear();
} }
#region Attributes #region Attributes
/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted. </summary> /// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted. </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireNodeAttribute : Attribute { public class RequireNodeAttribute : Attribute {
public Type type0; public Type type0;
public Type type1; public Type type1;
public Type type2; public Type type2;
/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary> /// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary>
public RequireNodeAttribute(Type type) { public RequireNodeAttribute(Type type) {
this.type0 = type; this.type0 = type;
this.type1 = null; this.type1 = null;
this.type2 = null; this.type2 = null;
} }
/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary> /// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary>
public RequireNodeAttribute(Type type, Type type2) { public RequireNodeAttribute(Type type, Type type2) {
this.type0 = type; this.type0 = type;
this.type1 = type2; this.type1 = type2;
this.type2 = null; this.type2 = null;
} }
/// <summary> Automatically ensures the existance of a certain node type, and prevents it from being deleted </summary> /// <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) { public RequireNodeAttribute(Type type, Type type2, Type type3) {
this.type0 = type; this.type0 = type;
this.type1 = type2; this.type1 = type2;
this.type2 = type3; this.type2 = type3;
} }
public bool Requires(Type type) { public bool Requires(Type type) {
if (type == null) return false; if (type == null) return false;
if (type == type0) return true; if (type == type0) return true;
else if (type == type1) return true; else if (type == type1) return true;
else if (type == type2) return true; else if (type == type2) return true;
return false; return false;
} }
} }
#endregion #endregion
} }
} }

View File

@ -1,418 +1,418 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
namespace XNode { namespace XNode {
[Serializable] [Serializable]
public class NodePort { public class NodePort {
public enum IO { Input, Output } public enum IO { Input, Output }
public int ConnectionCount { get { return connections.Count; } } public int ConnectionCount { get { return connections.Count; } }
/// <summary> Return the first non-null connection </summary> /// <summary> Return the first non-null connection </summary>
public NodePort Connection { public NodePort Connection {
get { get {
for (int i = 0; i < connections.Count; i++) { for (int i = 0; i < connections.Count; i++) {
if (connections[i] != null) return connections[i].Port; if (connections[i] != null) return connections[i].Port;
} }
return null; return null;
} }
} }
public IO direction { public IO direction {
get { return _direction; } get { return _direction; }
internal set { _direction = value; } internal set { _direction = value; }
} }
public Node.ConnectionType connectionType { public Node.ConnectionType connectionType {
get { return _connectionType; } get { return _connectionType; }
internal set { _connectionType = value; } internal set { _connectionType = value; }
} }
public Node.TypeConstraint typeConstraint { public Node.TypeConstraint typeConstraint {
get { return _typeConstraint; } get { return _typeConstraint; }
internal set { _typeConstraint = value; } internal set { _typeConstraint = value; }
} }
/// <summary> Is this port connected to anytihng? </summary> /// <summary> Is this port connected to anytihng? </summary>
public bool IsConnected { get { return connections.Count != 0; } } public bool IsConnected { get { return connections.Count != 0; } }
public bool IsInput { get { return direction == IO.Input; } } public bool IsInput { get { return direction == IO.Input; } }
public bool IsOutput { get { return direction == IO.Output; } } public bool IsOutput { get { return direction == IO.Output; } }
public string fieldName { get { return _fieldName; } } public string fieldName { get { return _fieldName; } }
public Node node { get { return _node; } } public Node node { get { return _node; } }
public bool IsDynamic { get { return _dynamic; } } public bool IsDynamic { get { return _dynamic; } }
public bool IsStatic { get { return !_dynamic; } } public bool IsStatic { get { return !_dynamic; } }
public Type ValueType { public Type ValueType {
get { get {
if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false);
return valueType; return valueType;
} }
set { set {
if (valueType == value) return; if (valueType == value) return;
valueType = value; valueType = value;
if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value); if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value);
} }
} }
private Type valueType; private Type valueType;
[SerializeField] private string _fieldName; [SerializeField] private string _fieldName;
[SerializeField] private Node _node; [SerializeField] private Node _node;
[SerializeField] private string _typeQualifiedName; [SerializeField] private string _typeQualifiedName;
[SerializeField] private List<PortConnection> connections = new List<PortConnection>(); [SerializeField] private List<PortConnection> connections = new List<PortConnection>();
[SerializeField] private IO _direction; [SerializeField] private IO _direction;
[SerializeField] private Node.ConnectionType _connectionType; [SerializeField] private Node.ConnectionType _connectionType;
[SerializeField] private Node.TypeConstraint _typeConstraint; [SerializeField] private Node.TypeConstraint _typeConstraint;
[SerializeField] private bool _dynamic; [SerializeField] private bool _dynamic;
/// <summary> Construct a static targetless nodeport. Used as a template. </summary> /// <summary> Construct a static targetless nodeport. Used as a template. </summary>
public NodePort(FieldInfo fieldInfo) { public NodePort(FieldInfo fieldInfo) {
_fieldName = fieldInfo.Name; _fieldName = fieldInfo.Name;
ValueType = fieldInfo.FieldType; ValueType = fieldInfo.FieldType;
_dynamic = false; _dynamic = false;
var attribs = fieldInfo.GetCustomAttributes(false); var attribs = fieldInfo.GetCustomAttributes(false);
for (int i = 0; i < attribs.Length; i++) { for (int i = 0; i < attribs.Length; i++) {
if (attribs[i] is Node.InputAttribute) { if (attribs[i] is Node.InputAttribute) {
_direction = IO.Input; _direction = IO.Input;
_connectionType = (attribs[i] as Node.InputAttribute).connectionType; _connectionType = (attribs[i] as Node.InputAttribute).connectionType;
_typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint; _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint;
} else if (attribs[i] is Node.OutputAttribute) { } else if (attribs[i] is Node.OutputAttribute) {
_direction = IO.Output; _direction = IO.Output;
_connectionType = (attribs[i] as Node.OutputAttribute).connectionType; _connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
_typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint; _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint;
} }
} }
} }
/// <summary> Copy a nodePort but assign it to another node. </summary> /// <summary> Copy a nodePort but assign it to another node. </summary>
public NodePort(NodePort nodePort, Node node) { public NodePort(NodePort nodePort, Node node) {
_fieldName = nodePort._fieldName; _fieldName = nodePort._fieldName;
ValueType = nodePort.valueType; ValueType = nodePort.valueType;
_direction = nodePort.direction; _direction = nodePort.direction;
_dynamic = nodePort._dynamic; _dynamic = nodePort._dynamic;
_connectionType = nodePort._connectionType; _connectionType = nodePort._connectionType;
_typeConstraint = nodePort._typeConstraint; _typeConstraint = nodePort._typeConstraint;
_node = node; _node = node;
} }
/// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary> /// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) { public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) {
_fieldName = fieldName; _fieldName = fieldName;
this.ValueType = type; this.ValueType = type;
_direction = direction; _direction = direction;
_node = node; _node = node;
_dynamic = true; _dynamic = true;
_connectionType = connectionType; _connectionType = connectionType;
_typeConstraint = typeConstraint; _typeConstraint = typeConstraint;
} }
/// <summary> Checks all connections for invalid references, and removes them. </summary> /// <summary> Checks all connections for invalid references, and removes them. </summary>
public void VerifyConnections() { public void VerifyConnections() {
for (int i = connections.Count - 1; i >= 0; i--) { for (int i = connections.Count - 1; i >= 0; i--) {
if (connections[i].node != null && if (connections[i].node != null &&
!string.IsNullOrEmpty(connections[i].fieldName) && !string.IsNullOrEmpty(connections[i].fieldName) &&
connections[i].node.GetPort(connections[i].fieldName) != null) connections[i].node.GetPort(connections[i].fieldName) != null)
continue; continue;
connections.RemoveAt(i); connections.RemoveAt(i);
} }
} }
/// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary> /// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary>
/// <returns> <see cref="Node.GetValue(NodePort)"/> </returns> /// <returns> <see cref="Node.GetValue(NodePort)"/> </returns>
public object GetOutputValue() { public object GetOutputValue() {
if (direction == IO.Input) return null; if (direction == IO.Input) return null;
return node.GetValue(this); return node.GetValue(this);
} }
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid.</summary> /// <summary> Return the output value of the first connected port. Returns null if none found or invalid.</summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public object GetInputValue() { public object GetInputValue() {
NodePort connectedPort = Connection; NodePort connectedPort = Connection;
if (connectedPort == null) return null; if (connectedPort == null) return null;
return connectedPort.GetOutputValue(); return connectedPort.GetOutputValue();
} }
/// <summary> Return the output values of all connected ports. </summary> /// <summary> Return the output values of all connected ports. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public object[] GetInputValues() { public object[] GetInputValues() {
object[] objs = new object[ConnectionCount]; object[] objs = new object[ConnectionCount];
for (int i = 0; i < ConnectionCount; i++) { for (int i = 0; i < ConnectionCount; i++) {
NodePort connectedPort = connections[i].Port; NodePort connectedPort = connections[i].Port;
if (connectedPort == null) { // if we happen to find a null port, remove it and look again if (connectedPort == null) { // if we happen to find a null port, remove it and look again
connections.RemoveAt(i); connections.RemoveAt(i);
i--; i--;
continue; continue;
} }
objs[i] = connectedPort.GetOutputValue(); objs[i] = connectedPort.GetOutputValue();
} }
return objs; return objs;
} }
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary> /// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public T GetInputValue<T>() { public T GetInputValue<T>() {
object obj = GetInputValue(); object obj = GetInputValue();
return obj is T ? (T) obj : default(T); return obj is T ? (T) obj : default(T);
} }
/// <summary> Return the output values of all connected ports. </summary> /// <summary> Return the output values of all connected ports. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public T[] GetInputValues<T>() { public T[] GetInputValues<T>() {
object[] objs = GetInputValues(); object[] objs = GetInputValues();
T[] ts = new T[objs.Length]; T[] ts = new T[objs.Length];
for (int i = 0; i < objs.Length; i++) { for (int i = 0; i < objs.Length; i++) {
if (objs[i] is T) ts[i] = (T) objs[i]; if (objs[i] is T) ts[i] = (T) objs[i];
} }
return ts; return ts;
} }
/// <summary> Return true if port is connected and has a valid input. </summary> /// <summary> Return true if port is connected and has a valid input. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public bool TryGetInputValue<T>(out T value) { public bool TryGetInputValue<T>(out T value) {
object obj = GetInputValue(); object obj = GetInputValue();
if (obj is T) { if (obj is T) {
value = (T) obj; value = (T) obj;
return true; return true;
} else { } else {
value = default(T); value = default(T);
return false; return false;
} }
} }
/// <summary> Return the sum of all inputs. </summary> /// <summary> Return the sum of all inputs. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public float GetInputSum(float fallback) { public float GetInputSum(float fallback) {
object[] objs = GetInputValues(); object[] objs = GetInputValues();
if (objs.Length == 0) return fallback; if (objs.Length == 0) return fallback;
float result = 0; float result = 0;
for (int i = 0; i < objs.Length; i++) { for (int i = 0; i < objs.Length; i++) {
if (objs[i] is float) result += (float) objs[i]; if (objs[i] is float) result += (float) objs[i];
} }
return result; return result;
} }
/// <summary> Return the sum of all inputs. </summary> /// <summary> Return the sum of all inputs. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public int GetInputSum(int fallback) { public int GetInputSum(int fallback) {
object[] objs = GetInputValues(); object[] objs = GetInputValues();
if (objs.Length == 0) return fallback; if (objs.Length == 0) return fallback;
int result = 0; int result = 0;
for (int i = 0; i < objs.Length; i++) { for (int i = 0; i < objs.Length; i++) {
if (objs[i] is int) result += (int) objs[i]; if (objs[i] is int) result += (int) objs[i];
} }
return result; return result;
} }
/// <summary> Connect this <see cref="NodePort"/> to another </summary> /// <summary> Connect this <see cref="NodePort"/> to another </summary>
/// <param name="port">The <see cref="NodePort"/> to connect to</param> /// <param name="port">The <see cref="NodePort"/> to connect to</param>
public void Connect(NodePort port) { public void Connect(NodePort port) {
if (connections == null) connections = new List<PortConnection>(); if (connections == null) connections = new List<PortConnection>();
if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; }
if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; } if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; }
if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; }
if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; } if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; }
#if UNITY_EDITOR #if UNITY_EDITOR
UnityEditor.Undo.RecordObject(node, "Connect Port"); UnityEditor.Undo.RecordObject(node, "Connect Port");
UnityEditor.Undo.RecordObject(port.node, "Connect Port"); UnityEditor.Undo.RecordObject(port.node, "Connect Port");
#endif #endif
if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
connections.Add(new PortConnection(port)); connections.Add(new PortConnection(port));
if (port.connections == null) port.connections = new List<PortConnection>(); if (port.connections == null) port.connections = new List<PortConnection>();
if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this));
node.OnCreateConnection(this, port); node.OnCreateConnection(this, port);
port.node.OnCreateConnection(this, port); port.node.OnCreateConnection(this, port);
} }
public List<NodePort> GetConnections() { public List<NodePort> GetConnections() {
List<NodePort> result = new List<NodePort>(); List<NodePort> result = new List<NodePort>();
for (int i = 0; i < connections.Count; i++) { for (int i = 0; i < connections.Count; i++) {
NodePort port = GetConnection(i); NodePort port = GetConnection(i);
if (port != null) result.Add(port); if (port != null) result.Add(port);
} }
return result; return result;
} }
public NodePort GetConnection(int i) { public NodePort GetConnection(int i) {
//If the connection is broken for some reason, remove it. //If the connection is broken for some reason, remove it.
if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) { if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) {
connections.RemoveAt(i); connections.RemoveAt(i);
return null; return null;
} }
NodePort port = connections[i].node.GetPort(connections[i].fieldName); NodePort port = connections[i].node.GetPort(connections[i].fieldName);
if (port == null) { if (port == null) {
connections.RemoveAt(i); connections.RemoveAt(i);
return null; return null;
} }
return port; return port;
} }
/// <summary> Get index of the connection connecting this and specified ports </summary> /// <summary> Get index of the connection connecting this and specified ports </summary>
public int GetConnectionIndex(NodePort port) { public int GetConnectionIndex(NodePort port) {
for (int i = 0; i < ConnectionCount; i++) { for (int i = 0; i < ConnectionCount; i++) {
if (connections[i].Port == port) return i; if (connections[i].Port == port) return i;
} }
return -1; return -1;
} }
public bool IsConnectedTo(NodePort port) { public bool IsConnectedTo(NodePort port) {
for (int i = 0; i < connections.Count; i++) { for (int i = 0; i < connections.Count; i++) {
if (connections[i].Port == port) return true; if (connections[i].Port == port) return true;
} }
return false; return false;
} }
/// <summary> Returns true if this port can connect to specified port </summary> /// <summary> Returns true if this port can connect to specified port </summary>
public bool CanConnectTo(NodePort port) { public bool CanConnectTo(NodePort port) {
// Figure out which is input and which is output // Figure out which is input and which is output
NodePort input = null, output = null; NodePort input = null, output = null;
if (IsInput) input = this; if (IsInput) input = this;
else output = this; else output = this;
if (port.IsInput) input = port; if (port.IsInput) input = port;
else output = port; else output = port;
// If there isn't one of each, they can't connect // If there isn't one of each, they can't connect
if (input == null || output == null) return false; if (input == null || output == null) return false;
// Check input type constraints // Check input type constraints
if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
// Check output type constraints // Check output type constraints
if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
// Success // Success
return true; return true;
} }
/// <summary> Disconnect this port from another port </summary> /// <summary> Disconnect this port from another port </summary>
public void Disconnect(NodePort port) { public void Disconnect(NodePort port) {
// Remove this ports connection to the other // Remove this ports connection to the other
for (int i = connections.Count - 1; i >= 0; i--) { for (int i = connections.Count - 1; i >= 0; i--) {
if (connections[i].Port == port) { if (connections[i].Port == port) {
connections.RemoveAt(i); connections.RemoveAt(i);
} }
} }
if (port != null) { if (port != null) {
// Remove the other ports connection to this port // Remove the other ports connection to this port
for (int i = 0; i < port.connections.Count; i++) { for (int i = 0; i < port.connections.Count; i++) {
if (port.connections[i].Port == this) { if (port.connections[i].Port == this) {
port.connections.RemoveAt(i); port.connections.RemoveAt(i);
// Trigger OnRemoveConnection from this side port // Trigger OnRemoveConnection from this side port
port.node.OnRemoveConnection(port); port.node.OnRemoveConnection(port);
} }
} }
} }
// Trigger OnRemoveConnection // Trigger OnRemoveConnection
node.OnRemoveConnection(this); node.OnRemoveConnection(this);
} }
/// <summary> Disconnect this port from another port </summary> /// <summary> Disconnect this port from another port </summary>
public void Disconnect(int i) { public void Disconnect(int i) {
// Remove the other ports connection to this port // Remove the other ports connection to this port
NodePort otherPort = connections[i].Port; NodePort otherPort = connections[i].Port;
if (otherPort != null) { if (otherPort != null) {
otherPort.connections.RemoveAll(it => { return it.Port == this; }); otherPort.connections.RemoveAll(it => { return it.Port == this; });
} }
// Remove this ports connection to the other // Remove this ports connection to the other
connections.RemoveAt(i); connections.RemoveAt(i);
// Trigger OnRemoveConnection // Trigger OnRemoveConnection
node.OnRemoveConnection(this); node.OnRemoveConnection(this);
if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort); if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort);
} }
public void ClearConnections() { public void ClearConnections() {
while (connections.Count > 0) { while (connections.Count > 0) {
Disconnect(connections[0].Port); Disconnect(connections[0].Port);
} }
} }
/// <summary> Get reroute points for a given connection. This is used for organization </summary> /// <summary> Get reroute points for a given connection. This is used for organization </summary>
public List<Vector2> GetReroutePoints(int index) { public List<Vector2> GetReroutePoints(int index) {
return connections[index].reroutePoints; return connections[index].reroutePoints;
} }
/// <summary> Swap connections with another node </summary> /// <summary> Swap connections with another node </summary>
public void SwapConnections(NodePort targetPort) { public void SwapConnections(NodePort targetPort) {
int aConnectionCount = connections.Count; int aConnectionCount = connections.Count;
int bConnectionCount = targetPort.connections.Count; int bConnectionCount = targetPort.connections.Count;
List<NodePort> portConnections = new List<NodePort>(); List<NodePort> portConnections = new List<NodePort>();
List<NodePort> targetPortConnections = new List<NodePort>(); List<NodePort> targetPortConnections = new List<NodePort>();
// Cache port connections // Cache port connections
for (int i = 0; i < aConnectionCount; i++) for (int i = 0; i < aConnectionCount; i++)
portConnections.Add(connections[i].Port); portConnections.Add(connections[i].Port);
// Cache target port connections // Cache target port connections
for (int i = 0; i < bConnectionCount; i++) for (int i = 0; i < bConnectionCount; i++)
targetPortConnections.Add(targetPort.connections[i].Port); targetPortConnections.Add(targetPort.connections[i].Port);
ClearConnections(); ClearConnections();
targetPort.ClearConnections(); targetPort.ClearConnections();
// Add port connections to targetPort // Add port connections to targetPort
for (int i = 0; i < portConnections.Count; i++) for (int i = 0; i < portConnections.Count; i++)
targetPort.Connect(portConnections[i]); targetPort.Connect(portConnections[i]);
// Add target port connections to this one // Add target port connections to this one
for (int i = 0; i < targetPortConnections.Count; i++) for (int i = 0; i < targetPortConnections.Count; i++)
Connect(targetPortConnections[i]); Connect(targetPortConnections[i]);
} }
/// <summary> Copy all connections pointing to a node and add them to this one </summary> /// <summary> Copy all connections pointing to a node and add them to this one </summary>
public void AddConnections(NodePort targetPort) { public void AddConnections(NodePort targetPort) {
int connectionCount = targetPort.ConnectionCount; int connectionCount = targetPort.ConnectionCount;
for (int i = 0; i < connectionCount; i++) { for (int i = 0; i < connectionCount; i++) {
PortConnection connection = targetPort.connections[i]; PortConnection connection = targetPort.connections[i];
NodePort otherPort = connection.Port; NodePort otherPort = connection.Port;
Connect(otherPort); Connect(otherPort);
} }
} }
/// <summary> Move all connections pointing to this node, to another node </summary> /// <summary> Move all connections pointing to this node, to another node </summary>
public void MoveConnections(NodePort targetPort) { public void MoveConnections(NodePort targetPort) {
int connectionCount = connections.Count; int connectionCount = connections.Count;
// Add connections to target port // Add connections to target port
for (int i = 0; i < connectionCount; i++) { for (int i = 0; i < connectionCount; i++) {
PortConnection connection = targetPort.connections[i]; PortConnection connection = targetPort.connections[i];
NodePort otherPort = connection.Port; NodePort otherPort = connection.Port;
Connect(otherPort); Connect(otherPort);
} }
ClearConnections(); ClearConnections();
} }
/// <summary> Swap connected nodes from the old list with nodes from the new list </summary> /// <summary> Swap connected nodes from the old list with nodes from the new list </summary>
public void Redirect(List<Node> oldNodes, List<Node> newNodes) { public void Redirect(List<Node> oldNodes, List<Node> newNodes) {
foreach (PortConnection connection in connections) { foreach (PortConnection connection in connections) {
int index = oldNodes.IndexOf(connection.node); int index = oldNodes.IndexOf(connection.node);
if (index >= 0) connection.node = newNodes[index]; if (index >= 0) connection.node = newNodes[index];
} }
} }
[Serializable] [Serializable]
private class PortConnection { private class PortConnection {
[SerializeField] public string fieldName; [SerializeField] public string fieldName;
[SerializeField] public Node node; [SerializeField] public Node node;
public NodePort Port { get { return port != null ? port : port = GetPort(); } } public NodePort Port { get { return port != null ? port : port = GetPort(); } }
[NonSerialized] private NodePort port; [NonSerialized] private NodePort port;
/// <summary> Extra connection path points for organization </summary> /// <summary> Extra connection path points for organization </summary>
[SerializeField] public List<Vector2> reroutePoints = new List<Vector2>(); [SerializeField] public List<Vector2> reroutePoints = new List<Vector2>();
public PortConnection(NodePort port) { public PortConnection(NodePort port) {
this.port = port; this.port = port;
node = port.node; node = port.node;
fieldName = port.fieldName; fieldName = port.fieldName;
} }
/// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary> /// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary>
private NodePort GetPort() { private NodePort GetPort() {
if (node == null || string.IsNullOrEmpty(fieldName)) return null; if (node == null || string.IsNullOrEmpty(fieldName)) return null;
return node.GetPort(fieldName); return node.GetPort(fieldName);
} }
} }
} }
} }