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

Delete xNode-master directory

This commit is contained in:
MowfaqAlarbi 2021-07-10 09:26:19 +02:00 committed by GitHub
parent 3b879ed725
commit 358b152545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 0 additions and 6687 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,21 +0,0 @@
MIT License
Copyright (c) 2017 Thor Brigsted
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

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

View File

@ -1,119 +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)
### 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 +0,0 @@
fileFormatVersion: 2
guid: 657b15cb3ec32a24ca80faebf094d0f4
folderAsset: yes
timeCreated: 1505418321
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 5644dfc7eed151045af664a9d4fd1906
folderAsset: yes
timeCreated: 1541633926
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +0,0 @@
using UnityEngine;
/// <summary> Draw enums correctly within nodes. Without it, enums show up at the wrong positions. </summary>
/// <remarks> Enums with this attribute are not detected by EditorGui.ChangeCheck due to waiting before executing </remarks>
public class NodeEnumAttribute : PropertyAttribute { }

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 10a8338f6c985854697b35459181af0a
timeCreated: 1541633942
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 7adf21edfb51f514fa991d7556ecd0ef
folderAsset: yes
timeCreated: 1541971984
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,71 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using XNode;
using XNodeEditor;
namespace XNodeEditor {
[CustomPropertyDrawer(typeof(NodeEnumAttribute))]
public class NodeEnumDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
EnumPopup(position, property, label);
EditorGUI.EndProperty();
}
public static void EnumPopup(Rect position, SerializedProperty property, GUIContent label) {
// Throw error on wrong type
if (property.propertyType != SerializedPropertyType.Enum) {
throw new ArgumentException("Parameter selected must be of type System.Enum");
}
// Add label
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
// Get current enum name
string enumName = "";
if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex];
#if UNITY_2017_1_OR_NEWER
// Display dropdown
if (EditorGUI.DropdownButton(position, new GUIContent(enumName), FocusType.Passive)) {
// Position is all wrong if we show the dropdown during the node draw phase.
// Instead, add it to onLateGUI to display it later.
NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
}
#else
// Display dropdown
if (GUI.Button(position, new GUIContent(enumName), "MiniPopup")) {
// Position is all wrong if we show the dropdown during the node draw phase.
// Instead, add it to onLateGUI to display it later.
NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
}
#endif
}
public static void ShowContextMenuAtMouse(SerializedProperty property) {
// Initialize menu
GenericMenu menu = new GenericMenu();
// Add all enum display names to menu
for (int i = 0; i < property.enumDisplayNames.Length; i++) {
int index = i;
menu.AddItem(new GUIContent(property.enumDisplayNames[i]), false, () => SetEnum(property, index));
}
// Display at cursor position
Rect r = new Rect(Event.current.mousePosition, new Vector2(0, 0));
menu.DropDown(r);
}
private static void SetEnum(SerializedProperty property, int index) {
property.enumValueIndex = index;
property.serializedObject.ApplyModifiedProperties();
property.serializedObject.Update();
}
}
}

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 83db81f92abadca439507e25d517cabe
timeCreated: 1541633798
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 327994a52f523b641898a39ff7500a02
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,48 +0,0 @@
#if UNITY_EDITOR && ODIN_INSPECTOR
using System;
using System.Collections.Generic;
using System.Reflection;
using Sirenix.OdinInspector.Editor;
using UnityEngine;
using XNode;
namespace XNodeEditor {
internal class OdinNodeInGraphAttributeProcessor<T> : OdinAttributeProcessor<T> where T : Node {
public override bool CanProcessSelfAttributes(InspectorProperty property) {
return false;
}
public override bool CanProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member) {
if (!NodeEditor.inNodeEditor)
return false;
if (member.MemberType == MemberTypes.Field) {
switch (member.Name) {
case "graph":
case "position":
case "ports":
return true;
default:
break;
}
}
return false;
}
public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List<Attribute> attributes) {
switch (member.Name) {
case "graph":
case "position":
case "ports":
attributes.Add(new HideInInspector());
break;
default:
break;
}
}
}
}
#endif

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 3cf2561fbfea9a041ac81efbbb5b3e0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,49 +0,0 @@
#if UNITY_EDITOR && ODIN_INSPECTOR
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities.Editor;
using UnityEngine;
using XNode;
namespace XNodeEditor {
public class InputAttributeDrawer : OdinAttributeDrawer<XNode.Node.InputAttribute> {
protected override bool CanDrawAttributeProperty(InspectorProperty property) {
Node node = property.Tree.WeakTargets[0] as Node;
return node != null;
}
protected override void DrawPropertyLayout(GUIContent label) {
Node node = Property.Tree.WeakTargets[0] as Node;
NodePort port = node.GetInputPort(Property.Name);
if (!NodeEditor.inNodeEditor) {
if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected)
CallNextDrawer(label);
return;
}
if (Property.Tree.WeakTargets.Count > 1) {
SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected");
return;
}
if (port != null) {
var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath);
if (portPropoerty == null) {
SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath);
return;
} else {
var labelWidth = Property.GetAttribute<LabelWidthAttribute>();
if (labelWidth != null)
GUIHelper.PushLabelWidth(labelWidth.Width);
NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30));
if (labelWidth != null)
GUIHelper.PopLabelWidth();
}
}
}
}
}
#endif

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2fd590b2e9ea0bd49b6986a2ca9010ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,49 +0,0 @@
#if UNITY_EDITOR && ODIN_INSPECTOR
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities.Editor;
using UnityEngine;
using XNode;
namespace XNodeEditor {
public class OutputAttributeDrawer : OdinAttributeDrawer<XNode.Node.OutputAttribute> {
protected override bool CanDrawAttributeProperty(InspectorProperty property) {
Node node = property.Tree.WeakTargets[0] as Node;
return node != null;
}
protected override void DrawPropertyLayout(GUIContent label) {
Node node = Property.Tree.WeakTargets[0] as Node;
NodePort port = node.GetOutputPort(Property.Name);
if (!NodeEditor.inNodeEditor) {
if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected)
CallNextDrawer(label);
return;
}
if (Property.Tree.WeakTargets.Count > 1) {
SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected");
return;
}
if (port != null) {
var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath);
if (portPropoerty == null) {
SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath);
return;
} else {
var labelWidth = Property.GetAttribute<LabelWidthAttribute>();
if (labelWidth != null)
GUIHelper.PushLabelWidth(labelWidth.Width);
NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30));
if (labelWidth != null)
GUIHelper.PopLabelWidth();
}
}
}
}
}
#endif

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: e7ebd8f2b42e2384aa109551dc46af88
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,75 +0,0 @@
using UnityEditor;
using UnityEngine;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
#endif
namespace XNodeEditor {
/// <summary> Override graph inspector to show an 'Open Graph' button at the top </summary>
[CustomEditor(typeof(XNode.NodeGraph), true)]
#if ODIN_INSPECTOR
public class GlobalGraphEditor : OdinEditor {
public override void OnInspectorGUI() {
if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph);
}
base.OnInspectorGUI();
}
}
#else
[CanEditMultipleObjects]
public class GlobalGraphEditor : Editor {
public override void OnInspectorGUI() {
serializedObject.Update();
if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph);
}
GUILayout.Space(EditorGUIUtility.singleLineHeight);
GUILayout.Label("Raw data", "BoldLabel");
DrawDefaultInspector();
serializedObject.ApplyModifiedProperties();
}
}
#endif
[CustomEditor(typeof(XNode.Node), true)]
#if ODIN_INSPECTOR
public class GlobalNodeEditor : OdinEditor {
public override void OnInspectorGUI() {
if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
SerializedProperty graphProp = serializedObject.FindProperty("graph");
NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph);
w.Home(); // Focus selected node
}
base.OnInspectorGUI();
}
}
#else
[CanEditMultipleObjects]
public class GlobalNodeEditor : Editor {
public override void OnInspectorGUI() {
serializedObject.Update();
if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
SerializedProperty graphProp = serializedObject.FindProperty("graph");
NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph);
w.Home(); // Focus selected node
}
GUILayout.Space(EditorGUIUtility.singleLineHeight);
GUILayout.Label("Raw data", "BoldLabel");
// Now draw the node itself.
DrawDefaultInspector();
serializedObject.ApplyModifiedProperties();
}
}
#endif
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: bdd6e443125ccac4dad0665515759637
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,35 +0,0 @@
using UnityEditor;
using XNode;
namespace XNodeEditor {
/// <summary>
/// This asset processor resolves an issue with the new v2 AssetDatabase system present on 2019.3 and later. When
/// renaming a <see cref="XNode.NodeGraph"/> asset, it appears that sometimes the v2 AssetDatabase will swap which asset
/// is the main asset (present at top level) between the <see cref="XNode.NodeGraph"/> and one of its <see cref="XNode.Node"/>
/// sub-assets. As a workaround until Unity fixes this, this asset processor checks all renamed assets and if it
/// finds a case where a <see cref="XNode.Node"/> has been made the main asset it will swap it back to being a sub-asset
/// and rename the node to the default name for that node type.
/// </summary>
internal sealed class GraphRenameFixAssetProcessor : AssetPostprocessor {
private static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths) {
for (int i = 0; i < movedAssets.Length; i++) {
Node nodeAsset = AssetDatabase.LoadMainAssetAtPath(movedAssets[i]) as Node;
// If the renamed asset is a node graph, but the v2 AssetDatabase has swapped a sub-asset node to be its
// main asset, reset the node graph to be the main asset and rename the node asset back to its default
// name.
if (nodeAsset != null && AssetDatabase.IsMainAsset(nodeAsset)) {
AssetDatabase.SetMainObject(nodeAsset.graph, movedAssets[i]);
AssetDatabase.ImportAsset(movedAssets[i]);
nodeAsset.name = NodeEditorUtilities.NodeDefaultName(nodeAsset.GetType());
EditorUtility.SetDirty(nodeAsset);
}
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 65da1ff1c50a9984a9c95fd18799e8dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a6a1bbc054e282346a02e7bbddde3206
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,20 +0,0 @@
using UnityEngine;
namespace XNodeEditor.Internal {
public struct RerouteReference {
public XNode.NodePort port;
public int connectionIndex;
public int pointIndex;
public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) {
this.port = port;
this.connectionIndex = connectionIndex;
this.pointIndex = pointIndex;
}
public void InsertPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex).Insert(pointIndex, pos); }
public void SetPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex) [pointIndex] = pos; }
public void RemovePoint() { port.GetReroutePoints(connectionIndex).RemoveAt(pointIndex); }
public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; }
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 399f3c5fb717b2c458c3e9746f8959a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@ -1,566 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using XNodeEditor.Internal;
namespace XNodeEditor {
public partial class NodeEditorWindow {
public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid }
public static NodeActivity currentActivity = NodeActivity.Idle;
public static bool isPanning { get; private set; }
public static Vector2[] dragOffset;
public static XNode.Node[] copyBuffer = null;
public bool IsDraggingPort { get { return draggedOutput != null; } }
public bool IsHoveringPort { get { return hoveredPort != null; } }
public bool IsHoveringNode { get { return hoveredNode != null; } }
public bool IsHoveringReroute { get { return hoveredReroute.port != null; } }
/// <summary> Return the dragged port or null if not exist </summary>
public XNode.NodePort DraggedOutputPort { get { XNode.NodePort result = draggedOutput; return result; } }
/// <summary> Return the Hovered port or null if not exist </summary>
public XNode.NodePort HoveredPort { get { XNode.NodePort result = hoveredPort; return result; } }
/// <summary> Return the Hovered node or null if not exist </summary>
public XNode.Node HoveredNode { get { XNode.Node result = hoveredNode; return result; } }
private XNode.Node hoveredNode = null;
[NonSerialized] public XNode.NodePort hoveredPort = null;
[NonSerialized] private XNode.NodePort draggedOutput = null;
[NonSerialized] private XNode.NodePort draggedOutputTarget = null;
[NonSerialized] private XNode.NodePort autoConnectOutput = null;
[NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
private RerouteReference hoveredReroute = new RerouteReference();
public List<RerouteReference> selectedReroutes = new List<RerouteReference>();
private Vector2 dragBoxStart;
private UnityEngine.Object[] preBoxSelection;
private RerouteReference[] preBoxSelectionReroute;
private Rect selectionBox;
private bool isDoubleClick = false;
private Vector2 lastMousePosition;
private float dragThreshold = 1f;
public void Controls() {
wantsMouseMove = true;
Event e = Event.current;
switch (e.type) {
case EventType.DragUpdated:
case EventType.DragPerform:
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
if (e.type == EventType.DragPerform) {
DragAndDrop.AcceptDrag();
graphEditor.OnDropObjects(DragAndDrop.objectReferences);
}
break;
case EventType.MouseMove:
//Keyboard commands will not get correct mouse position from Event
lastMousePosition = e.mousePosition;
break;
case EventType.ScrollWheel:
float oldZoom = zoom;
if (e.delta.y > 0) zoom += 0.1f * zoom;
else zoom -= 0.1f * zoom;
if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);
break;
case EventType.MouseDrag:
if (e.button == 0) {
if (IsDraggingPort) {
// Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously
if (IsHoveringPort && hoveredPort.IsInput && !draggedOutput.IsConnectedTo(hoveredPort)) {
draggedOutputTarget = hoveredPort;
} else {
draggedOutputTarget = null;
}
Repaint();
} else if (currentActivity == NodeActivity.HoldNode) {
RecalculateDragOffsets(e);
currentActivity = NodeActivity.DragNode;
Repaint();
}
if (currentActivity == NodeActivity.DragNode) {
// Holding ctrl inverts grid snap
bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
if (e.control) gridSnap = !gridSnap;
Vector2 mousePos = WindowToGridPosition(e.mousePosition);
// Move selected nodes with offset
for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) {
XNode.Node node = Selection.objects[i] as XNode.Node;
Undo.RecordObject(node, "Moved Node");
Vector2 initial = node.position;
node.position = mousePos + dragOffset[i];
if (gridSnap) {
node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
}
// Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.
Vector2 offset = node.position - initial;
if (offset.sqrMagnitude > 0) {
foreach (XNode.NodePort output in node.Outputs) {
Rect rect;
if (portConnectionPoints.TryGetValue(output, out rect)) {
rect.position += offset;
portConnectionPoints[output] = rect;
}
}
foreach (XNode.NodePort input in node.Inputs) {
Rect rect;
if (portConnectionPoints.TryGetValue(input, out rect)) {
rect.position += offset;
portConnectionPoints[input] = rect;
}
}
}
}
}
// Move selected reroutes with offset
for (int i = 0; i < selectedReroutes.Count; i++) {
Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i];
if (gridSnap) {
pos.x = (Mathf.Round(pos.x / 16) * 16);
pos.y = (Mathf.Round(pos.y / 16) * 16);
}
selectedReroutes[i].SetPoint(pos);
}
Repaint();
} else if (currentActivity == NodeActivity.HoldGrid) {
currentActivity = NodeActivity.DragGrid;
preBoxSelection = Selection.objects;
preBoxSelectionReroute = selectedReroutes.ToArray();
dragBoxStart = WindowToGridPosition(e.mousePosition);
Repaint();
} else if (currentActivity == NodeActivity.DragGrid) {
Vector2 boxStartPos = GridToWindowPosition(dragBoxStart);
Vector2 boxSize = e.mousePosition - boxStartPos;
if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
selectionBox = new Rect(boxStartPos, boxSize);
Repaint();
}
} else if (e.button == 1 || e.button == 2) {
//check drag threshold for larger screens
if (e.delta.magnitude > dragThreshold) {
panOffset += e.delta * zoom;
isPanning = true;
}
}
break;
case EventType.MouseDown:
Repaint();
if (e.button == 0) {
draggedOutputReroutes.Clear();
if (IsHoveringPort) {
if (hoveredPort.IsOutput) {
draggedOutput = hoveredPort;
autoConnectOutput = hoveredPort;
} else {
hoveredPort.VerifyConnections();
autoConnectOutput = null;
if (hoveredPort.IsConnected) {
XNode.Node node = hoveredPort.node;
XNode.NodePort output = hoveredPort.Connection;
int outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex);
hoveredPort.Disconnect(output);
draggedOutput = output;
draggedOutputTarget = hoveredPort;
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
}
}
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
// If mousedown on node header, select or deselect
if (!Selection.Contains(hoveredNode)) {
SelectNode(hoveredNode, e.control || e.shift);
if (!e.control && !e.shift) selectedReroutes.Clear();
} else if (e.control || e.shift) DeselectNode(hoveredNode);
// Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
isDoubleClick = (e.clickCount == 2);
e.Use();
currentActivity = NodeActivity.HoldNode;
} else if (IsHoveringReroute) {
// If reroute isn't selected
if (!selectedReroutes.Contains(hoveredReroute)) {
// Add it
if (e.control || e.shift) selectedReroutes.Add(hoveredReroute);
// Select it
else {
selectedReroutes = new List<RerouteReference>() { hoveredReroute };
Selection.activeObject = null;
}
}
// Deselect
else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute);
e.Use();
currentActivity = NodeActivity.HoldNode;
}
// If mousedown on grid background, deselect all
else if (!IsHoveringNode) {
currentActivity = NodeActivity.HoldGrid;
if (!e.control && !e.shift) {
selectedReroutes.Clear();
Selection.activeObject = null;
}
}
}
break;
case EventType.MouseUp:
if (e.button == 0) {
//Port drag release
if (IsDraggingPort) {
// If connection is valid, save it
if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget)) {
XNode.Node node = draggedOutputTarget.node;
if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget);
// ConnectionIndex can be -1 if the connection is removed instantly after creation
int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
if (connectionIndex != -1) {
draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
EditorUtility.SetDirty(graph);
}
}
// Open context menu for auto-connection if there is no target node
else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) {
GenericMenu menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu, draggedOutput.ValueType);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
}
//Release dragged connection
draggedOutput = null;
draggedOutputTarget = null;
EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else if (currentActivity == NodeActivity.DragNode) {
IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node);
foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} else if (!IsHoveringNode) {
// If click outside node, release field focus
if (!isPanning) {
EditorGUI.FocusTextInControl(null);
EditorGUIUtility.editingTextField = false;
}
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
// If click node header, select it.
if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) {
selectedReroutes.Clear();
SelectNode(hoveredNode, false);
// Double click to center node
if (isDoubleClick) {
Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero;
panOffset = -hoveredNode.position - nodeDimension;
}
}
// If click reroute, select it.
if (IsHoveringReroute && !(e.control || e.shift)) {
selectedReroutes = new List<RerouteReference>() { hoveredReroute };
Selection.activeObject = null;
}
Repaint();
currentActivity = NodeActivity.Idle;
} else if (e.button == 1 || e.button == 2) {
if (!isPanning) {
if (IsDraggingPort) {
draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition));
} else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) {
selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint());
selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1);
} else if (IsHoveringReroute) {
ShowRerouteContextMenu(hoveredReroute);
} else if (IsHoveringPort) {
ShowPortContextMenu(hoveredPort);
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);
autoConnectOutput = null;
GenericMenu menu = new GenericMenu();
NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places.
} else if (!IsHoveringNode) {
autoConnectOutput = null;
GenericMenu menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
}
}
isPanning = false;
}
// Reset DoubleClick
isDoubleClick = false;
break;
case EventType.KeyDown:
if (EditorGUIUtility.editingTextField || GUIUtility.keyboardControl != 0) break;
else if (e.keyCode == KeyCode.F) Home();
if (NodeEditorUtilities.IsMac()) {
if (e.keyCode == KeyCode.Return) RenameSelectedNode();
} else {
if (e.keyCode == KeyCode.F2) RenameSelectedNode();
}
if (e.keyCode == KeyCode.A) {
if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) {
foreach (XNode.Node node in graph.nodes) {
DeselectNode(node);
}
} else {
foreach (XNode.Node node in graph.nodes) {
SelectNode(node, true);
}
}
Repaint();
}
break;
case EventType.ValidateCommand:
case EventType.ExecuteCommand:
if (e.commandName == "SoftDelete") {
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
e.Use();
} else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") {
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
e.Use();
} else if (e.commandName == "Duplicate") {
if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
e.Use();
} else if (e.commandName == "Copy") {
if (!EditorGUIUtility.editingTextField) {
if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
e.Use();
}
} else if (e.commandName == "Paste") {
if (!EditorGUIUtility.editingTextField) {
if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
e.Use();
}
}
Repaint();
break;
case EventType.Ignore:
// If release mouse outside window
if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) {
Repaint();
currentActivity = NodeActivity.Idle;
}
break;
}
}
private void RecalculateDragOffsets(Event current) {
dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
// Selected nodes
for (int i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) {
XNode.Node node = Selection.objects[i] as XNode.Node;
dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
}
}
// Selected reroutes
for (int i = 0; i < selectedReroutes.Count; i++) {
dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);
}
}
/// <summary> Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary>
public void Home() {
var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList();
if (nodes.Count > 0) {
Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y)));
panOffset = -(minPos + (maxPos - minPos) / 2f);
} else {
zoom = 2;
panOffset = Vector2.zero;
}
}
/// <summary> Remove nodes in the graph in Selection.objects</summary>
public void RemoveSelectedNodes() {
// We need to delete reroutes starting at the highest point index to avoid shifting indices
selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();
for (int i = 0; i < selectedReroutes.Count; i++) {
selectedReroutes[i].RemovePoint();
}
selectedReroutes.Clear();
foreach (UnityEngine.Object item in Selection.objects) {
if (item is XNode.Node) {
XNode.Node node = item as XNode.Node;
graphEditor.RemoveNode(node);
}
}
}
/// <summary> Initiate a rename on the currently selected node </summary>
public void RenameSelectedNode() {
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
Vector2 size;
if (nodeSizes.TryGetValue(node, out size)) {
RenamePopup.Show(Selection.activeObject, size.x);
} else {
RenamePopup.Show(Selection.activeObject);
}
}
}
/// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary>
public void MoveNodeToTop(XNode.Node node) {
int index;
while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) {
graph.nodes[index] = graph.nodes[index + 1];
graph.nodes[index + 1] = node;
}
}
/// <summary> Duplicate selected nodes and select the duplicates </summary>
public void DuplicateSelectedNodes() {
// Get selected nodes which are part of this graph
XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
if (selectedNodes == null || selectedNodes.Length == 0) return;
// Get top left node position
Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));
}
public void CopySelectedNodes() {
copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
}
public void PasteNodes(Vector2 pos) {
InsertDuplicateNodes(copyBuffer, pos);
}
private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) {
if (nodes == null || nodes.Length == 0) return;
// Get top-left node
Vector2 topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
Vector2 offset = topLeft - topLeftNode;
UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length];
Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>();
for (int i = 0; i < nodes.Length; i++) {
XNode.Node srcNode = nodes[i];
if (srcNode == null) continue;
// Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
Type nodeType = srcNode.GetType();
if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) {
int typeCount = graph.nodes.Count(x => x.GetType() == nodeType);
if (typeCount >= disallowAttrib.max) continue;
}
XNode.Node newNode = graphEditor.CopyNode(srcNode);
substitutes.Add(srcNode, newNode);
newNode.position = srcNode.position + offset;
newNodes[i] = newNode;
}
// Walk through the selected nodes again, recreate connections, using the new nodes
for (int i = 0; i < nodes.Length; i++) {
XNode.Node srcNode = nodes[i];
if (srcNode == null) continue;
foreach (XNode.NodePort port in srcNode.Ports) {
for (int c = 0; c < port.ConnectionCount; c++) {
XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c);
XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);
XNode.Node newNodeIn, newNodeOut;
if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) {
newNodeIn.UpdatePorts();
newNodeOut.UpdatePorts();
inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
outputPort = newNodeOut.GetOutputPort(outputPort.fieldName);
}
if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort);
}
}
}
EditorUtility.SetDirty(graph);
// Select the new nodes
Selection.objects = newNodes;
}
/// <summary> Draw a connection as we are dragging it </summary>
public void DrawDraggedConnection() {
if (IsDraggingPort) {
Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);
NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null);
Rect fromRect;
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
List<Vector2> gridPoints = new List<Vector2>();
gridPoints.Add(fromRect.center);
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
gridPoints.Add(draggedOutputReroutes[i]);
}
if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition));
DrawNoodle(gradient, path, stroke, thickness, gridPoints);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput);
Color bgcol = Color.black;
Color frcol = gradient.colorKeys[0].color;
bgcol.a = 0.6f;
frcol.a = 0.6f;
// Loop through reroute points again and draw the points
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
// Draw reroute point at position
Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
rect = GridToWindowRect(rect);
NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol, portStyle.normal.background, portStyle.active.background);
}
}
}
bool IsHoveringTitle(XNode.Node node) {
Vector2 mousePos = Event.current.mousePosition;
//Get node position
Vector2 nodePos = GridToWindowPosition(node.position);
float width;
Vector2 size;
if (nodeSizes.TryGetValue(node, out size)) width = size.x;
else width = 200;
Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom));
return windowRect.Contains(mousePos);
}
/// <summary> Attempt to connect dragged output to target node </summary>
public void AutoConnect(XNode.Node node) {
if (autoConnectOutput == null) return;
// Find input port of same type
XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType);
// Fallback to input port
if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput);
// Autoconnect if connection is compatible
if (inputPort != null && inputPort.CanConnectTo(autoConnectOutput)) autoConnectOutput.Connect(inputPort);
// Save changes
EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
autoConnectOutput = null;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: aa7d4286bf0ad2e4086252f2893d2cf5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,66 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.IO;
namespace XNodeEditor {
/// <summary> Deals with modified assets </summary>
class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor {
/// <summary> Automatically delete Node sub-assets before deleting their script.
/// This is important to do, because you can't delete null sub assets.
/// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary>
private static AssetDeleteResult OnWillDeleteAsset (string path, RemoveAssetOptions options) {
// Skip processing anything without the .cs extension
if (Path.GetExtension(path) != ".cs") return AssetDeleteResult.DidNotDelete;
// Get the object that is requested for deletion
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object> (path);
// If we aren't deleting a script, return
if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete;
// Check script type. Return if deleting a non-node script
UnityEditor.MonoScript script = obj as UnityEditor.MonoScript;
System.Type scriptType = script.GetClass ();
if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete;
// Find all ScriptableObjects using this script
string[] guids = AssetDatabase.FindAssets ("t:" + scriptType);
for (int i = 0; i < guids.Length; i++) {
string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
for (int k = 0; k < objs.Length; k++) {
XNode.Node node = objs[k] as XNode.Node;
if (node.GetType () == scriptType) {
if (node != null && node.graph != null) {
// Delete the node and notify the user
Debug.LogWarning (node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph);
node.graph.RemoveNode (node);
}
}
}
}
// We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure
return AssetDeleteResult.DidNotDelete;
}
/// <summary> Automatically re-add loose node assets to the Graph node list </summary>
[InitializeOnLoadMethod]
private static void OnReloadEditor () {
// Find all NodeGraph assets
string[] guids = AssetDatabase.FindAssets ("t:" + typeof (XNode.NodeGraph));
for (int i = 0; i < guids.Length; i++) {
string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
XNode.NodeGraph graph = AssetDatabase.LoadAssetAtPath (assetpath, typeof (XNode.NodeGraph)) as XNode.NodeGraph;
graph.nodes.RemoveAll(x => x == null); //Remove null items
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
// Ensure that all sub node assets are present in the graph node list
for (int u = 0; u < objs.Length; u++) {
// Ignore null sub assets
if (objs[u] == null) continue;
if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node);
}
}
}
}
}

View File

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

View File

@ -1,101 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor;
#endif
namespace XNodeEditor.Internal {
/// <summary> Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type) </summary>
/// <typeparam name="T">Editor Type. Should be the type of the deriving script itself (eg. NodeEditor) </typeparam>
/// <typeparam name="A">Attribute Type. The attribute used to connect with the runtime type (eg. CustomNodeEditorAttribute) </typeparam>
/// <typeparam name="K">Runtime Type. The ScriptableObject this can be an editor for (eg. Node) </typeparam>
public abstract class NodeEditorBase<T, A, K> where A : Attribute, NodeEditorBase<T, A, K>.INodeEditorAttrib where T : NodeEditorBase<T, A, K> where K : ScriptableObject {
/// <summary> Custom editors defined with [CustomNodeEditor] </summary>
private static Dictionary<Type, Type> editorTypes;
private static Dictionary<K, T> editors = new Dictionary<K, T>();
public NodeEditorWindow window;
public K target;
public SerializedObject serializedObject;
#if ODIN_INSPECTOR
private PropertyTree _objectTree;
public PropertyTree objectTree {
get {
if (this._objectTree == null){
try {
bool wasInEditor = NodeEditor.inNodeEditor;
NodeEditor.inNodeEditor = true;
this._objectTree = PropertyTree.Create(this.serializedObject);
NodeEditor.inNodeEditor = wasInEditor;
} catch (ArgumentException ex) {
Debug.Log(ex);
}
}
return this._objectTree;
}
}
#endif
public static T GetEditor(K target, NodeEditorWindow window) {
if (target == null) return null;
T editor;
if (!editors.TryGetValue(target, out editor)) {
Type type = target.GetType();
Type editorType = GetEditorType(type);
editor = Activator.CreateInstance(editorType) as T;
editor.target = target;
editor.serializedObject = new SerializedObject(target);
editor.window = window;
editor.OnCreate();
editors.Add(target, editor);
}
if (editor.target == null) editor.target = target;
if (editor.window != window) editor.window = window;
if (editor.serializedObject == null) editor.serializedObject = new SerializedObject(target);
return editor;
}
public static void DestroyEditor( K target )
{
if ( target == null ) return;
T editor;
if ( editors.TryGetValue( target, out editor ) )
{
editors.Remove( target );
}
}
private static Type GetEditorType(Type type) {
if (type == null) return null;
if (editorTypes == null) CacheCustomEditors();
Type result;
if (editorTypes.TryGetValue(type, out result)) return result;
//If type isn't found, try base type
return GetEditorType(type.BaseType);
}
private static void CacheCustomEditors() {
editorTypes = new Dictionary<Type, Type>();
//Get all classes deriving from NodeEditor via reflection
Type[] nodeEditors = typeof(T).GetDerivedTypes();
for (int i = 0; i < nodeEditors.Length; i++) {
if (nodeEditors[i].IsAbstract) continue;
var attribs = nodeEditors[i].GetCustomAttributes(typeof(A), false);
if (attribs == null || attribs.Length == 0) continue;
A attrib = attribs[0] as A;
editorTypes.Add(attrib.GetInspectedType(), nodeEditors[i]);
}
}
/// <summary> Called on creation, after references have been set </summary>
public virtual void OnCreate() { }
public interface INodeEditorAttrib {
Type GetInspectedType();
}
}
}

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: e85122ded59aceb4eb4b1bd9d9202642
timeCreated: 1511353946
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,588 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using XNodeEditor.Internal;
namespace XNodeEditor {
/// <summary> Contains GUI methods </summary>
public partial class NodeEditorWindow {
public NodeGraphEditor graphEditor;
private List<UnityEngine.Object> selectionCache;
private List<XNode.Node> culledNodes;
/// <summary> 19 if docked, 22 if not </summary>
private int topPadding { get { return isDocked() ? 19 : 22; } }
/// <summary> Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run.</summary>
public event Action onLateGUI;
private static readonly Vector3[] polyLineTempArray = new Vector3[2];
protected virtual void OnGUI() {
Event e = Event.current;
Matrix4x4 m = GUI.matrix;
if (graph == null) return;
ValidateGraphEditor();
Controls();
DrawGrid(position, zoom, panOffset);
DrawConnections();
DrawDraggedConnection();
DrawNodes();
DrawSelectionBox();
DrawTooltip();
graphEditor.OnGUI();
RepaintAll();
// Run and reset onLateGUI
if (onLateGUI != null) {
onLateGUI();
onLateGUI = null;
}
GUI.matrix = m;
}
public static void BeginZoomed(Rect rect, float zoom, float topPadding) {
GUI.EndClip();
GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f);
Vector4 padding = new Vector4(0, topPadding, 0, 0);
padding *= zoom;
GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (topPadding * zoom),
rect.width * zoom,
rect.height * zoom));
}
public static void EndZoomed(Rect rect, float zoom, float topPadding) {
GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f);
Vector3 offset = new Vector3(
(((rect.width * zoom) - rect.width) * 0.5f),
(((rect.height * zoom) - rect.height) * 0.5f) + (-topPadding * zoom) + topPadding,
0);
GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one);
}
public void DrawGrid(Rect rect, float zoom, Vector2 panOffset) {
rect.position = Vector2.zero;
Vector2 center = rect.size / 2f;
Texture2D gridTex = graphEditor.GetGridTexture();
Texture2D crossTex = graphEditor.GetSecondaryGridTexture();
// Offset from origin in tile units
float xOffset = -(center.x * zoom + panOffset.x) / gridTex.width;
float yOffset = ((center.y - rect.size.y) * zoom + panOffset.y) / gridTex.height;
Vector2 tileOffset = new Vector2(xOffset, yOffset);
// Amount of tiles
float tileAmountX = Mathf.Round(rect.size.x * zoom) / gridTex.width;
float tileAmountY = Mathf.Round(rect.size.y * zoom) / gridTex.height;
Vector2 tileAmount = new Vector2(tileAmountX, tileAmountY);
// Draw tiled background
GUI.DrawTextureWithTexCoords(rect, gridTex, new Rect(tileOffset, tileAmount));
GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount));
}
public void DrawSelectionBox() {
if (currentActivity == NodeActivity.DragGrid) {
Vector2 curPos = WindowToGridPosition(Event.current.mousePosition);
Vector2 size = curPos - dragBoxStart;
Rect r = new Rect(dragBoxStart, size);
r.position = GridToWindowPosition(r.position);
r.size /= zoom;
Handles.DrawSolidRectangleWithOutline(r, new Color(0, 0, 0, 0.1f), new Color(1, 1, 1, 0.6f));
}
}
public static bool DropdownButton(string name, float width) {
return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width));
}
/// <summary> Show right-click context menu for hovered reroute </summary>
void ShowRerouteContextMenu(RerouteReference reroute) {
GenericMenu contextMenu = new GenericMenu();
contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint());
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
/// <summary> Show right-click context menu for hovered port </summary>
void ShowPortContextMenu(XNode.NodePort hoveredPort) {
GenericMenu contextMenu = new GenericMenu();
foreach (var port in hoveredPort.GetConnections()) {
var name = port.node.name;
var index = hoveredPort.GetConnectionIndex(port);
contextMenu.AddItem(new GUIContent(string.Format("Disconnect({0})", name)), false, () => hoveredPort.Disconnect(index));
}
contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections());
//Get compatible nodes with this port
if (NodeEditorPreferences.GetSettings().createFilter) {
contextMenu.AddSeparator("");
if (hoveredPort.direction == XNode.NodePort.IO.Input)
graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Output);
else
graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Input);
}
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
static Vector2 CalculateBezierPoint(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) {
float u = 1 - t;
float tt = t * t, uu = u * u;
float uuu = uu * u, ttt = tt * t;
return new Vector2(
(uuu * p0.x) + (3 * uu * t * p1.x) + (3 * u * tt * p2.x) + (ttt * p3.x),
(uuu * p0.y) + (3 * uu * t * p1.y) + (3 * u * tt * p2.y) + (ttt * p3.y)
);
}
/// <summary> Draws a line segment without allocating temporary arrays </summary>
static void DrawAAPolyLineNonAlloc(float thickness, Vector2 p0, Vector2 p1) {
polyLineTempArray[0].x = p0.x;
polyLineTempArray[0].y = p0.y;
polyLineTempArray[1].x = p1.x;
polyLineTempArray[1].y = p1.y;
Handles.DrawAAPolyLine(thickness, polyLineTempArray);
}
/// <summary> Draw a bezier from output to input in grid coordinates </summary>
public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, List<Vector2> gridPoints) {
// convert grid points to window points
for (int i = 0; i < gridPoints.Count; ++i)
gridPoints[i] = GridToWindowPosition(gridPoints[i]);
Color originalHandlesColor = Handles.color;
Handles.color = gradient.Evaluate(0f);
int length = gridPoints.Count;
switch (path) {
case NoodlePath.Curvy:
Vector2 outputTangent = Vector2.right;
for (int i = 0; i < length - 1; i++) {
Vector2 inputTangent;
// Cached most variables that repeat themselves here to avoid so many indexer calls :p
Vector2 point_a = gridPoints[i];
Vector2 point_b = gridPoints[i + 1];
float dist_ab = Vector2.Distance(point_a, point_b);
if (i == 0) outputTangent = zoom * dist_ab * 0.01f * Vector2.right;
if (i < length - 2) {
Vector2 point_c = gridPoints[i + 2];
Vector2 ab = (point_b - point_a).normalized;
Vector2 cb = (point_b - point_c).normalized;
Vector2 ac = (point_c - point_a).normalized;
Vector2 p = (ab + cb) * 0.5f;
float tangentLength = (dist_ab + Vector2.Distance(point_b, point_c)) * 0.005f * zoom;
float side = ((ac.x * (point_b.y - point_a.y)) - (ac.y * (point_b.x - point_a.x)));
p = tangentLength * Mathf.Sign(side) * new Vector2(-p.y, p.x);
inputTangent = p;
} else {
inputTangent = zoom * dist_ab * 0.01f * Vector2.left;
}
// Calculates the tangents for the bezier's curves.
float zoomCoef = 50 / zoom;
Vector2 tangent_a = point_a + outputTangent * zoomCoef;
Vector2 tangent_b = point_b + inputTangent * zoomCoef;
// Hover effect.
int division = Mathf.RoundToInt(.2f * dist_ab) + 3;
// Coloring and bezier drawing.
int draw = 0;
Vector2 bezierPrevious = point_a;
for (int j = 1; j <= division; ++j) {
if (stroke == NoodleStroke.Dashed) {
draw++;
if (draw >= 2) draw = -2;
if (draw < 0) continue;
if (draw == 0) bezierPrevious = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, (j - 1f) / (float) division);
}
if (i == length - 2)
Handles.color = gradient.Evaluate((j + 1f) / division);
Vector2 bezierNext = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, j / (float) division);
DrawAAPolyLineNonAlloc(thickness, bezierPrevious, bezierNext);
bezierPrevious = bezierNext;
}
outputTangent = -inputTangent;
}
break;
case NoodlePath.Straight:
for (int i = 0; i < length - 1; i++) {
Vector2 point_a = gridPoints[i];
Vector2 point_b = gridPoints[i + 1];
// Draws the line with the coloring.
Vector2 prev_point = point_a;
// Approximately one segment per 5 pixels
int segments = (int) Vector2.Distance(point_a, point_b) / 5;
segments = Math.Max(segments, 1);
int draw = 0;
for (int j = 0; j <= segments; j++) {
draw++;
float t = j / (float) segments;
Vector2 lerp = Vector2.Lerp(point_a, point_b, t);
if (draw > 0) {
if (i == length - 2) Handles.color = gradient.Evaluate(t);
DrawAAPolyLineNonAlloc(thickness, prev_point, lerp);
}
prev_point = lerp;
if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2;
}
}
break;
case NoodlePath.Angled:
for (int i = 0; i < length - 1; i++) {
if (i == length - 1) continue; // Skip last index
if (gridPoints[i].x <= gridPoints[i + 1].x - (50 / zoom)) {
float midpoint = (gridPoints[i].x + gridPoints[i + 1].x) * 0.5f;
Vector2 start_1 = gridPoints[i];
Vector2 end_1 = gridPoints[i + 1];
start_1.x = midpoint;
end_1.x = midpoint;
if (i == length - 2) {
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
Handles.color = gradient.Evaluate(0.5f);
DrawAAPolyLineNonAlloc(thickness, start_1, end_1);
Handles.color = gradient.Evaluate(1f);
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
} else {
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
DrawAAPolyLineNonAlloc(thickness, start_1, end_1);
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
}
} else {
float midpoint = (gridPoints[i].y + gridPoints[i + 1].y) * 0.5f;
Vector2 start_1 = gridPoints[i];
Vector2 end_1 = gridPoints[i + 1];
start_1.x += 25 / zoom;
end_1.x -= 25 / zoom;
Vector2 start_2 = start_1;
Vector2 end_2 = end_1;
start_2.y = midpoint;
end_2.y = midpoint;
if (i == length - 2) {
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
Handles.color = gradient.Evaluate(0.25f);
DrawAAPolyLineNonAlloc(thickness, start_1, start_2);
Handles.color = gradient.Evaluate(0.5f);
DrawAAPolyLineNonAlloc(thickness, start_2, end_2);
Handles.color = gradient.Evaluate(0.75f);
DrawAAPolyLineNonAlloc(thickness, end_2, end_1);
Handles.color = gradient.Evaluate(1f);
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
} else {
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
DrawAAPolyLineNonAlloc(thickness, start_1, start_2);
DrawAAPolyLineNonAlloc(thickness, start_2, end_2);
DrawAAPolyLineNonAlloc(thickness, end_2, end_1);
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
}
}
}
break;
case NoodlePath.ShaderLab:
Vector2 start = gridPoints[0];
Vector2 end = gridPoints[length - 1];
//Modify first and last point in array so we can loop trough them nicely.
gridPoints[0] = gridPoints[0] + Vector2.right * (20 / zoom);
gridPoints[length - 1] = gridPoints[length - 1] + Vector2.left * (20 / zoom);
//Draw first vertical lines going out from nodes
Handles.color = gradient.Evaluate(0f);
DrawAAPolyLineNonAlloc(thickness, start, gridPoints[0]);
Handles.color = gradient.Evaluate(1f);
DrawAAPolyLineNonAlloc(thickness, end, gridPoints[length - 1]);
for (int i = 0; i < length - 1; i++) {
Vector2 point_a = gridPoints[i];
Vector2 point_b = gridPoints[i + 1];
// Draws the line with the coloring.
Vector2 prev_point = point_a;
// Approximately one segment per 5 pixels
int segments = (int) Vector2.Distance(point_a, point_b) / 5;
segments = Math.Max(segments, 1);
int draw = 0;
for (int j = 0; j <= segments; j++) {
draw++;
float t = j / (float) segments;
Vector2 lerp = Vector2.Lerp(point_a, point_b, t);
if (draw > 0) {
if (i == length - 2) Handles.color = gradient.Evaluate(t);
DrawAAPolyLineNonAlloc(thickness, prev_point, lerp);
}
prev_point = lerp;
if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2;
}
}
gridPoints[0] = start;
gridPoints[length - 1] = end;
break;
}
Handles.color = originalHandlesColor;
}
/// <summary> Draws all connections </summary>
public void DrawConnections() {
Vector2 mousePos = Event.current.mousePosition;
List<RerouteReference> selection = preBoxSelectionReroute != null ? new List<RerouteReference>(preBoxSelectionReroute) : new List<RerouteReference>();
hoveredReroute = new RerouteReference();
List<Vector2> gridPoints = new List<Vector2>(2);
Color col = GUI.color;
foreach (XNode.Node node in graph.nodes) {
//If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset.
if (node == null) continue;
// Draw full connections and output > reroute
foreach (XNode.NodePort output in node.Outputs) {
//Needs cleanup. Null checks are ugly
Rect fromRect;
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue;
Color portColor = graphEditor.GetPortColor(output);
GUIStyle portStyle = graphEditor.GetPortStyle(output);
for (int k = 0; k < output.ConnectionCount; k++) {
XNode.NodePort input = output.GetConnection(k);
Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input);
float noodleThickness = graphEditor.GetNoodleThickness(output, input);
NoodlePath noodlePath = graphEditor.GetNoodlePath(output, input);
NoodleStroke noodleStroke = graphEditor.GetNoodleStroke(output, input);
// Error handling
if (input == null) continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return.
if (!input.IsConnectedTo(output)) input.Connect(output);
Rect toRect;
if (!_portConnectionPoints.TryGetValue(input, out toRect)) continue;
List<Vector2> reroutePoints = output.GetReroutePoints(k);
gridPoints.Clear();
gridPoints.Add(fromRect.center);
gridPoints.AddRange(reroutePoints);
gridPoints.Add(toRect.center);
DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints);
// Loop through reroute points again and draw the points
for (int i = 0; i < reroutePoints.Count; i++) {
RerouteReference rerouteRef = new RerouteReference(output, k, i);
// Draw reroute point at position
Rect rect = new Rect(reroutePoints[i], new Vector2(12, 12));
rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6);
rect = GridToWindowRect(rect);
// Draw selected reroute points with an outline
if (selectedReroutes.Contains(rerouteRef)) {
GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
GUI.DrawTexture(rect, portStyle.normal.background);
}
GUI.color = portColor;
GUI.DrawTexture(rect, portStyle.active.background);
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;
}
}
}
}
GUI.color = col;
if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection;
}
private void DrawNodes() {
Event e = Event.current;
if (e.type == EventType.Layout) {
selectionCache = new List<UnityEngine.Object>(Selection.objects);
}
System.Reflection.MethodInfo onValidate = null;
if (Selection.activeObject != null && Selection.activeObject is XNode.Node) {
onValidate = Selection.activeObject.GetType().GetMethod("OnValidate");
if (onValidate != null) EditorGUI.BeginChangeCheck();
}
BeginZoomed(position, zoom, topPadding);
Vector2 mousePos = Event.current.mousePosition;
if (e.type != EventType.Layout) {
hoveredNode = null;
hoveredPort = null;
}
List<UnityEngine.Object> preSelection = preBoxSelection != null ? new List<UnityEngine.Object>(preBoxSelection) : new List<UnityEngine.Object>();
// Selection box stuff
Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart);
Vector2 boxSize = mousePos - boxStartPos;
if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
Rect selectionBox = new Rect(boxStartPos, boxSize);
//Save guiColor so we can revert it
Color guiColor = GUI.color;
List<XNode.NodePort> removeEntries = new List<XNode.NodePort>();
if (e.type == EventType.Layout) culledNodes = new List<XNode.Node>();
for (int n = 0; n < graph.nodes.Count; n++) {
// Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable.
if (graph.nodes[n] == null) continue;
if (n >= graph.nodes.Count) return;
XNode.Node node = graph.nodes[n];
// Culling
if (e.type == EventType.Layout) {
// Cull unselected nodes outside view
if (!Selection.Contains(node) && ShouldBeCulled(node)) {
culledNodes.Add(node);
continue;
}
} else if (culledNodes.Contains(node)) continue;
if (e.type == EventType.Repaint) {
removeEntries.Clear();
foreach (var kvp in _portConnectionPoints)
if (kvp.Key.node == node) removeEntries.Add(kvp.Key);
foreach (var k in removeEntries) _portConnectionPoints.Remove(k);
}
NodeEditor nodeEditor = NodeEditor.GetEditor(node, this);
NodeEditor.portPositions.Clear();
// Set default label width. This is potentially overridden in OnBodyGUI
EditorGUIUtility.labelWidth = 84;
//Get node position
Vector2 nodePos = GridToWindowPositionNoClipped(node.position);
GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000)));
bool selected = selectionCache.Contains(graph.nodes[n]);
if (selected) {
GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle());
GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle());
highlightStyle.padding = style.padding;
style.padding = new RectOffset();
GUI.color = nodeEditor.GetTint();
GUILayout.BeginVertical(style);
GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
GUILayout.BeginVertical(new GUIStyle(highlightStyle));
} else {
GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle());
GUI.color = nodeEditor.GetTint();
GUILayout.BeginVertical(style);
}
GUI.color = guiColor;
EditorGUI.BeginChangeCheck();
//Draw node contents
nodeEditor.OnHeaderGUI();
nodeEditor.OnBodyGUI();
//If user changed a value, notify other scripts through onUpdateNode
if (EditorGUI.EndChangeCheck()) {
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node);
EditorUtility.SetDirty(node);
nodeEditor.serializedObject.ApplyModifiedProperties();
}
GUILayout.EndVertical();
//Cache data about the node for next frame
if (e.type == EventType.Repaint) {
Vector2 size = GUILayoutUtility.GetLastRect().size;
if (nodeSizes.ContainsKey(node)) nodeSizes[node] = size;
else nodeSizes.Add(node, size);
foreach (var kvp in NodeEditor.portPositions) {
Vector2 portHandlePos = kvp.Value;
portHandlePos += node.position;
Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16);
portConnectionPoints[kvp.Key] = rect;
}
}
if (selected) GUILayout.EndVertical();
if (e.type != EventType.Layout) {
//Check if we are hovering this node
Vector2 nodeSize = GUILayoutUtility.GetLastRect().size;
Rect windowRect = new Rect(nodePos, nodeSize);
if (windowRect.Contains(mousePos)) hoveredNode = node;
//If dragging a selection box, add nodes inside to selection
if (currentActivity == NodeActivity.DragGrid) {
if (windowRect.Overlaps(selectionBox)) preSelection.Add(node);
}
//Check if we are hovering any of this nodes ports
//Check input ports
foreach (XNode.NodePort input in node.Inputs) {
//Check if port rect is available
if (!portConnectionPoints.ContainsKey(input)) continue;
Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]);
if (r.Contains(mousePos)) hoveredPort = input;
}
//Check all output ports
foreach (XNode.NodePort output in node.Outputs) {
//Check if port rect is available
if (!portConnectionPoints.ContainsKey(output)) continue;
Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]);
if (r.Contains(mousePos)) hoveredPort = output;
}
}
GUILayout.EndArea();
}
if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray();
EndZoomed(position, zoom, topPadding);
//If a change in is detected in the selected node, call OnValidate method.
//This is done through reflection because OnValidate is only relevant in editor,
//and thus, the code should not be included in build.
if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null);
}
private bool ShouldBeCulled(XNode.Node node) {
Vector2 nodePos = GridToWindowPositionNoClipped(node.position);
if (nodePos.x / _zoom > position.width) return true; // Right
else if (nodePos.y / _zoom > position.height) return true; // Bottom
else if (nodeSizes.ContainsKey(node)) {
Vector2 size = nodeSizes[node];
if (nodePos.x + size.x < 0) return true; // Left
else if (nodePos.y + size.y < 0) return true; // Top
}
return false;
}
private void DrawTooltip() {
if (!NodeEditorPreferences.GetSettings().portTooltips || graphEditor == null)
return;
string tooltip = null;
if (hoveredPort != null) {
tooltip = graphEditor.GetPortTooltip(hoveredPort);
} else if (hoveredNode != null && IsHoveringNode && IsHoveringTitle(hoveredNode)) {
tooltip = NodeEditor.GetEditor(hoveredNode, this).GetHeaderTooltip();
}
if (string.IsNullOrEmpty(tooltip)) return;
GUIContent content = new GUIContent(tooltip);
Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content);
size.x += 8;
Rect rect = new Rect(Event.current.mousePosition - (size), size);
EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip);
Repaint();
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 756276bfe9a0c2f4da3930ba1964f58d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,540 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace XNodeEditor {
/// <summary> xNode-specific version of <see cref="EditorGUILayout"/> </summary>
public static class NodeEditorGUILayout {
private static readonly Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>> reorderableListCache = new Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>>();
private static int reorderableListIndex = -1;
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options) {
PropertyField(property, (GUIContent)null, includeChildren, options);
}
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) {
if (property == null) throw new NullReferenceException();
XNode.Node node = property.serializedObject.targetObject as XNode.Node;
XNode.NodePort port = node.GetPort(property.name);
PropertyField(property, label, port, includeChildren);
}
/// <summary> Make a field for a serialized property. Manual node port override. </summary>
public static void PropertyField(SerializedProperty property, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) {
PropertyField(property, null, port, includeChildren, options);
}
/// <summary> Make a field for a serialized property. Manual node port override. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) {
if (property == null) throw new NullReferenceException();
// If property is not a port, display a regular property field
if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
else {
Rect rect = new Rect();
List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name);
// If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) {
// Get data from [Input] attribute
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
XNode.Node.InputAttribute inputAttribute;
bool dynamicPortList = false;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) {
dynamicPortList = inputAttribute.dynamicPortList;
showBacking = inputAttribute.backingValue;
}
bool usePropertyAttributes = dynamicPortList ||
showBacking == XNode.Node.ShowBackingValue.Never ||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
float spacePadding = 0;
string tooltip = null;
foreach (var attr in propertyAttributes) {
if (attr is SpaceAttribute) {
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
else spacePadding += (attr as SpaceAttribute).height;
} else if (attr is HeaderAttribute) {
if (usePropertyAttributes) {
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
} else if (attr is TooltipAttribute) {
tooltip = (attr as TooltipAttribute).tooltip;
}
}
if (dynamicPortList) {
Type type = GetType(property);
XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
return;
}
switch (showBacking) {
case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
// Display an editable property field if port is not connected
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
break;
case XNode.Node.ShowBackingValue.Never:
// Display a label
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
break;
case XNode.Node.ShowBackingValue.Always:
// Display an editable property field
EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
break;
}
rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
rect.position = rect.position - new Vector2(16 + paddingLeft, -spacePadding);
// If property is an output, display a text label and put a port handle on the right side
} else if (port.direction == XNode.NodePort.IO.Output) {
// Get data from [Output] attribute
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
XNode.Node.OutputAttribute outputAttribute;
bool dynamicPortList = false;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) {
dynamicPortList = outputAttribute.dynamicPortList;
showBacking = outputAttribute.backingValue;
}
bool usePropertyAttributes = dynamicPortList ||
showBacking == XNode.Node.ShowBackingValue.Never ||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
float spacePadding = 0;
string tooltip = null;
foreach (var attr in propertyAttributes) {
if (attr is SpaceAttribute) {
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
else spacePadding += (attr as SpaceAttribute).height;
} else if (attr is HeaderAttribute) {
if (usePropertyAttributes) {
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
} else if (attr is TooltipAttribute) {
tooltip = (attr as TooltipAttribute).tooltip;
}
}
if (dynamicPortList) {
Type type = GetType(property);
XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
return;
}
switch (showBacking) {
case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
// Display an editable property field if port is not connected
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
break;
case XNode.Node.ShowBackingValue.Never:
// Display a label
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
break;
case XNode.Node.ShowBackingValue.Always:
// Display an editable property field
EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
break;
}
rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
rect.position = rect.position + new Vector2(rect.width, spacePadding);
}
rect.size = new Vector2(16, 16);
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position
Vector2 portPos = rect.center;
NodeEditor.portPositions[port] = portPos;
}
}
private static System.Type GetType(SerializedProperty property) {
System.Type parentType = property.serializedObject.targetObject.GetType();
System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name);
return fi.FieldType;
}
/// <summary> Make a simple port field. </summary>
public static void PortField(XNode.NodePort port, params GUILayoutOption[] options) {
PortField(null, port, options);
}
/// <summary> Make a simple port field. </summary>
public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) {
if (port == null) return;
if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) };
Vector2 position = Vector3.zero;
GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName));
// If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) {
// Display a label
EditorGUILayout.LabelField(content, options);
Rect rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
position = rect.position - new Vector2(16 + paddingLeft, 0);
}
// If property is an output, display a text label and put a port handle on the right side
else if (port.direction == XNode.NodePort.IO.Output) {
// Display a label
EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
Rect rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
position = rect.position + new Vector2(rect.width, 0);
}
PortField(position, port);
}
/// <summary> Make a simple port field. </summary>
public static void PortField(Vector2 position, XNode.NodePort port) {
if (port == null) return;
Rect rect = new Rect(position, new Vector2(16, 16));
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position
Vector2 portPos = rect.center;
NodeEditor.portPositions[port] = portPos;
}
/// <summary> Add a port field to previous layout element. </summary>
public static void AddPortField(XNode.NodePort port) {
if (port == null) return;
Rect rect = new Rect();
// If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) {
rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
rect.position = rect.position - new Vector2(16 + paddingLeft, 0);
// If property is an output, display a text label and put a port handle on the right side
} else if (port.direction == XNode.NodePort.IO.Output) {
rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
rect.position = rect.position + new Vector2(rect.width, 0);
}
rect.size = new Vector2(16, 16);
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position
Vector2 portPos = rect.center;
NodeEditor.portPositions[port] = portPos;
}
/// <summary> Draws an input and an output port on the same line </summary>
public static void PortPair(XNode.NodePort input, XNode.NodePort output) {
GUILayout.BeginHorizontal();
NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0));
NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0));
GUILayout.EndHorizontal();
}
/// <summary>
/// Draw the port
/// </summary>
/// <param name="rect">position and size</param>
/// <param name="backgroundColor">color for background texture of the port. Normaly used to Border</param>
/// <param name="typeColor"></param>
/// <param name="border">texture for border of the dot port</param>
/// <param name="dot">texture for the dot port</param>
public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor, Texture2D border, Texture2D dot) {
Color col = GUI.color;
GUI.color = backgroundColor;
GUI.DrawTexture(rect, border);
GUI.color = typeColor;
GUI.DrawTexture(rect, dot);
GUI.color = col;
}
#region Obsolete
[Obsolete("Use IsDynamicPortListPort instead")]
public static bool IsInstancePortListPort(XNode.NodePort port) {
return IsDynamicPortListPort(port);
}
[Obsolete("Use DynamicPortList instead")]
public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) {
DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation);
}
#endregion
/// <summary> Is this port part of a DynamicPortList? </summary>
public static bool IsDynamicPortListPort(XNode.NodePort port) {
string[] parts = port.fieldName.Split(' ');
if (parts.Length != 2) return false;
Dictionary<string, ReorderableList> cache;
if (reorderableListCache.TryGetValue(port.node, out cache)) {
ReorderableList list;
if (cache.TryGetValue(parts[0], out list)) return true;
}
return false;
}
/// <summary> Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" </summary>
/// <param name="fieldName">Supply a list for editable values</param>
/// <param name="type">Value type of added dynamic ports</param>
/// <param name="serializedObject">The serializedObject of the node</param>
/// <param name="connectionType">Connection type of added dynamic ports</param>
/// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) {
XNode.Node node = serializedObject.targetObject as XNode.Node;
var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) {
int i = -1;
if (int.TryParse(split[1], out i)) {
return new { index = i, port = x };
}
}
return new { index = -1, port = (XNode.NodePort)null };
}).Where(x => x.port != null);
List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
node.UpdatePorts();
ReorderableList list = null;
Dictionary<string, ReorderableList> rlc;
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) {
if (!rlc.TryGetValue(fieldName, out list)) list = null;
}
// If a ReorderableList isn't cached for this array, do so.
if (list == null) {
SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } });
}
list.list = dynamicPorts;
list.DoLayoutList();
}
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) {
bool hasArrayData = arrayData != null && arrayData.isArray;
XNode.Node node = serializedObject.targetObject as XNode.Node;
ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true);
string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName);
list.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
XNode.NodePort port = node.GetPort(fieldName + " " + index);
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
if (arrayData.arraySize <= index) {
EditorGUI.LabelField(rect, "Array[" + index + "] data out of range");
return;
}
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, itemData, true);
} else EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
if (port != null) {
Vector2 pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0));
NodeEditorGUILayout.PortField(pos, port);
}
};
list.elementHeightCallback =
(int index) => {
if (hasArrayData) {
if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight;
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
return EditorGUI.GetPropertyHeight(itemData);
} else return EditorGUIUtility.singleLineHeight;
};
list.drawHeaderCallback =
(Rect rect) => {
EditorGUI.LabelField(rect, label);
};
list.onSelectCallback =
(ReorderableList rl) => {
reorderableListIndex = rl.index;
};
list.onReorderCallback =
(ReorderableList rl) => {
bool hasRect = false;
bool hasNewRect = false;
Rect rect = Rect.zero;
Rect newRect = Rect.zero;
// Move up
if (rl.index > reorderableListIndex) {
for (int i = reorderableListIndex; i < rl.index; ++i) {
XNode.NodePort port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1));
port.SwapConnections(nextPort);
// Swap cached positions to mitigate twitching
hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect);
hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect);
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect;
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect;
}
}
// Move down
else {
for (int i = reorderableListIndex; i > rl.index; --i) {
XNode.NodePort port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1));
port.SwapConnections(nextPort);
// Swap cached positions to mitigate twitching
hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect);
hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect);
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect;
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect;
}
}
// Apply changes
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
// Move array data if there is any
if (hasArrayData) {
arrayData.MoveArrayElement(reorderableListIndex, rl.index);
}
// Apply changes
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
NodeEditorWindow.current.Repaint();
EditorApplication.delayCall += NodeEditorWindow.current.Repaint;
};
list.onAddCallback =
(ReorderableList rl) => {
// Add dynamic port postfixed with an index number
string newName = fieldName + " 0";
int i = 0;
while (node.HasPort(newName)) newName = fieldName + " " + (++i);
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName);
else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
serializedObject.Update();
EditorUtility.SetDirty(node);
if (hasArrayData) {
arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
}
serializedObject.ApplyModifiedProperties();
};
list.onRemoveCallback =
(ReorderableList rl) => {
var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) {
int i = -1;
if (int.TryParse(split[1], out i)) {
return new { index = i, port = x };
}
}
return new { index = -1, port = (XNode.NodePort)null };
}).Where(x => x.port != null);
dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
int index = rl.index;
if (dynamicPorts[index] == null) {
Debug.LogWarning("No port found at index " + index + " - Skipped");
} else if (dynamicPorts.Count <= index) {
Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped");
} else {
// Clear the removed ports connections
dynamicPorts[index].ClearConnections();
// Move following connections one step up to replace the missing connection
for (int k = index + 1; k < dynamicPorts.Count(); k++) {
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) {
XNode.NodePort other = dynamicPorts[k].GetConnection(j);
dynamicPorts[k].Disconnect(other);
dynamicPorts[k - 1].Connect(other);
}
}
// Remove the last dynamic port, to avoid messing up the indexing
node.RemoveDynamicPort(dynamicPorts[dynamicPorts.Count() - 1].fieldName);
serializedObject.Update();
EditorUtility.SetDirty(node);
}
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
if (arrayData.arraySize <= index) {
Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped");
Debug.Log(rl.list[0]);
return;
}
arrayData.DeleteArrayElementAtIndex(index);
// Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues
if (dynamicPorts.Count <= arrayData.arraySize) {
while (dynamicPorts.Count <= arrayData.arraySize) {
arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1);
}
UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed.");
}
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
};
if (hasArrayData) {
int dynamicPortCount = dynamicPorts.Count;
while (dynamicPortCount < arrayData.arraySize) {
// Add dynamic port postfixed with an index number
string newName = arrayData.name + " 0";
int i = 0;
while (node.HasPort(newName)) newName = arrayData.name + " " + (++i);
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName);
else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
EditorUtility.SetDirty(node);
dynamicPortCount++;
}
while (arrayData.arraySize < dynamicPortCount) {
arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
}
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (onCreation != null) onCreation(list);
return list;
}
}
}

View File

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

View File

@ -1,321 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
namespace XNodeEditor {
public enum NoodlePath { Curvy, Straight, Angled, ShaderLab }
public enum NoodleStroke { Full, Dashed }
public static class NodeEditorPreferences {
/// <summary> The last editor we checked. This should be the one we modify </summary>
private static XNodeEditor.NodeGraphEditor lastEditor;
/// <summary> The last key we checked. This should be the one we modify </summary>
private static string lastKey = "xNode.Settings";
private static Dictionary<Type, Color> typeColors = new Dictionary<Type, Color>();
private static Dictionary<string, Settings> settings = new Dictionary<string, Settings>();
[System.Serializable]
public class Settings : ISerializationCallbackReceiver {
[SerializeField] private Color32 _gridLineColor = new Color(.23f, .23f, .23f);
public Color32 gridLineColor { get { return _gridLineColor; } set { _gridLineColor = value; _gridTexture = null; _crossTexture = null; } }
[SerializeField] private Color32 _gridBgColor = new Color(.19f, .19f, .19f);
public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } }
[Obsolete("Use maxZoom instead")]
public float zoomOutLimit { get { return maxZoom; } set { maxZoom = value; } }
[UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")]
public float maxZoom = 5f;
public float minZoom = 1f;
public Color32 tintColor = new Color32(90, 97, 105, 255);
public Color32 highlightColor = new Color32(255, 255, 255, 255);
public bool gridSnap = true;
public bool autoSave = true;
public bool openOnCreate = true;
public bool dragToCreate = true;
public bool createFilter = true;
public bool zoomToMouse = true;
public bool portTooltips = true;
public bool themeResetButton;
[SerializeField] private string typeColorsData = "";
[NonSerialized] public Dictionary<string, Color> typeColors = new Dictionary<string, Color>();
[FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy;
public float noodleThickness = 2f;
public NoodleStroke noodleStroke = NoodleStroke.Full;
private Texture2D _gridTexture;
public Texture2D gridTexture {
get {
if (_gridTexture == null) _gridTexture = NodeEditorResources.GenerateGridTexture(gridLineColor, gridBgColor);
return _gridTexture;
}
}
private Texture2D _crossTexture;
public Texture2D crossTexture {
get {
if (_crossTexture == null) _crossTexture = NodeEditorResources.GenerateCrossTexture(gridLineColor);
return _crossTexture;
}
}
public XNode.Theme _theme;
public XNode.Theme theme {
get {
if(_theme != null)
return _theme;
else
return Resources.Load<XNode.Theme>("DefualtXNodeTheme");
}
set { _theme = value;}
}
public void OnAfterDeserialize() {
// Deserialize typeColorsData
typeColors = new Dictionary<string, Color>();
string[] data = typeColorsData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < data.Length; i += 2) {
Color col;
if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) {
typeColors.Add(data[i], col);
}
}
}
public void OnBeforeSerialize() {
// Serialize typeColors
typeColorsData = "";
foreach (var item in typeColors) {
typeColorsData += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ",";
}
}
}
/// <summary> Get settings of current active editor </summary>
public static Settings GetSettings() {
if (XNodeEditor.NodeEditorWindow.current == null) return new Settings();
if (lastEditor != XNodeEditor.NodeEditorWindow.current.graphEditor) {
object[] attribs = XNodeEditor.NodeEditorWindow.current.graphEditor.GetType().GetCustomAttributes(typeof(XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute), true);
if (attribs.Length == 1) {
XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute;
lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor;
lastKey = attrib.editorPrefsKey;
} else return null;
}
if (!settings.ContainsKey(lastKey)) VerifyLoaded();
return settings[lastKey];
}
#if UNITY_2019_1_OR_NEWER
[SettingsProvider]
public static SettingsProvider CreateXNodeSettingsProvider() {
SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) {
guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); },
keywords = new HashSet<string>(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" })
};
return provider;
}
#endif
#if !UNITY_2019_1_OR_NEWER
[PreferenceItem("Node Editor")]
#endif
private static void PreferencesGUI() {
VerifyLoaded();
Settings settings = NodeEditorPreferences.settings[lastKey];
if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100))) Application.OpenURL("https://github.com/Siccity/xNode/wiki");
EditorGUILayout.Space();
ThemeSettings(lastKey, settings);
NodeSettingsGUI(lastKey, settings);
GridSettingsGUI(lastKey, settings);
SystemSettingsGUI(lastKey, settings);
TypeColorsGUI(lastKey, settings);
if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120))) {
ResetPrefs();
}
}
private static void GridSettingsGUI(string key, Settings settings) {
//Label
EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel);
settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap);
settings.zoomToMouse = EditorGUILayout.Toggle(new GUIContent("Zoom to Mouse", "Zooms towards mouse position"), settings.zoomToMouse);
EditorGUILayout.LabelField("Zoom");
EditorGUI.indentLevel++;
settings.maxZoom = EditorGUILayout.FloatField(new GUIContent("Max", "Upper limit to zoom"), settings.maxZoom);
settings.minZoom = EditorGUILayout.FloatField(new GUIContent("Min", "Lower limit to zoom"), settings.minZoom);
EditorGUI.indentLevel--;
//
if (GUI.changed) {
SavePrefs(key, settings);
NodeEditorWindow.RepaintAll();
}
EditorGUILayout.Space();
}
private static void SystemSettingsGUI(string key, Settings settings) {
//Label
EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave);
settings.openOnCreate = EditorGUILayout.Toggle(new GUIContent("Open Editor on Create", "Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate);
if (GUI.changed) SavePrefs(key, settings);
EditorGUILayout.Space();
}
private static void NodeSettingsGUI(string key, Settings settings) {
//Label
EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
//
settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips);
settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate);
settings.createFilter = EditorGUILayout.Toggle(new GUIContent("Create Filter", "Only show nodes that are compatible with the selected port"), settings.createFilter);
//END
if (GUI.changed) {
SavePrefs(key, settings);
NodeEditorWindow.RepaintAll();
}
EditorGUILayout.Space();
}
private static void TypeColorsGUI(string key, Settings settings) {
//Label
EditorGUILayout.LabelField("Types", EditorStyles.boldLabel);
//Clone keys so we can enumerate the dictionary and make changes.
var typeColorKeys = new List<Type>(typeColors.Keys);
//Display type colors. Save them if they are edited by the user
foreach (var type in typeColorKeys) {
string typeColorKey = NodeEditorUtilities.PrettyName(type);
Color col = typeColors[type];
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
col = EditorGUILayout.ColorField(typeColorKey, col);
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck()) {
typeColors[type] = col;
if (settings.typeColors.ContainsKey(typeColorKey)) settings.typeColors[typeColorKey] = col;
else settings.typeColors.Add(typeColorKey, col);
SavePrefs(key, settings);
NodeEditorWindow.RepaintAll();
}
}
}
private static void ThemeSettings (string key, Settings settings)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Theme", EditorStyles.boldLabel);
if(GUILayout.Button("Create New", GUILayout.Width(90)))
{
XNode.Theme theme = ScriptableObject.CreateInstance<XNode.Theme>();
//AssetDatabase.CreateAsset(theme, GetSelectedPathOrFallback());
System.Reflection.MethodInfo getActiveFolderPath = typeof(ProjectWindowUtil).GetMethod(
"GetActiveFolderPath",
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
string folderPath = (string) getActiveFolderPath.Invoke(null, null);
ProjectWindowUtil.CreateAsset(theme, folderPath + "/New Theme.asset");
AssetDatabase.SaveAssets();
EditorUtility.FocusProjectWindow();
Selection.activeObject = theme;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
settings.theme = (XNode.Theme) EditorGUILayout.ObjectField(settings.theme, typeof(XNode.Theme), true);
if(EditorGUI.EndChangeCheck() && !NodeEditorWindow.justOpened || settings.themeResetButton)
{
settings.tintColor = settings.theme.tint;
settings.highlightColor = settings.theme.selection;
settings.noodlePath = settings.theme.noodlePath;
settings.noodleThickness = settings.theme.noodleThickness;
settings.noodleStroke = settings.theme.noodleStroke;
settings.gridLineColor = settings.theme.gridLinesColor;
settings.gridBgColor = settings.theme.backgroundColor;
NodeEditorResources._dot = settings.theme.xNodeDot;
NodeEditorResources._dotOuter = settings.theme.xNodeDotOuter;
NodeEditorResources._nodeBody = settings.theme.xNodeNode;
NodeEditorResources._nodeHighlight = settings.theme.xNodeNodeHighlight;
}
settings.themeResetButton = GUILayout.Button("Reset", new GUILayoutOption[] {GUILayout.Width(45)});
EditorGUILayout.EndHorizontal();
settings.tintColor = EditorGUILayout.ColorField("Tint", settings.tintColor);
settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor);
settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath);
settings.noodleThickness = EditorGUILayout.FloatField(new GUIContent("Noodle thickness", "Noodle Thickness of the node connections"), settings.noodleThickness);
settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke);
settings.gridLineColor = EditorGUILayout.ColorField("Grid Lines Color", settings.gridLineColor);
settings.gridBgColor = EditorGUILayout.ColorField("Grid Background Color", settings.gridBgColor);
if(GUI.changed) SavePrefs(key, settings);
EditorGUILayout.Space();
}
/// <summary> Load prefs if they exist. Create if they don't </summary>
private static Settings LoadPrefs() {
// Create settings if it doesn't exist
if (!EditorPrefs.HasKey(lastKey)) {
if (lastEditor != null) EditorPrefs.SetString(lastKey, JsonUtility.ToJson(lastEditor.GetDefaultPreferences()));
else EditorPrefs.SetString(lastKey, JsonUtility.ToJson(new Settings()));
}
return JsonUtility.FromJson<Settings>(EditorPrefs.GetString(lastKey));
}
/// <summary> Delete all prefs </summary>
public static void ResetPrefs() {
if (EditorPrefs.HasKey(lastKey)) EditorPrefs.DeleteKey(lastKey);
if (settings.ContainsKey(lastKey)) settings.Remove(lastKey);
typeColors = new Dictionary<Type, Color>();
VerifyLoaded();
NodeEditorWindow.RepaintAll();
}
/// <summary> Save preferences in EditorPrefs </summary>
private static void SavePrefs(string key, Settings settings) {
EditorPrefs.SetString(key, JsonUtility.ToJson(settings));
}
/// <summary> Check if we have loaded settings for given key. If not, load them </summary>
private static void VerifyLoaded() {
if (!settings.ContainsKey(lastKey)) settings.Add(lastKey, LoadPrefs());
}
/// <summary> Return color based on type </summary>
public static Color GetTypeColor(System.Type type) {
VerifyLoaded();
if (type == null) return Color.gray;
Color col;
if (!typeColors.TryGetValue(type, out col)) {
string typeName = type.PrettyName();
if (settings[lastKey].typeColors.ContainsKey(typeName)) typeColors.Add(type, settings[lastKey].typeColors[typeName]);
else {
#if UNITY_5_4_OR_NEWER
UnityEngine.Random.State oldState = UnityEngine.Random.state;
UnityEngine.Random.InitState(typeName.GetHashCode());
#else
int oldSeed = UnityEngine.Random.seed;
UnityEngine.Random.seed = typeName.GetHashCode();
#endif
col = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value);
typeColors.Add(type, col);
#if UNITY_5_4_OR_NEWER
UnityEngine.Random.state = oldState;
#else
UnityEngine.Random.seed = oldSeed;
#endif
}
}
return col;
}
}
}

View File

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

View File

@ -1,180 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace XNodeEditor {
/// <summary> Contains reflection-related extensions built for xNode </summary>
public static class NodeEditorReflection {
[NonSerialized] private static Dictionary<Type, Color> nodeTint;
[NonSerialized] private static Dictionary<Type, int> nodeWidth;
/// <summary> All available node types </summary>
public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } }
[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>
public static Func<bool> GetIsDockedDelegate(this EditorWindow window) {
BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true);
return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod);
}
public static Type[] GetNodeTypes() {
//Get all classes deriving from Node via reflection
return GetDerivedTypes(typeof(XNode.Node));
}
/// <summary> Custom node tint colors defined with [NodeColor(r, g, b)] </summary>
public static bool TryGetAttributeTint(this Type nodeType, out Color tint) {
if (nodeTint == null) {
CacheAttributes<Color, XNode.Node.NodeTintAttribute>(ref nodeTint, x => x.color);
}
return nodeTint.TryGetValue(nodeType, out tint);
}
/// <summary> Get custom node widths defined with [NodeWidth(width)] </summary>
public static bool TryGetAttributeWidth(this Type nodeType, out int width) {
if (nodeWidth == null) {
CacheAttributes<int, XNode.Node.NodeWidthAttribute>(ref nodeWidth, x => x.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 {
dict = new Dictionary<Type, V>();
for (int i = 0; i < nodeTypes.Length; i++) {
object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true);
if (attribs == null || attribs.Length == 0) continue;
A attrib = attribs[0] as A;
dict.Add(nodeTypes[i], getter(attrib));
}
}
/// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary>
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.
FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// 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);
return field;
}
/// <summary> Get all classes deriving from baseType via reflection </summary>
public static Type[] GetDerivedTypes(this Type baseType) {
List<System.Type> types = new List<System.Type>();
System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies) {
try {
types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
} catch (ReflectionTypeLoadException) { }
}
return types.ToArray();
}
/// <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) {
KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj);
if (items.Length != 0) {
contextMenu.AddSeparator("");
List<string> invalidatedEntries = new List<string>();
foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) {
if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) {
invalidatedEntries.Add(checkValidate.Key.menuItem);
}
}
for (int i = 0; i < items.Length; i++) {
KeyValuePair<ContextMenu, MethodInfo> kvp = items[i];
if (invalidatedEntries.Contains(kvp.Key.menuItem)) {
contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem));
} else {
contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null));
}
}
}
}
/// <summary> Call OnValidate on target </summary>
public static void TriggerOnValidate(this UnityEngine.Object target) {
System.Reflection.MethodInfo onValidate = null;
if (target != null) {
onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (onValidate != null) onValidate.Invoke(target, null);
}
}
public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) {
Type type = obj.GetType();
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
List<KeyValuePair<ContextMenu, MethodInfo>> kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>();
for (int i = 0; i < methods.Length; i++) {
ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray();
if (attribs == null || attribs.Length == 0) continue;
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.");
continue;
}
if (methods[i].IsStatic) {
Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " is static and cannot be used for context menu commands.");
continue;
}
for (int k = 0; k < attribs.Length; k++) {
kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i]));
}
}
#if UNITY_5_5_OR_NEWER
//Sort menu items
kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority));
#endif
return kvp.ToArray();
}
/// <summary> Very crude. Uses a lot of reflection. </summary>
public static void OpenPreferences() {
try {
#if UNITY_2018_3_OR_NEWER
SettingsService.OpenUserPreferences("Preferences/Node Editor");
#else
//Open preferences window
Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow));
Type type = assembly.GetType("UnityEditor.PreferencesWindow");
type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
//Get the window
EditorWindow window = EditorWindow.GetWindow(type);
//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);
if ((bool) refreshField.GetValue(window)) {
type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null);
refreshField.SetValue(window, false);
}
//Get sections
FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic);
IList sections = sectionsField.GetValue(window) as IList;
//Iterate through sections and check contents
Type sectionType = sectionsField.FieldType.GetGenericArguments() [0];
FieldInfo sectionContentField = sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public);
for (int i = 0; i < sections.Count; i++) {
GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent;
if (sectionContent.text == "Node Editor") {
//Found contents - Set index
FieldInfo sectionIndexField = type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic);
sectionIndexField.SetValue(window, i);
return;
}
}
#endif
} catch (Exception 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.");
}
}
}
}

View File

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

View File

@ -1,113 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace XNodeEditor {
public static class NodeEditorResources {
// Textures
public static Texture2D dot { get { return _dot != null ? _dot : _dot = NodeEditorPreferences.GetSettings().theme.xNodeDot; }}
public static Texture2D _dot;
public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = NodeEditorPreferences.GetSettings().theme.xNodeDotOuter; }}
public static Texture2D _dotOuter;
public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = NodeEditorPreferences.GetSettings().theme.xNodeNode; }}
public static Texture2D _nodeBody;
public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = NodeEditorPreferences.GetSettings().theme.xNodeNodeHighlight; }}
public static Texture2D _nodeHighlight;
// Styles
public static Styles styles { get { return _styles = new Styles(); } }
public static Styles _styles = null;
public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } }
public class Styles {
public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
public Styles() {
GUIStyle baseStyle = new GUIStyle("Label");
baseStyle.fixedHeight = 18;
inputPort = new GUIStyle(baseStyle);
inputPort.alignment = TextAnchor.UpperLeft;
inputPort.padding.left = 0;
if(!NodeEditorPreferences.GetSettings().theme.makeTheDotOuterInfrontOfFill)
{
inputPort.active.background = dot;
inputPort.normal.background = dotOuter;
}
else
{
inputPort.normal.background = dot;
inputPort.active.background = dotOuter;
}
outputPort = new GUIStyle(baseStyle);
outputPort.alignment = TextAnchor.UpperRight;
outputPort.padding.right = 0;
if(!NodeEditorPreferences.GetSettings().theme.makeTheDotOuterInfrontOfFill)
{
outputPort.active.background = dot;
outputPort.normal.background = dotOuter;
}
else
{
outputPort.normal.background = dot;
outputPort.active.background = dotOuter;
}
nodeHeader = new GUIStyle();
nodeHeader.alignment = TextAnchor.MiddleCenter;
nodeHeader.fontStyle = NodeEditorPreferences.GetSettings().theme.headerFontStyle;
nodeHeader.normal.textColor = NodeEditorPreferences.GetSettings().theme.headerColor;
nodeHeader.font = NodeEditorPreferences.GetSettings().theme.headerFont;
nodeHeader.fontSize = NodeEditorPreferences.GetSettings().theme.headerFontSize;
nodeBody = new GUIStyle();
nodeBody.normal.background = NodeEditorResources.nodeBody;
nodeBody.border = new RectOffset(32, 32, 32, 32);
nodeBody.padding = NodeEditorPreferences.GetSettings().theme.padding;
nodeHighlight = new GUIStyle();
nodeHighlight.normal.background = NodeEditorResources.nodeHighlight;
nodeHighlight.border = new RectOffset(32, 32, 32, 32);
tooltip = new GUIStyle("helpBox");
tooltip.alignment = TextAnchor.MiddleCenter;
}
}
public static Texture2D GenerateGridTexture(Color line, Color bg) {
Texture2D tex = new Texture2D(64, 64);
Color[] cols = new Color[64 * 64];
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) {
Color col = bg;
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);
cols[(y * 64) + x] = col;
}
}
tex.SetPixels(cols);
tex.wrapMode = TextureWrapMode.Repeat;
tex.filterMode = FilterMode.Bilinear;
tex.name = "Grid";
tex.Apply();
return tex;
}
public static Texture2D GenerateCrossTexture(Color line) {
Texture2D tex = new Texture2D(64, 64);
Color[] cols = new Color[64 * 64];
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) {
Color col = line;
if (y != 31 && x != 31) col.a = 0;
cols[(y * 64) + x] = col;
}
}
tex.SetPixels(cols);
tex.wrapMode = TextureWrapMode.Clamp;
tex.filterMode = FilterMode.Bilinear;
tex.name = "Grid";
tex.Apply();
return tex;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,273 +0,0 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace XNodeEditor {
/// <summary> Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. </summary>
[CustomNodeGraphEditor(typeof(XNode.NodeGraph))]
public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph> {
[Obsolete("Use window.position instead")]
public Rect position { get { return window.position; } set { window.position = value; } }
/// <summary> Are we currently renaming a node? </summary>
protected bool isRenaming;
public virtual void OnGUI() { }
/// <summary> Called when opened by NodeEditorWindow </summary>
public virtual void OnOpen() { }
/// <summary> Called when NodeEditorWindow gains focus </summary>
public virtual void OnWindowFocus() { }
/// <summary> Called when NodeEditorWindow loses focus </summary>
public virtual void OnWindowFocusLost() { }
public virtual Texture2D GetGridTexture() {
return NodeEditorPreferences.GetSettings().gridTexture;
}
public virtual Texture2D GetSecondaryGridTexture() {
return NodeEditorPreferences.GetSettings().crossTexture;
}
/// <summary> Return default settings for this graph type. This is the settings the user will load if no previous settings have been saved. </summary>
public virtual NodeEditorPreferences.Settings GetDefaultPreferences() {
return new NodeEditorPreferences.Settings();
}
/// <summary> Returns context node menu path. Null or empty strings for hidden nodes. </summary>
public virtual string GetNodeMenuName(Type type) {
//Check if type has the CreateNodeMenuAttribute
XNode.Node.CreateNodeMenuAttribute attrib;
if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
return attrib.menuName;
else // Return generated path
return NodeEditorUtilities.NodeDefaultPath(type);
}
/// <summary> The order by which the menu items are displayed. </summary>
public virtual int GetNodeMenuOrder(Type type) {
//Check if type has the CreateNodeMenuAttribute
XNode.Node.CreateNodeMenuAttribute attrib;
if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
return attrib.order;
else
return 0;
}
/// <summary>
/// Add items for the context menu when right-clicking this node.
/// Override to add custom menu items.
/// </summary>
/// <param name="menu"></param>
/// <param name="compatibleType">Use it to filter only nodes with ports value type, compatible with this type</param>
/// <param name="direction">Direction of the compatiblity</param>
public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition);
Type[] nodeTypes;
if (compatibleType != null && NodeEditorPreferences.GetSettings().createFilter) {
nodeTypes = NodeEditorUtilities.GetCompatibleNodesTypes(NodeEditorReflection.nodeTypes, compatibleType, direction).OrderBy(GetNodeMenuOrder).ToArray();
} else {
nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(GetNodeMenuOrder).ToArray();
}
for (int i = 0; i < nodeTypes.Length; i++) {
Type type = nodeTypes[i];
//Get node context menu path
string path = GetNodeMenuName(type);
if (string.IsNullOrEmpty(path)) continue;
// Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
bool disallowed = false;
if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) {
int typeCount = target.nodes.Count(x => x.GetType() == type);
if (typeCount >= disallowAttrib.max) disallowed = true;
}
// Add node entry to context menu
if (disallowed) menu.AddItem(new GUIContent(path), false, null);
else menu.AddItem(new GUIContent(path), false, () => {
XNode.Node node = CreateNode(type, pos);
NodeEditorWindow.current.AutoConnect(node);
});
}
menu.AddSeparator("");
if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos));
else menu.AddDisabledItem(new GUIContent("Paste"));
menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences());
menu.AddCustomContextMenuItems(target);
}
/// <summary> Returned gradient is used to color noodles </summary>
/// <param name="output"> The output this noodle comes from. Never null. </param>
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) {
Gradient grad = new Gradient();
// If dragging the noodle, draw solid, slightly transparent
if (input == null) {
Color a = GetTypeColor(output.ValueType);
grad.SetKeys(
new GradientColorKey[] { new GradientColorKey(a, 0f) },
new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) }
);
}
// If normal, draw gradient fading from one input color to the other
else {
Color a = GetTypeColor(output.ValueType);
Color b = GetTypeColor(input.ValueType);
// If any port is hovered, tint white
if (window.hoveredPort == output || window.hoveredPort == input) {
a = Color.Lerp(a, Color.white, 0.8f);
b = Color.Lerp(b, Color.white, 0.8f);
}
grad.SetKeys(
new GradientColorKey[] { new GradientColorKey(a, 0f), new GradientColorKey(b, 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }
);
}
return grad;
}
/// <summary> Returned float is used for noodle thickness </summary>
/// <param name="output"> The output this noodle comes from. Never null. </param>
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) {
return NodeEditorPreferences.GetSettings().noodleThickness;
}
public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) {
return NodeEditorPreferences.GetSettings().noodlePath;
}
public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) {
return NodeEditorPreferences.GetSettings().noodleStroke;
}
/// <summary> Returned color is used to color ports </summary>
public virtual Color GetPortColor(XNode.NodePort port) {
return GetTypeColor(port.ValueType);
}
/// <summary>
/// The returned Style is used to configure the paddings and icon texture of the ports.
/// Use these properties to customize your port style.
///
/// The properties used is:
/// <see cref="GUIStyle.padding"/>[Left and Right], <see cref="GUIStyle.normal"/> [Background] = border texture,
/// and <seealso cref="GUIStyle.active"/> [Background] = dot texture;
/// </summary>
/// <param name="port">the owner of the style</param>
/// <returns></returns>
public virtual GUIStyle GetPortStyle(XNode.NodePort port) {
if (port.direction == XNode.NodePort.IO.Input)
return NodeEditorResources.styles.inputPort;
return NodeEditorResources.styles.outputPort;
}
/// <summary> The returned color is used to color the background of the door.
/// Usually used for outer edge effect </summary>
public virtual Color GetPortBackgroundColor(XNode.NodePort port) {
return Color.gray;
}
/// <summary> Returns generated color for a type. This color is editable in preferences </summary>
public virtual Color GetTypeColor(Type type) {
return NodeEditorPreferences.GetTypeColor(type);
}
/// <summary> Override to display custom tooltips </summary>
public virtual string GetPortTooltip(XNode.NodePort port) {
Type portType = port.ValueType;
string tooltip = "";
tooltip = portType.PrettyName();
if (port.IsOutput) {
object obj = port.node.GetValue(port);
tooltip += " = " + (obj != null ? obj.ToString() : "null");
}
return tooltip;
}
/// <summary> Deal with objects dropped into the graph through DragAndDrop </summary>
public virtual void OnDropObjects(UnityEngine.Object[] objects) {
if (GetType() != typeof(NodeGraphEditor)) Debug.Log("No OnDropObjects override defined for " + GetType());
}
/// <summary> Create a node and save it in the graph asset </summary>
public virtual XNode.Node CreateNode(Type type, Vector2 position) {
Undo.RecordObject(target, "Create Node");
XNode.Node node = target.AddNode(type);
Undo.RegisterCreatedObjectUndo(node, "Create Node");
node.position = position;
if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
NodeEditorWindow.RepaintAll();
return node;
}
/// <summary> Creates a copy of the original node in the graph </summary>
public virtual XNode.Node CopyNode(XNode.Node original) {
Undo.RecordObject(target, "Duplicate Node");
XNode.Node node = target.CopyNode(original);
Undo.RegisterCreatedObjectUndo(node, "Duplicate Node");
node.name = original.name;
AssetDatabase.AddObjectToAsset(node, target);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
return node;
}
/// <summary> Return false for nodes that can't be removed </summary>
public virtual bool CanRemove(XNode.Node node) {
// Check graph attributes to see if this node is required
Type graphType = target.GetType();
XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute);
if (attribs.Any(x => x.Requires(node.GetType()))) {
if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) {
return false;
}
}
return true;
}
/// <summary> Safely remove a node and all its connections. </summary>
public virtual void RemoveNode(XNode.Node node) {
if (!CanRemove(node)) return;
// Remove the node
Undo.RecordObject(node, "Delete Node");
Undo.RecordObject(target, "Delete Node");
foreach (var port in node.Ports)
foreach (var conn in port.GetConnections())
Undo.RecordObject(conn.node, "Delete Node");
target.RemoveNode(node);
Undo.DestroyObjectImmediate(node);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeGraphEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph>.INodeEditorAttrib {
private Type inspectedType;
public string editorPrefsKey;
/// <summary> Tells a NodeGraphEditor which Graph type it is an editor for </summary>
/// <param name="inspectedType">Type that this editor can edit</param>
/// <param name="editorPrefsKey">Define unique key for unique layout settings instance</param>
public CustomNodeGraphEditorAttribute(Type inspectedType, string editorPrefsKey = "xNode.Settings") {
this.inspectedType = inspectedType;
this.editorPrefsKey = editorPrefsKey;
}
public Type GetInspectedType() {
return inspectedType;
}
}
}
}

View File

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

View File

@ -1,45 +0,0 @@
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
using XNode;
namespace XNodeEditor {
/// <summary> Deals with modified assets </summary>
class NodeGraphImporter : AssetPostprocessor {
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
foreach (string path in importedAssets) {
// Skip processing anything without the .asset extension
if (Path.GetExtension(path) != ".asset") continue;
// Get the object that is requested for deletion
NodeGraph graph = AssetDatabase.LoadAssetAtPath<NodeGraph>(path);
if (graph == null) continue;
// Get attributes
Type graphType = graph.GetType();
NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute);
Vector2 position = Vector2.zero;
foreach (NodeGraph.RequireNodeAttribute attrib in attribs) {
if (attrib.type0 != null) AddRequired(graph, attrib.type0, ref position);
if (attrib.type1 != null) AddRequired(graph, attrib.type1, ref position);
if (attrib.type2 != null) AddRequired(graph, attrib.type2, ref position);
}
}
}
private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) {
if (!graph.nodes.Any(x => x.GetType() == type)) {
XNode.Node node = graph.AddNode(type);
node.position = position;
position.x += 200;
if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(graph))) AssetDatabase.AddObjectToAsset(node, graph);
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7a816f2790bf3da48a2d6d0035ebc9a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,83 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace XNodeEditor {
/// <summary> Utility for renaming assets </summary>
public class RenamePopup : EditorWindow {
private const string inputControlName = "nameInput";
public static RenamePopup current { get; private set; }
public Object target;
public string input;
private bool firstFrame = true;
/// <summary> Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply.
public static RenamePopup Show(Object target, float width = 200) {
RenamePopup window = EditorWindow.GetWindow<RenamePopup>(true, "Rename " + target.name, true);
if (current != null) current.Close();
current = window;
window.target = target;
window.input = target.name;
window.minSize = new Vector2(100, 44);
window.position = new Rect(0, 0, width, 44);
window.UpdatePositionToMouse();
return window;
}
private void UpdatePositionToMouse() {
if (Event.current == null) return;
Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
Rect pos = position;
pos.x = mousePoint.x - position.width * 0.5f;
pos.y = mousePoint.y - 10;
position = pos;
}
private void OnLostFocus() {
// Make the popup close on lose focus
Close();
}
private void OnGUI() {
if (firstFrame) {
UpdatePositionToMouse();
firstFrame = false;
}
GUI.SetNextControlName(inputControlName);
input = EditorGUILayout.TextField(input);
EditorGUI.FocusTextInControl(inputControlName);
Event e = Event.current;
// If input is empty, revert name to default instead
if (input == null || input.Trim() == "") {
if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) {
target.name = NodeEditorUtilities.NodeDefaultName(target.GetType());
NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename();
AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target));
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
Close();
target.TriggerOnValidate();
}
}
// Rename asset to input text
else {
if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) {
target.name = input;
NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename();
AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target));
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
Close();
target.TriggerOnValidate();
}
}
if (e.isKey && e.keyCode == KeyCode.Escape) {
Close();
}
}
private void OnDestroy() {
EditorGUIUtility.editingTextField = false;
}
}
}

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 4ef3ddc25518318469bce838980c64be
timeCreated: 1552067957
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 964fc201163fe884ca6a20094b6f3b49
folderAsset: yes
timeCreated: 1506110871
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,35 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 64467354e9a472d49b23559c7a85c9fb, type: 3}
m_Name: DefualtXNodeTheme
m_EditorClassIdentifier:
tint: {r: 0.3529412, g: 0.3803922, b: 0.41176474, a: 255}
selection: {r: 255, g: 255, b: 255, a: 255}
noodlePath: 0
noodleThickness: 2
noodleStroke: 0
makeTheDotOuterInfrontOfFill: 0
gridLinesColor: {r: 0.23137257, g: 0.23137257, b: 0.23137257, a: 255}
backgroundColor: {r: 0.18823531, g: 0.18823531, b: 0.18823531, a: 255}
xNodeDot: {fileID: 2800000, guid: 75a1fe0b102226a418486ed823c9a7fb, type: 3}
xNodeDotOuter: {fileID: 2800000, guid: 434ca8b4bdfa5574abb0002bbc9b65ad, type: 3}
xNodeNode: {fileID: 2800000, guid: 2fea1dcb24935ef4ca514d534eb6aa3d, type: 3}
xNodeNodeHighlight: {fileID: 2800000, guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5, type: 3}
headerFont: {fileID: 0}
headerFontStyle: 1
headerColor: {r: 1, g: 1, b: 1, a: 1}
headerFontSize: 13
padding:
m_Left: 16
m_Right: 16
m_Top: 4
m_Bottom: 16

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 6b1133905b1380d45a4f5512f874a7f6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,163 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!181963792 &2655988077585873504
Preset:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: DefualtXNodeTheme
m_TargetType:
m_NativeTypeID: 114
m_ManagedTypePPtr: {fileID: 11500000, guid: 64467354e9a472d49b23559c7a85c9fb, type: 3}
m_ManagedTypeFallback:
m_Properties:
- target: {fileID: 0}
propertyPath: m_Enabled
value: 1
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: m_EditorHideFlags
value: 0
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: m_EditorClassIdentifier
value:
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: tint.r
value: 0.3529412
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: tint.g
value: 0.3803922
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: tint.b
value: 0.41176474
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: tint.a
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: selection.r
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: selection.g
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: selection.b
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: selection.a
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: noodlePath
value: 0
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: noodleThickness
value: 2
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: noodleStroke
value: 0
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: gridLinesColor.r
value: 0.23137257
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: gridLinesColor.g
value: 0.23137257
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: gridLinesColor.b
value: 0.23137257
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: gridLinesColor.a
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: backgroundColor.r
value: 0.18823531
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: backgroundColor.g
value: 0.18823531
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: backgroundColor.b
value: 0.18823531
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: backgroundColor.a
value: 255
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: xNodeDot
value:
objectReference: {fileID: 2800000, guid: 75a1fe0b102226a418486ed823c9a7fb, type: 3}
- target: {fileID: 0}
propertyPath: xNodeDotOuter
value:
objectReference: {fileID: 2800000, guid: 434ca8b4bdfa5574abb0002bbc9b65ad, type: 3}
- target: {fileID: 0}
propertyPath: xNodeNode
value:
objectReference: {fileID: 2800000, guid: 2fea1dcb24935ef4ca514d534eb6aa3d, type: 3}
- target: {fileID: 0}
propertyPath: xNodeNodeHighlight
value:
objectReference: {fileID: 2800000, guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5, type: 3}
- target: {fileID: 0}
propertyPath: headerFont
value:
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: headerFontStyle
value: 1
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: headerColor.r
value: 1
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: headerColor.g
value: 1
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: headerColor.b
value: 1
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: headerColor.a
value: 1
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: headerFontSize
value: 13
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: padding.m_Left
value: 16
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: padding.m_Right
value: 16
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: padding.m_Top
value: 4
objectReference: {fileID: 0}
- target: {fileID: 0}
propertyPath: padding.m_Bottom
value: 16
objectReference: {fileID: 0}
m_ExcludedProperties: []

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 90c2de33a7495bf4491ed611bb7ec018
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2655988077585873504
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 86b677955452bb5449f9f4dd47b6ddfe
folderAsset: yes
timeCreated: 1519049391
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XNode;
[CreateAssetMenu]
public class #SCRIPTNAME# : NodeGraph {
#NOTRIM#
}

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 8165767f64da7d94e925f61a38da668c
timeCreated: 1519049802
licenseType: Free
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,18 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XNode;
public class #SCRIPTNAME# : Node {
// Use this for initialization
protected override void Init() {
base.Init();
#NOTRIM#
}
// Return the correct value of an output port when requested
public override object GetValue(NodePort port) {
return null; // Replace this
}
}

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 85f6f570600a1a44d8e734cb111a8b89
timeCreated: 1519049802
licenseType: Free
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 75a1fe0b102226a418486ed823c9a7fb
timeCreated: 1506110357
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: -1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 434ca8b4bdfa5574abb0002bbc9b65ad
timeCreated: 1506110357
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: -1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 2fea1dcb24935ef4ca514d534eb6aa3d
timeCreated: 1507454532
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,87 +0,0 @@
fileFormatVersion: 2
guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5
timeCreated: 1516610730
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 2267efa6e1e349348ae0b28fb659a6e2
timeCreated: 1507454532
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: -1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,78 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using XNode;
namespace XNodeEditor {
[CustomEditor(typeof(SceneGraph), true)]
public class SceneGraphEditor : Editor {
private SceneGraph sceneGraph;
private bool removeSafely;
private Type graphType;
public override void OnInspectorGUI() {
if (sceneGraph.graph == null) {
if (GUILayout.Button("New graph", GUILayout.Height(40))) {
if (graphType == null) {
Type[] graphTypes = NodeEditorReflection.GetDerivedTypes(typeof(NodeGraph));
GenericMenu menu = new GenericMenu();
for (int i = 0; i < graphTypes.Length; i++) {
Type graphType = graphTypes[i];
menu.AddItem(new GUIContent(graphType.Name), false, () => CreateGraph(graphType));
}
menu.ShowAsContext();
} else {
CreateGraph(graphType);
}
}
} else {
if (GUILayout.Button("Open graph", GUILayout.Height(40))) {
NodeEditorWindow.Open(sceneGraph.graph);
}
if (removeSafely) {
GUILayout.BeginHorizontal();
GUILayout.Label("Really remove graph?");
GUI.color = new Color(1, 0.8f, 0.8f);
if (GUILayout.Button("Remove")) {
removeSafely = false;
Undo.RecordObject(sceneGraph, "Removed graph");
sceneGraph.graph = null;
}
GUI.color = Color.white;
if (GUILayout.Button("Cancel")) {
removeSafely = false;
}
GUILayout.EndHorizontal();
} else {
GUI.color = new Color(1, 0.8f, 0.8f);
if (GUILayout.Button("Remove graph")) {
removeSafely = true;
}
GUI.color = Color.white;
}
}
DrawDefaultInspector();
}
private void OnEnable() {
sceneGraph = target as SceneGraph;
Type sceneGraphType = sceneGraph.GetType();
if (sceneGraphType == typeof(SceneGraph)) {
graphType = null;
} else {
Type baseType = sceneGraphType.BaseType;
if (baseType.IsGenericType) {
graphType = sceneGraphType = baseType.GetGenericArguments() [0];
}
}
}
public void CreateGraph(Type type) {
Undo.RecordObject(sceneGraph, "Create graph");
sceneGraph.graph = ScriptableObject.CreateInstance(type) as NodeGraph;
sceneGraph.graph.name = sceneGraph.name + "-graph";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: aea725adabc311f44b5ea8161360a915
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,34 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace XNode {
[System.Serializable]
public class Theme : ScriptableObject
{
[Header("Node Settings")]
public Color tint = new Color (90, 97, 105, 255);
public Color selection = new Color (255, 255, 255, 255);
public XNodeEditor.NoodlePath noodlePath = XNodeEditor.NoodlePath.Curvy;
public float noodleThickness = 2;
public XNodeEditor.NoodleStroke noodleStroke = XNodeEditor.NoodleStroke.Full;
[Tooltip("makes the dot outer switch colors with the dot, as well as it makes the dot outer infrot of the dot")]public bool makeTheDotOuterInfrontOfFill = false;
[Header("Graph Settings")]
public Color gridLinesColor = new Color (59, 59, 59, 255);
public Color backgroundColor = new Color (48, 48, 48, 255);
[Header("Node Pictures")]
[Tooltip("an xNode dot picture that has dimensions that relates to 16x16")] public Texture2D xNodeDot;
[Tooltip("an xNode dot outer picture that has dimensions that relates to 16x16")] public Texture2D xNodeDotOuter;
[Tooltip("an xNode node picture that has dimensions that relates to 64x64")] public Texture2D xNodeNode;
[Tooltip("an xNode node highlight picture that has dimensions that relates to 64x64")] public Texture2D xNodeNodeHighlight;
[Header("Node Header Settings")]
[Tooltip("if empty, xNode will use the defualt text")] public Font headerFont;
public FontStyle headerFontStyle = FontStyle.Bold;
public Color headerColor = Color.white;
public int headerFontSize = 13;
[Tooltip("you can adjust the padding to make the node gui content fit to the node picture")] public RectOffset padding;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 64467354e9a472d49b23559c7a85c9fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,17 +0,0 @@
{
"name": "XNodeEditor",
"references": [
"XNode"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
}

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 002c1bbed08fa44d282ef34fd5edb138
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,416 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace XNode {
/// <summary>
/// Base class for all nodes
/// </summary>
/// <example>
/// Classes extending this class will be considered as valid nodes by xNode.
/// <code>
/// [System.Serializable]
/// public class Adder : Node {
/// [Input] public float a;
/// [Input] public float b;
/// [Output] public float result;
///
/// // GetValue should be overridden to return a value for any specified output port
/// public override object GetValue(NodePort port) {
/// return a + b;
/// }
/// }
/// </code>
/// </example>
[Serializable]
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>
public enum ShowBackingValue {
/// <summary> Never show the backing value </summary>
Never,
/// <summary> Show the backing value only when the port does not have any active connections </summary>
Unconnected,
/// <summary> Always show the backing value </summary>
Always
}
public enum ConnectionType {
/// <summary> Allow multiple connections</summary>
Multiple,
/// <summary> always override the current connection </summary>
Override,
}
/// <summary> Tells which types of input to allow </summary>
public enum TypeConstraint {
/// <summary> Allow all types of input</summary>
None,
/// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary>
Inherited,
/// <summary> Allow only similar types </summary>
Strict,
/// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
InheritedInverse,
/// <summary> Allow connections where output value type is assignable from input value or input value type is assignable from output value type</summary>
InheritedAny
}
#region Obsolete
[Obsolete("Use DynamicPorts instead")]
public IEnumerable<NodePort> InstancePorts { get { return DynamicPorts; } }
[Obsolete("Use DynamicOutputs instead")]
public IEnumerable<NodePort> InstanceOutputs { get { return DynamicOutputs; } }
[Obsolete("Use DynamicInputs instead")]
public IEnumerable<NodePort> InstanceInputs { get { return DynamicInputs; } }
[Obsolete("Use AddDynamicInput instead")]
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);
}
[Obsolete("Use AddDynamicOutput instead")]
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);
}
[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) {
return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
}
[Obsolete("Use RemoveDynamicPort instead")]
public void RemoveInstancePort(string fieldName) {
RemoveDynamicPort(fieldName);
}
[Obsolete("Use RemoveDynamicPort instead")]
public void RemoveInstancePort(NodePort port) {
RemoveDynamicPort(port);
}
[Obsolete("Use ClearDynamicPorts instead")]
public void ClearInstancePorts() {
ClearDynamicPorts();
}
#endregion
/// <summary> Iterate over all ports on this node. </summary>
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
/// <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; } } }
/// <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; } } }
/// <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; } } }
/// <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; } } }
/// <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; } } }
/// <summary> Parent <see cref="NodeGraph"/> </summary>
[SerializeField] public NodeGraph graph;
/// <summary> Position on the <see cref="NodeGraph"/> </summary>
[SerializeField] public Vector2 position;
/// <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();
/// <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;
protected void OnEnable() {
if (graphHotfix != null) graph = graphHotfix;
graphHotfix = null;
UpdatePorts();
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>
public void UpdatePorts() {
NodeDataCache.UpdatePorts(this, ports);
}
/// <summary> Initialize node. Called on enable. </summary>
protected virtual void Init() { }
/// <summary> Checks all connections for invalid references, and removes them. </summary>
public void VerifyConnections() {
foreach (NodePort port in Ports) port.VerifyConnections();
}
#region Dynamic Ports
/// <summary> Convenience function. </summary>
/// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceOutput"/>
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);
}
/// <summary> Convenience function. </summary>
/// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceInput"/>
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);
}
/// <summary> Add a dynamic, serialized port to this node. </summary>
/// <seealso cref="AddDynamicInput"/>
/// <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) {
if (fieldName == null) {
fieldName = "dynamicInput_0";
int i = 0;
while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
} else if (HasPort(fieldName)) {
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
return ports[fieldName];
}
NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
ports.Add(fieldName, port);
return port;
}
/// <summary> Remove an dynamic port from the node </summary>
public void RemoveDynamicPort(string fieldName) {
NodePort dynamicPort = GetPort(fieldName);
if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
RemoveDynamicPort(GetPort(fieldName));
}
/// <summary> Remove an dynamic port from the node </summary>
public void RemoveDynamicPort(NodePort port) {
if (port == null) throw new ArgumentNullException("port");
else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
port.ClearConnections();
ports.Remove(port.fieldName);
}
/// <summary> Removes all dynamic ports from the node </summary>
[ContextMenu("Clear Dynamic Ports")]
public void ClearDynamicPorts() {
List<NodePort> dynamicPorts = new List<NodePort>(DynamicPorts);
foreach (NodePort port in dynamicPorts) {
RemoveDynamicPort(port);
}
}
#endregion
#region Ports
/// <summary> Returns output port which matches fieldName </summary>
public NodePort GetOutputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Output) return null;
else return port;
}
/// <summary> Returns input port which matches fieldName </summary>
public NodePort GetInputPort(string fieldName) {
NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Input) return null;
else return port;
}
/// <summary> Returns port which matches fieldName </summary>
public NodePort GetPort(string fieldName) {
NodePort port;
if (ports.TryGetValue(fieldName, out port)) return port;
else return null;
}
public bool HasPort(string fieldName) {
return ports.ContainsKey(fieldName);
}
#endregion
#region Inputs/Outputs
/// <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="fallback">If no ports are connected, this value will be returned</param>
public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValue<T>();
else return fallback;
}
/// <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="fallback">If no ports are connected, this value will be returned</param>
public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValues<T>();
else return fallback;
}
/// <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>
public virtual object GetValue(NodePort port) {
Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
return null;
}
#endregion
/// <summary> Called after a connection between two <see cref="NodePort"/>s is created </summary>
/// <param name="from">Output</param> <param name="to">Input</param>
public virtual void OnCreateConnection(NodePort from, NodePort to) { }
/// <summary> Called after a connection is removed from this port </summary>
/// <param name="port">Output or Input</param>
public virtual void OnRemoveConnection(NodePort port) { }
/// <summary> Disconnect everything from this node </summary>
public void ClearConnections() {
foreach (NodePort port in Ports) port.ClearConnections();
}
#region Attributes
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
[AttributeUsage(AttributeTargets.Field)]
public class InputAttribute : Attribute {
public ShowBackingValue backingValue;
public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")]
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList;
public TypeConstraint typeConstraint;
/// <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="connectionType">Should we allow multiple connections? </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>
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
}
}
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
[AttributeUsage(AttributeTargets.Field)]
public class OutputAttribute : Attribute {
public ShowBackingValue backingValue;
public ConnectionType connectionType;
[Obsolete("Use dynamicPortList instead")]
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList;
public TypeConstraint typeConstraint;
/// <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="connectionType">Should we allow multiple connections? </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>
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
}
/// <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="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>
[Obsolete("Use constructor with TypeConstraint")]
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>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CreateNodeMenuAttribute : Attribute {
public string menuName;
public int order;
/// <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>
public CreateNodeMenuAttribute(string menuName) {
this.menuName = menuName;
this.order = 0;
}
/// <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="order"> The order by which the menu items are displayed. </param>
public CreateNodeMenuAttribute(string menuName, int order) {
this.menuName = menuName;
this.order = order;
}
}
/// <summary> Prevents Node of the same type to be added more than once (configurable) to a NodeGraph </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DisallowMultipleNodesAttribute : Attribute {
// 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.
public int max;
/// <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>
public DisallowMultipleNodesAttribute(int max = 1) {
this.max = max;
}
}
/// <summary> Specify a color for this node type </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeTintAttribute : Attribute {
public Color color;
/// <summary> Specify a color for this node type </summary>
/// <param name="r"> Red [0.0f .. 1.0f] </param>
/// <param name="g"> Green [0.0f .. 1.0f] </param>
/// <param name="b"> Blue [0.0f .. 1.0f] </param>
public NodeTintAttribute(float r, float g, float b) {
color = new Color(r, g, b);
}
/// <summary> Specify a color for this node type </summary>
/// <param name="hex"> HEX color value </param>
public NodeTintAttribute(string hex) {
ColorUtility.TryParseHtmlString(hex, out color);
}
/// <summary> Specify a color for this node type </summary>
/// <param name="r"> Red [0 .. 255] </param>
/// <param name="g"> Green [0 .. 255] </param>
/// <param name="b"> Blue [0 .. 255] </param>
public NodeTintAttribute(byte r, byte g, byte b) {
color = new Color32(r, g, b, byte.MaxValue);
}
}
/// <summary> Specify a width for this node type </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NodeWidthAttribute : Attribute {
public int width;
/// <summary> Specify a width for this node type </summary>
/// <param name="width"> Width </param>
public NodeWidthAttribute(int width) {
this.width = width;
}
}
#endregion
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
[SerializeField] private List<string> keys = new List<string>();
[SerializeField] private List<NodePort> values = new List<NodePort>();
public void OnBeforeSerialize() {
keys.Clear();
values.Clear();
foreach (KeyValuePair<string, NodePort> pair in this) {
keys.Add(pair.Key);
values.Add(pair.Value);
}
}
public void OnAfterDeserialize() {
this.Clear();
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.");
for (int i = 0; i < keys.Count; i++)
this.Add(keys[i], values[i]);
}
}
}
}

View File

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

View File

@ -1,236 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace XNode {
/// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
public static class NodeDataCache {
private static PortDataCache portDataCache;
private static Dictionary<System.Type, Dictionary<string, string>> formerlySerializedAsCache;
private static bool Initialized { get { return portDataCache != null; } }
/// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary>
public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) {
if (!Initialized) BuildCache();
Dictionary<string, NodePort> staticPorts = new Dictionary<string, NodePort>();
Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>();
System.Type nodeType = node.GetType();
Dictionary<string, string> formerlySerializedAs = null;
if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs);
List<NodePort> dynamicListPorts = new List<NodePort>();
List<NodePort> typePortCache;
if (portDataCache.TryGetValue(nodeType, out typePortCache)) {
for (int i = 0; i < typePortCache.Count; i++) {
staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]);
}
}
// Cleanup port dict - Remove nonexisting static ports - update static port types
// AND update dynamic ports (albeit only those in lists) too, in order to enforce proper serialisation.
// Loop through current node ports
foreach (NodePort port in ports.Values.ToList()) {
// If port still exists, check it it has been changed
NodePort staticPort;
if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
// If port exists but with wrong settings, remove it. Re-add it later.
if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
// If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections.
if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
port.ClearConnections();
ports.Remove(port.fieldName);
} else port.ValueType = staticPort.ValueType;
}
// If port doesn't exist anymore, remove it
else if (port.IsStatic) {
//See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts
// so it can be reconnected in missing ports stage.
string newName = null;
if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections());
port.ClearConnections();
ports.Remove(port.fieldName);
}
// If the port is dynamic and is managed by a dynamic port list, flag it for reference updates
else if (IsDynamicListPort(port)) {
dynamicListPorts.Add(port);
}
}
// Add missing ports
foreach (NodePort staticPort in staticPorts.Values) {
if (!ports.ContainsKey(staticPort.fieldName)) {
NodePort port = new NodePort(staticPort, node);
//If we just removed the port, try re-adding the connections
List<NodePort> reconnectConnections;
if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) {
for (int i = 0; i < reconnectConnections.Count; i++) {
NodePort connection = reconnectConnections[i];
if (connection == null) continue;
if (port.CanConnectTo(connection)) port.Connect(connection);
}
}
ports.Add(staticPort.fieldName, port);
}
}
// Finally, make sure dynamic list port settings correspond to the settings of their "backing port"
foreach (NodePort listPort in dynamicListPorts) {
// At this point we know that ports here are dynamic list ports
// which have passed name/"backing port" checks, ergo we can proceed more safely.
string backingPortName = listPort.fieldName.Split(' ')[0];
NodePort backingPort = staticPorts[backingPortName];
// Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
listPort.ValueType = GetBackingValueType(backingPort.ValueType);
listPort.direction = backingPort.direction;
listPort.connectionType = backingPort.connectionType;
listPort.typeConstraint = backingPort.typeConstraint;
}
}
/// <summary>
/// Extracts the underlying types from arrays and lists, the only collections for dynamic port lists
/// currently supported. If the given type is not applicable (i.e. if the dynamic list port was not
/// defined as an array or a list), returns the given type itself.
/// </summary>
private static System.Type GetBackingValueType(System.Type portValType) {
if (portValType.HasElementType) {
return portValType.GetElementType();
}
if (portValType.IsGenericType && portValType.GetGenericTypeDefinition() == typeof(List<>)) {
return portValType.GetGenericArguments()[0];
}
return portValType;
}
/// <summary>Returns true if the given port is in a dynamic port list.</summary>
private static bool IsDynamicListPort(NodePort port) {
// Ports flagged as "dynamicPortList = true" end up having a "backing port" and a name with an index, but we have
// no guarantee that a dynamic port called "output 0" is an element in a list backed by a static "output" port.
// Thus, we need to check for attributes... (but at least we don't need to look at all fields this time)
string[] fieldNameParts = port.fieldName.Split(' ');
if (fieldNameParts.Length != 2) return false;
FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]);
if (backingPortInfo == null) return false;
object[] attribs = backingPortInfo.GetCustomAttributes(true);
return attribs.Any(x => {
Node.InputAttribute inputAttribute = x as Node.InputAttribute;
Node.OutputAttribute outputAttribute = x as Node.OutputAttribute;
return inputAttribute != null && inputAttribute.dynamicPortList ||
outputAttribute != null && outputAttribute.dynamicPortList;
});
}
/// <summary> Cache node types </summary>
private static void BuildCache() {
portDataCache = new PortDataCache();
System.Type baseType = typeof(Node);
List<System.Type> nodeTypes = new List<System.Type>();
System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
// Loop through assemblies and add node types to list
foreach (Assembly assembly in assemblies) {
// Skip certain dlls to improve performance
string assemblyName = assembly.GetName().Name;
int index = assemblyName.IndexOf('.');
if (index != -1) assemblyName = assemblyName.Substring(0, index);
switch (assemblyName) {
// The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped
case "UnityEditor":
case "UnityEngine":
case "System":
case "mscorlib":
case "Microsoft":
continue;
default:
nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
break;
}
}
for (int i = 0; i < nodeTypes.Count; i++) {
CachePorts(nodeTypes[i]);
}
}
public static List<FieldInfo> GetNodeFields(System.Type nodeType) {
List<System.Reflection.FieldInfo> fieldInfo = new List<System.Reflection.FieldInfo>(nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
// GetFields doesnt return inherited private fields, so walk through base types and pick those up
System.Type tempType = nodeType;
while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < parentFields.Length; i++) {
// Ensure that we do not already have a member with this type and name
FieldInfo parentField = parentFields[i];
if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) {
fieldInfo.Add(parentField);
}
}
}
return fieldInfo;
}
private static void CachePorts(System.Type nodeType) {
List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType);
for (int i = 0; i < fieldInfo.Count; i++) {
//Get InputAttribute and OutputAttribute
object[] attribs = fieldInfo[i].GetCustomAttributes(true);
Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute;
if (inputAttrib == null && outputAttrib == null) continue;
if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
else {
if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
}
if(formerlySerializedAsAttribute != null) {
if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary<System.Type, Dictionary<string, string>>();
if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary<string, string>());
if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node.");
else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name);
}
}
}
[System.Serializable]
private class PortDataCache : Dictionary<System.Type, List<NodePort>>, ISerializationCallbackReceiver {
[SerializeField] private List<System.Type> keys = new List<System.Type>();
[SerializeField] private List<List<NodePort>> values = new List<List<NodePort>>();
// save the dictionary to lists
public void OnBeforeSerialize() {
keys.Clear();
values.Clear();
foreach (var pair in this) {
keys.Add(pair.Key);
values.Add(pair.Value);
}
}
// load dictionary from lists
public void OnAfterDeserialize() {
this.Clear();
if (keys.Count != values.Count)
throw new System.Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are serializable."));
for (int i = 0; i < keys.Count; i++)
this.Add(keys[i], values[i]);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XNode;
namespace XNode {
/// <summary> Lets you instantiate a node graph in the scene. This allows you to reference in-scene objects. </summary>
public class SceneGraph : MonoBehaviour {
public NodeGraph graph;
}
/// <summary> Derive from this class to create a SceneGraph with a specific graph type. </summary>
/// <example>
/// <code>
/// public class MySceneGraph : SceneGraph<MyGraph> {
///
/// }
/// </code>
/// </example>
public class SceneGraph<T> : SceneGraph where T : NodeGraph {
public new T graph { get { return base.graph as T; } set { base.graph = value; } }
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7915171fc13472a40a0162003052d2db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +0,0 @@
{
"name": "XNode",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
}

View File

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

View File

@ -1,7 +0,0 @@
{
"name": "com.github.siccity.xnode",
"description": "xNode provides a set of APIs and an editor interface for creating and editing custom node graphs.",
"version": "1.8.0",
"unity": "2018.1",
"displayName": "xNode"
}

View File

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