├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── CONTRIBUTING.md
├── CONTRIBUTING.md.meta
├── LICENSE.md
├── LICENSE.md.meta
├── README.md
├── README.md.meta
├── Scripts.meta
├── Scripts
├── Attributes.meta
├── Attributes
│ ├── NodeEnum.cs
│ ├── NodeEnum.cs.meta
│ ├── PortTypeOverrideAttribute.cs
│ └── PortTypeOverrideAttribute.cs.meta
├── Editor.meta
├── Editor
│ ├── AdvancedGenericMenu.cs
│ ├── AdvancedGenericMenu.cs.meta
│ ├── Drawers.meta
│ ├── Drawers
│ │ ├── NodeEnumDrawer.cs
│ │ ├── NodeEnumDrawer.cs.meta
│ │ ├── Odin.meta
│ │ └── Odin
│ │ │ ├── InNodeEditorAttributeProcessor.cs
│ │ │ ├── InNodeEditorAttributeProcessor.cs.meta
│ │ │ ├── InputAttributeDrawer.cs
│ │ │ ├── InputAttributeDrawer.cs.meta
│ │ │ ├── OutputAttributeDrawer.cs
│ │ │ └── OutputAttributeDrawer.cs.meta
│ ├── GraphAndNodeEditor.cs
│ ├── GraphAndNodeEditor.cs.meta
│ ├── GraphRenameFixAssetProcessor.cs
│ ├── GraphRenameFixAssetProcessor.cs.meta
│ ├── Internal.meta
│ ├── Internal
│ │ ├── RerouteReference.cs
│ │ └── RerouteReference.cs.meta
│ ├── NodeEditor.cs
│ ├── NodeEditor.cs.meta
│ ├── NodeEditorAction.cs
│ ├── NodeEditorAction.cs.meta
│ ├── NodeEditorAssetModProcessor.cs
│ ├── NodeEditorAssetModProcessor.cs.meta
│ ├── NodeEditorBase.cs
│ ├── NodeEditorBase.cs.meta
│ ├── NodeEditorGUI.cs
│ ├── NodeEditorGUI.cs.meta
│ ├── NodeEditorGUILayout.cs
│ ├── NodeEditorGUILayout.cs.meta
│ ├── NodeEditorPreferences.cs
│ ├── NodeEditorPreferences.cs.meta
│ ├── NodeEditorReflection.cs
│ ├── NodeEditorReflection.cs.meta
│ ├── NodeEditorResources.cs
│ ├── NodeEditorResources.cs.meta
│ ├── NodeEditorUtilities.cs
│ ├── NodeEditorUtilities.cs.meta
│ ├── NodeEditorWindow.cs
│ ├── NodeEditorWindow.cs.meta
│ ├── NodeGraphEditor.cs
│ ├── NodeGraphEditor.cs.meta
│ ├── NodeGraphImporter.cs
│ ├── NodeGraphImporter.cs.meta
│ ├── RenamePopup.cs
│ ├── RenamePopup.cs.meta
│ ├── Resources.meta
│ ├── Resources
│ │ ├── ScriptTemplates.meta
│ │ ├── ScriptTemplates
│ │ │ ├── xNode_NodeGraphTemplate.cs.txt
│ │ │ ├── xNode_NodeGraphTemplate.cs.txt.meta
│ │ │ ├── xNode_NodeTemplate.cs.txt
│ │ │ └── xNode_NodeTemplate.cs.txt.meta
│ │ ├── xnode_dot.png
│ │ ├── xnode_dot.png.meta
│ │ ├── xnode_dot_outer.png
│ │ ├── xnode_dot_outer.png.meta
│ │ ├── xnode_node.png
│ │ ├── xnode_node.png.meta
│ │ ├── xnode_node_highlight.png
│ │ ├── xnode_node_highlight.png.meta
│ │ ├── xnode_node_workfile.psd
│ │ └── xnode_node_workfile.psd.meta
│ ├── SceneGraphEditor.cs
│ ├── SceneGraphEditor.cs.meta
│ ├── XNodeEditor.asmdef
│ └── XNodeEditor.asmdef.meta
├── Node.cs
├── Node.cs.meta
├── NodeDataCache.cs
├── NodeDataCache.cs.meta
├── NodeGraph.cs
├── NodeGraph.cs.meta
├── NodePort.cs
├── NodePort.cs.meta
├── SceneGraph.cs
├── SceneGraph.cs.meta
├── XNode.asmdef
└── XNode.asmdef.meta
├── package.json
└── package.json.meta
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.cs]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = crlf
7 | insert_final_newline = false
8 | trim_trailing_whitespace = true
9 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: thorbrigsted
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: thorbrigsted
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /[Ll]ibrary/
2 | /[Tt]emp/
3 | /[Oo]bj/
4 | /[Bb]uild/
5 |
6 | # Autogenerated VS/MD solution and project files
7 | *.csproj
8 | *.unityproj
9 | *.sln
10 | *.suo
11 | *.tmp
12 | *.user
13 | *.userprefs
14 | *.pidb
15 | *.booproj
16 |
17 | # Unity3D generated meta files
18 | *.pidb.meta
19 |
20 | # Unity3D Generated File On Crash Reports
21 | sysinfo.txt
22 |
23 | /Examples/
24 |
25 | .git.meta
26 | .gitignore.meta
27 | .gitattributes.meta
28 |
29 | # OS X only:
30 | .DS_Store
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to xNode
2 | 💙Thank you for taking the time to contribute💙
3 |
4 | If you haven't already, join our [Discord channel](https://discord.gg/qgPrHv4)!
5 |
6 | ## Pull Requests
7 | Try to keep your pull requests relevant, neat, and manageable. If you are adding multiple features, split them into separate PRs.
8 | These are the main points to follow:
9 |
10 | 1) Use formatting which is consistent with the rest of xNode base (see below)
11 | 2) Keep _one feature_ per PR (see below)
12 | 3) xNode aims to be compatible with C# 4.x, do not use new language features
13 | 4) Avoid including irellevant whitespace or formatting changes
14 | 5) Comment your code
15 | 6) Spell check your code / comments
16 | 7) Use concrete types, not *var*
17 | 8) Use english language
18 |
19 | ## New features
20 | xNode aims to be simple and extendible, not trying to fix all of Unity's shortcomings.
21 |
22 | Approved changes might be rejected if bundled with rejected changes, so keep PRs as separate as possible.
23 |
24 | 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.
25 |
26 | ## Coding conventions
27 | Using consistent formatting is key to having a clean git history. Skim through the code and you'll get the hang of it quickly.
28 | * Methods, Types and properties PascalCase
29 | * Variables camelCase
30 | * Public methods XML commented. Params described if not obvious
31 | * Explicit usage of brackets when doing multiple math operations on the same line
32 |
33 | ## Formatting
34 | I use VSCode with the C# FixFormat extension and the following setting overrides:
35 | ```json
36 | "csharpfixformat.style.spaces.beforeParenthesis": false,
37 | "csharpfixformat.style.indent.regionIgnored": true
38 | ```
39 | * Open braces on same line as condition
40 | * 4 spaces for indentation.
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bc1db8b29c76d44648c9c86c2dfade6d
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Thor Brigsted
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 77523c356ccf04f56b53e6527c6b12fd
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://discord.gg/qgPrHv4)
4 | [](https://github.com/Siccity/xNode/issues)
5 | [](https://raw.githubusercontent.com/Siccity/xNode/master/LICENSE.md)
6 | [](https://github.com/Siccity/xNode/wiki)
7 | [](https://openupm.com/packages/com.github.siccity.xnode/)
8 |
9 | [Downloads](https://github.com/Siccity/xNode/releases) / [Asset Store](http://u3d.as/108S) / [Documentation](https://github.com/Siccity/xNode/wiki)
10 |
11 | Support xNode on [Ko-fi](https://ko-fi.com/Z8Z5DYWA) or [Patreon](https://www.patreon.com/thorbrigsted)
12 |
13 | For full Odin support, consider using [KAJed82's fork](https://github.com/KAJed82/xNode)
14 |
15 | ### xNode
16 | 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.
17 |
18 | xNode is super userfriendly, intuitive and will help you reap the benefits of node graphs in no time.
19 | With a minimal footprint, it is ideal as a base for custom state machines, dialogue systems, decision makers etc.
20 |
21 |
22 |
23 |
24 |
25 | ### Key features
26 | * Lightweight in runtime
27 | * Very little boilerplate code
28 | * Strong separation of editor and runtime code
29 | * No runtime reflection (unless you need to edit/build node graphs at runtime. In this case, all reflection is cached.)
30 | * Does not rely on any 3rd party plugins
31 | * Custom node inspector code is very similar to regular custom inspector code
32 | * Supported from Unity 5.3 and up
33 |
34 | ### Wiki
35 | * [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph
36 | * [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects
37 |
38 | ### Installation
39 | Instructions
40 |
41 | ### Installing with Unity Package Manager
42 | ***Via Git URL***
43 | *(Requires Unity version 2018.3.0b7 or above)*
44 |
45 | To install this project as a [Git dependency](https://docs.unity3d.com/Manual/upm-git.html) using the Unity Package Manager,
46 | add the following line to your project's `manifest.json`:
47 |
48 | ```
49 | "com.github.siccity.xnode": "https://github.com/siccity/xNode.git"
50 | ```
51 |
52 | You will need to have Git installed and available in your system's PATH.
53 |
54 | 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.
55 |
56 | ***Via OpenUPM***
57 |
58 | 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).
59 |
60 | ```
61 | openupm add com.github.siccity.xnode
62 | ```
63 |
64 | ### Installing with git
65 | ***Via Git Submodule***
66 |
67 | To add xNode as a [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) in your existing git project,
68 | run the following git command from your project root:
69 |
70 | ```
71 | git submodule add git@github.com:Siccity/xNode.git Assets/Submodules/xNode
72 | ```
73 |
74 | ### Installing 'the old way'
75 | If no source control or package manager is available to you, you can simply copy/paste the source files into your assets folder.
76 |
77 |
78 |
79 | ### Node example:
80 | ```csharp
81 | // public classes deriving from Node are registered as nodes for use within a graph
82 | public class MathNode : Node {
83 | // Adding [Input] or [Output] is all you need to do to register a field as a valid port on your node
84 | [Input] public float a;
85 | [Input] public float b;
86 | // The value of an output node field is not used for anything, but could be used for caching output results
87 | [Output] public float result;
88 | [Output] public float sum;
89 |
90 | // The value of 'mathType' will be displayed on the node in an editable format, similar to the inspector
91 | public MathType mathType = MathType.Add;
92 | public enum MathType { Add, Subtract, Multiply, Divide}
93 |
94 | // GetValue should be overridden to return a value for any specified output port
95 | public override object GetValue(NodePort port) {
96 |
97 | // Get new a and b values from input connections. Fallback to field values if input is not connected
98 | float a = GetInputValue("a", this.a);
99 | float b = GetInputValue("b", this.b);
100 |
101 | // After you've gotten your input values, you can perform your calculations and return a value
102 | if (port.fieldName == "result")
103 | switch(mathType) {
104 | case MathType.Add: default: return a + b;
105 | case MathType.Subtract: return a - b;
106 | case MathType.Multiply: return a * b;
107 | case MathType.Divide: return a / b;
108 | }
109 | else if (port.fieldName == "sum") return a + b;
110 | else return 0f;
111 | }
112 | }
113 | ```
114 |
115 | ### Plugins
116 | Plugins are repositories that add functionality to xNode
117 | * [xNodeGroups](https://github.com/Siccity/xNodeGroups): adds resizable groups
118 |
119 | ### Community
120 | Join the [Discord](https://discord.gg/qgPrHv4 "Join Discord server") server to leave feedback or get support.
121 | Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page.
122 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 243efae3a6b7941ad8f8e54dcf38ce8c
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 657b15cb3ec32a24ca80faebf094d0f4
3 | folderAsset: yes
4 | timeCreated: 1505418321
5 | licenseType: Free
6 | DefaultImporter:
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Scripts/Attributes.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5644dfc7eed151045af664a9d4fd1906
3 | folderAsset: yes
4 | timeCreated: 1541633926
5 | licenseType: Free
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Scripts/Attributes/NodeEnum.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | /// Draw enums correctly within nodes. Without it, enums show up at the wrong positions.
4 | /// Enums with this attribute are not detected by EditorGui.ChangeCheck due to waiting before executing
5 | public class NodeEnumAttribute : PropertyAttribute { }
--------------------------------------------------------------------------------
/Scripts/Attributes/NodeEnum.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 10a8338f6c985854697b35459181af0a
3 | timeCreated: 1541633942
4 | licenseType: Free
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Scripts/Attributes/PortTypeOverrideAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | /// Overrides the ValueType of the Port, to have a ValueType different from the type of its serializable field
3 | /// Especially useful in Dynamic Port Lists to create Value-Port Pairs with different type.
4 | [AttributeUsage(AttributeTargets.Field)]
5 | public class PortTypeOverrideAttribute : Attribute {
6 | public Type type;
7 | /// Overrides the ValueType of the Port
8 | /// ValueType of the Port
9 | public PortTypeOverrideAttribute(Type type) {
10 | this.type = type;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Scripts/Attributes/PortTypeOverrideAttribute.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1410c1437e863ab4fac7a7428aaca35b
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 94d4fd78d9120634ebe0e8717610c412
3 | folderAsset: yes
4 | timeCreated: 1505418345
5 | licenseType: Free
6 | DefaultImporter:
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Scripts/Editor/AdvancedGenericMenu.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_2019_1_OR_NEWER
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEditor.IMGUI.Controls;
5 | using UnityEngine;
6 | using static UnityEditor.GenericMenu;
7 |
8 | namespace XNodeEditor
9 | {
10 | public class AdvancedGenericMenu : AdvancedDropdown
11 | {
12 | public static float? DefaultMinWidth = 200f;
13 | public static float? DefaultMaxWidth = 300f;
14 |
15 | private class AdvancedGenericMenuItem : AdvancedDropdownItem
16 | {
17 | private MenuFunction func;
18 |
19 | private MenuFunction2 func2;
20 | private object userData;
21 |
22 | public AdvancedGenericMenuItem( string name ) : base( name )
23 | {
24 | }
25 |
26 | public AdvancedGenericMenuItem( string name, bool enabled, Texture2D icon, MenuFunction func ) : base( name )
27 | {
28 | Set( enabled, icon, func );
29 | }
30 |
31 | public AdvancedGenericMenuItem( string name, bool enabled, Texture2D icon, MenuFunction2 func, object userData ) : base( name )
32 | {
33 | Set( enabled, icon, func, userData );
34 | }
35 |
36 | public void Set( bool enabled, Texture2D icon, MenuFunction func )
37 | {
38 | this.enabled = enabled;
39 | this.icon = icon;
40 | this.func = func;
41 | }
42 |
43 | public void Set( bool enabled, Texture2D icon, MenuFunction2 func, object userData )
44 | {
45 | this.enabled = enabled;
46 | this.icon = icon;
47 | this.func2 = func;
48 | this.userData = userData;
49 | }
50 |
51 | public void Run()
52 | {
53 | if ( func2 != null )
54 | func2( userData );
55 | else if ( func != null )
56 | func();
57 | }
58 | }
59 |
60 | private List items = new List();
61 |
62 | private AdvancedGenericMenuItem FindOrCreateItem( string name, AdvancedGenericMenuItem currentRoot = null )
63 | {
64 | if ( string.IsNullOrWhiteSpace( name ) )
65 | return null;
66 |
67 | AdvancedGenericMenuItem item = null;
68 |
69 | string[] paths = name.Split( '/' );
70 | if ( currentRoot == null )
71 | {
72 | item = items.FirstOrDefault( x => x != null && x.name == paths[0] );
73 | if ( item == null )
74 | items.Add( item = new AdvancedGenericMenuItem( paths[0] ) );
75 | }
76 | else
77 | {
78 | item = currentRoot.children.OfType().FirstOrDefault( x => x.name == paths[0] );
79 | if ( item == null )
80 | currentRoot.AddChild( item = new AdvancedGenericMenuItem( paths[0] ) );
81 | }
82 |
83 | if ( paths.Length > 1 )
84 | return FindOrCreateItem( string.Join( "/", paths, 1, paths.Length - 1 ), item );
85 |
86 | return item;
87 | }
88 |
89 | private AdvancedGenericMenuItem FindParent( string name )
90 | {
91 | string[] paths = name.Split( '/' );
92 | return FindOrCreateItem( string.Join( "/", paths, 0, paths.Length - 1 ) );
93 | }
94 |
95 | private string Name { get; set; }
96 |
97 | public AdvancedGenericMenu() : base( new AdvancedDropdownState() )
98 | {
99 | Name = "";
100 | }
101 |
102 | public AdvancedGenericMenu( string name, AdvancedDropdownState state ) : base( state )
103 | {
104 | Name = name;
105 | }
106 |
107 | //
108 | // Summary:
109 | // Add a disabled item to the menu.
110 | //
111 | // Parameters:
112 | // content:
113 | // The GUIContent to display as a disabled menu item.
114 | public void AddDisabledItem( GUIContent content )
115 | {
116 | //var parent = FindParent( content.text );
117 | var item = FindOrCreateItem( content.text );
118 | item.Set( false, null, null );
119 | }
120 |
121 | //
122 | // Summary:
123 | // Add a disabled item to the menu.
124 | //
125 | // Parameters:
126 | // content:
127 | // The GUIContent to display as a disabled menu item.
128 | //
129 | // on:
130 | // Specifies whether to show that the item is currently activated (i.e. a tick next
131 | // to the item in the menu).
132 | public void AddDisabledItem( GUIContent content, bool on )
133 | {
134 | }
135 |
136 | public void AddItem( string name, bool on, MenuFunction func )
137 | {
138 | AddItem( new GUIContent( name ), on, func );
139 | }
140 |
141 | public void AddItem( GUIContent content, bool on, MenuFunction func )
142 | {
143 | //var parent = FindParent( content.text );
144 | var item = FindOrCreateItem( content.text );
145 | item.Set( true/*on*/, null, func );
146 | }
147 |
148 | public void AddItem( string name, bool on, MenuFunction2 func, object userData )
149 | {
150 | AddItem( new GUIContent( name ), on, func, userData );
151 | }
152 |
153 | public void AddItem( GUIContent content, bool on, MenuFunction2 func, object userData )
154 | {
155 | //var parent = FindParent( content.text );
156 | var item = FindOrCreateItem( content.text );
157 | item.Set( true/*on*/, null, func, userData );
158 | }
159 |
160 | //
161 | // Summary:
162 | // Add a seperator item to the menu.
163 | //
164 | // Parameters:
165 | // path:
166 | // The path to the submenu, if adding a separator to a submenu. When adding a separator
167 | // to the top level of a menu, use an empty string as the path.
168 | public void AddSeparator( string path = null )
169 | {
170 | var parent = string.IsNullOrWhiteSpace( path ) ? null : FindParent( path );
171 | if ( parent == null )
172 | items.Add( null );
173 | else
174 | parent.AddSeparator();
175 | }
176 |
177 | //
178 | // Summary:
179 | // Show the menu at the given screen rect.
180 | //
181 | // Parameters:
182 | // position:
183 | // The position at which to show the menu.
184 | public void DropDown( Rect position )
185 | {
186 | position.width = Mathf.Clamp( position.width, DefaultMinWidth.HasValue ? DefaultMinWidth.Value : 1f, DefaultMaxWidth.HasValue ? DefaultMaxWidth.Value : Screen.width );
187 |
188 | Show( position );
189 | }
190 |
191 | protected override AdvancedDropdownItem BuildRoot()
192 | {
193 | var root = new AdvancedDropdownItem( Name );
194 |
195 | foreach ( var m in items )
196 | {
197 | if ( m == null )
198 | root.AddSeparator();
199 | else
200 | root.AddChild( m );
201 | }
202 |
203 | return root;
204 | }
205 |
206 | protected override void ItemSelected( AdvancedDropdownItem item )
207 | {
208 | if ( item is AdvancedGenericMenuItem gmItem )
209 | gmItem.Run();
210 | }
211 | }
212 | }
213 | #endif
--------------------------------------------------------------------------------
/Scripts/Editor/AdvancedGenericMenu.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ddde711109af02e42bfe8eb006577081
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7adf21edfb51f514fa991d7556ecd0ef
3 | folderAsset: yes
4 | timeCreated: 1541971984
5 | licenseType: Free
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/NodeEnumDrawer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEditor;
5 | using UnityEngine;
6 | using XNode;
7 | using XNodeEditor;
8 |
9 | namespace XNodeEditor {
10 | [CustomPropertyDrawer(typeof(NodeEnumAttribute))]
11 | public class NodeEnumDrawer : PropertyDrawer {
12 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
13 | EditorGUI.BeginProperty(position, label, property);
14 |
15 | EnumPopup(position, property, label);
16 |
17 | EditorGUI.EndProperty();
18 | }
19 |
20 | public static void EnumPopup(Rect position, SerializedProperty property, GUIContent label) {
21 | // Throw error on wrong type
22 | if (property.propertyType != SerializedPropertyType.Enum) {
23 | throw new ArgumentException("Parameter selected must be of type System.Enum");
24 | }
25 |
26 | // Add label
27 | position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
28 |
29 | // Get current enum name
30 | string enumName = "";
31 | if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex];
32 |
33 | #if UNITY_2017_1_OR_NEWER
34 | // Display dropdown
35 | if (EditorGUI.DropdownButton(position, new GUIContent(enumName), FocusType.Passive)) {
36 | // Position is all wrong if we show the dropdown during the node draw phase.
37 | // Instead, add it to onLateGUI to display it later.
38 | NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
39 | }
40 | #else
41 | // Display dropdown
42 | if (GUI.Button(position, new GUIContent(enumName), "MiniPopup")) {
43 | // Position is all wrong if we show the dropdown during the node draw phase.
44 | // Instead, add it to onLateGUI to display it later.
45 | NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
46 | }
47 | #endif
48 | }
49 |
50 | public static void ShowContextMenuAtMouse(SerializedProperty property) {
51 | // Initialize menu
52 | GenericMenu menu = new GenericMenu();
53 |
54 | // Add all enum display names to menu
55 | for (int i = 0; i < property.enumDisplayNames.Length; i++) {
56 | int index = i;
57 | menu.AddItem(new GUIContent(property.enumDisplayNames[i]), false, () => SetEnum(property, index));
58 | }
59 |
60 | // Display at cursor position
61 | Rect r = new Rect(Event.current.mousePosition, new Vector2(0, 0));
62 | menu.DropDown(r);
63 | }
64 |
65 | private static void SetEnum(SerializedProperty property, int index) {
66 | property.enumValueIndex = index;
67 | property.serializedObject.ApplyModifiedProperties();
68 | property.serializedObject.Update();
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 83db81f92abadca439507e25d517cabe
3 | timeCreated: 1541633798
4 | licenseType: Free
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 327994a52f523b641898a39ff7500a02
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_EDITOR && ODIN_INSPECTOR
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 | using Sirenix.OdinInspector.Editor;
6 | using UnityEngine;
7 | using XNode;
8 |
9 | namespace XNodeEditor {
10 | internal class OdinNodeInGraphAttributeProcessor : OdinAttributeProcessor where T : Node {
11 | public override bool CanProcessSelfAttributes(InspectorProperty property) {
12 | return false;
13 | }
14 |
15 | public override bool CanProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member) {
16 | if (!NodeEditor.inNodeEditor)
17 | return false;
18 |
19 | if (member.MemberType == MemberTypes.Field) {
20 | switch (member.Name) {
21 | case "graph":
22 | case "position":
23 | case "ports":
24 | return true;
25 |
26 | default:
27 | break;
28 | }
29 | }
30 |
31 | return false;
32 | }
33 |
34 | public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List attributes) {
35 | switch (member.Name) {
36 | case "graph":
37 | case "position":
38 | case "ports":
39 | attributes.Add(new HideInInspector());
40 | break;
41 |
42 | default:
43 | break;
44 | }
45 | }
46 | }
47 | }
48 | #endif
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3cf2561fbfea9a041ac81efbbb5b3e0d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_EDITOR && ODIN_INSPECTOR
2 | using Sirenix.OdinInspector;
3 | using Sirenix.OdinInspector.Editor;
4 | using Sirenix.Utilities.Editor;
5 | using UnityEngine;
6 | using XNode;
7 |
8 | namespace XNodeEditor {
9 | public class InputAttributeDrawer : OdinAttributeDrawer {
10 | protected override bool CanDrawAttributeProperty(InspectorProperty property) {
11 | Node node = property.Tree.WeakTargets[0] as Node;
12 | return node != null;
13 | }
14 |
15 | protected override void DrawPropertyLayout(GUIContent label) {
16 | Node node = Property.Tree.WeakTargets[0] as Node;
17 | NodePort port = node.GetInputPort(Property.Name);
18 |
19 | if (!NodeEditor.inNodeEditor) {
20 | if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected)
21 | CallNextDrawer(label);
22 | return;
23 | }
24 |
25 | if (Property.Tree.WeakTargets.Count > 1) {
26 | SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected");
27 | return;
28 | }
29 |
30 | if (port != null) {
31 | var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath);
32 | if (portPropoerty == null) {
33 | SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath);
34 | return;
35 | } else {
36 | var labelWidth = Property.GetAttribute();
37 | if (labelWidth != null)
38 | GUIHelper.PushLabelWidth(labelWidth.Width);
39 |
40 | NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30));
41 |
42 | if (labelWidth != null)
43 | GUIHelper.PopLabelWidth();
44 | }
45 | }
46 | }
47 | }
48 | }
49 | #endif
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2fd590b2e9ea0bd49b6986a2ca9010ab
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_EDITOR && ODIN_INSPECTOR
2 | using Sirenix.OdinInspector;
3 | using Sirenix.OdinInspector.Editor;
4 | using Sirenix.Utilities.Editor;
5 | using UnityEngine;
6 | using XNode;
7 |
8 | namespace XNodeEditor {
9 | public class OutputAttributeDrawer : OdinAttributeDrawer {
10 | protected override bool CanDrawAttributeProperty(InspectorProperty property) {
11 | Node node = property.Tree.WeakTargets[0] as Node;
12 | return node != null;
13 | }
14 |
15 | protected override void DrawPropertyLayout(GUIContent label) {
16 | Node node = Property.Tree.WeakTargets[0] as Node;
17 | NodePort port = node.GetOutputPort(Property.Name);
18 |
19 | if (!NodeEditor.inNodeEditor) {
20 | if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected)
21 | CallNextDrawer(label);
22 | return;
23 | }
24 |
25 | if (Property.Tree.WeakTargets.Count > 1) {
26 | SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected");
27 | return;
28 | }
29 |
30 | if (port != null) {
31 | var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath);
32 | if (portPropoerty == null) {
33 | SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath);
34 | return;
35 | } else {
36 | var labelWidth = Property.GetAttribute();
37 | if (labelWidth != null)
38 | GUIHelper.PushLabelWidth(labelWidth.Width);
39 |
40 | NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30));
41 |
42 | if (labelWidth != null)
43 | GUIHelper.PopLabelWidth();
44 | }
45 | }
46 | }
47 | }
48 | }
49 | #endif
--------------------------------------------------------------------------------
/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e7ebd8f2b42e2384aa109551dc46af88
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/GraphAndNodeEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 | #if ODIN_INSPECTOR
4 | using Sirenix.OdinInspector.Editor;
5 | using Sirenix.Utilities;
6 | using Sirenix.Utilities.Editor;
7 | #endif
8 |
9 | namespace XNodeEditor {
10 | /// Override graph inspector to show an 'Open Graph' button at the top
11 | [CustomEditor(typeof(XNode.NodeGraph), true)]
12 | #if ODIN_INSPECTOR
13 | public class GlobalGraphEditor : OdinEditor {
14 | public override void OnInspectorGUI() {
15 | if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
16 | NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph);
17 | }
18 | base.OnInspectorGUI();
19 | }
20 | }
21 | #else
22 | [CanEditMultipleObjects]
23 | public class GlobalGraphEditor : Editor {
24 | public override void OnInspectorGUI() {
25 | serializedObject.Update();
26 |
27 | if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
28 | NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph);
29 | }
30 |
31 | GUILayout.Space(EditorGUIUtility.singleLineHeight);
32 | GUILayout.Label("Raw data", "BoldLabel");
33 |
34 | DrawDefaultInspector();
35 |
36 | serializedObject.ApplyModifiedProperties();
37 | }
38 | }
39 | #endif
40 |
41 | [CustomEditor(typeof(XNode.Node), true)]
42 | #if ODIN_INSPECTOR
43 | public class GlobalNodeEditor : OdinEditor {
44 | public override void OnInspectorGUI() {
45 | if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
46 | SerializedProperty graphProp = serializedObject.FindProperty("graph");
47 | NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph);
48 | w.Home(); // Focus selected node
49 | }
50 | base.OnInspectorGUI();
51 | }
52 | }
53 | #else
54 | [CanEditMultipleObjects]
55 | public class GlobalNodeEditor : Editor {
56 | public override void OnInspectorGUI() {
57 | serializedObject.Update();
58 |
59 | if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
60 | SerializedProperty graphProp = serializedObject.FindProperty("graph");
61 | NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph);
62 | w.Home(); // Focus selected node
63 | }
64 |
65 | GUILayout.Space(EditorGUIUtility.singleLineHeight);
66 | GUILayout.Label("Raw data", "BoldLabel");
67 |
68 | // Now draw the node itself.
69 | DrawDefaultInspector();
70 |
71 | serializedObject.ApplyModifiedProperties();
72 | }
73 | }
74 | #endif
75 | }
--------------------------------------------------------------------------------
/Scripts/Editor/GraphAndNodeEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bdd6e443125ccac4dad0665515759637
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/GraphRenameFixAssetProcessor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using XNode;
3 |
4 | namespace XNodeEditor {
5 | ///
6 | /// This asset processor resolves an issue with the new v2 AssetDatabase system present on 2019.3 and later. When
7 | /// renaming a asset, it appears that sometimes the v2 AssetDatabase will swap which asset
8 | /// is the main asset (present at top level) between the and one of its
9 | /// sub-assets. As a workaround until Unity fixes this, this asset processor checks all renamed assets and if it
10 | /// finds a case where a has been made the main asset it will swap it back to being a sub-asset
11 | /// and rename the node to the default name for that node type.
12 | ///
13 | internal sealed class GraphRenameFixAssetProcessor : AssetPostprocessor {
14 | private static void OnPostprocessAllAssets(
15 | string[] importedAssets,
16 | string[] deletedAssets,
17 | string[] movedAssets,
18 | string[] movedFromAssetPaths) {
19 | for (int i = 0; i < movedAssets.Length; i++) {
20 | Node nodeAsset = AssetDatabase.LoadMainAssetAtPath(movedAssets[i]) as Node;
21 |
22 | // If the renamed asset is a node graph, but the v2 AssetDatabase has swapped a sub-asset node to be its
23 | // main asset, reset the node graph to be the main asset and rename the node asset back to its default
24 | // name.
25 | if (nodeAsset != null && AssetDatabase.IsMainAsset(nodeAsset)) {
26 | AssetDatabase.SetMainObject(nodeAsset.graph, movedAssets[i]);
27 | AssetDatabase.ImportAsset(movedAssets[i]);
28 |
29 | nodeAsset.name = NodeEditorUtilities.NodeDefaultName(nodeAsset.GetType());
30 | EditorUtility.SetDirty(nodeAsset);
31 | }
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Scripts/Editor/GraphRenameFixAssetProcessor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 65da1ff1c50a9984a9c95fd18799e8dd
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/Internal.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a6a1bbc054e282346a02e7bbddde3206
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Editor/Internal/RerouteReference.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace XNodeEditor.Internal {
4 | public struct RerouteReference {
5 | public XNode.NodePort port;
6 | public int connectionIndex;
7 | public int pointIndex;
8 |
9 | public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) {
10 | this.port = port;
11 | this.connectionIndex = connectionIndex;
12 | this.pointIndex = pointIndex;
13 | }
14 |
15 | public void InsertPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex).Insert(pointIndex, pos); }
16 | public void SetPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex) [pointIndex] = pos; }
17 | public void RemovePoint() { port.GetReroutePoints(connectionIndex).RemoveAt(pointIndex); }
18 | public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; }
19 | }
20 | }
--------------------------------------------------------------------------------
/Scripts/Editor/Internal/RerouteReference.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 399f3c5fb717b2c458c3e9746f8959a3
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using UnityEditor;
5 | using UnityEngine;
6 | #if ODIN_INSPECTOR
7 | using Sirenix.OdinInspector.Editor;
8 | using Sirenix.Utilities;
9 | using Sirenix.Utilities.Editor;
10 | #endif
11 | #if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
12 | using GenericMenu = XNodeEditor.AdvancedGenericMenu;
13 | #endif
14 |
15 | namespace XNodeEditor {
16 | /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes.
17 | [CustomNodeEditor(typeof(XNode.Node))]
18 | public class NodeEditor : XNodeEditor.Internal.NodeEditorBase {
19 |
20 | /// Fires every whenever a node was modified through the editor
21 | public static Action onUpdateNode;
22 | public readonly static Dictionary portPositions = new Dictionary();
23 |
24 | #if ODIN_INSPECTOR
25 | protected internal static bool inNodeEditor = false;
26 | #endif
27 |
28 | public virtual void OnHeaderGUI() {
29 | GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
30 | }
31 |
32 | /// Draws standard field editors for all public fields
33 | public virtual void OnBodyGUI() {
34 | #if ODIN_INSPECTOR
35 | inNodeEditor = true;
36 | #endif
37 |
38 | // Unity specifically requires this to save/update any serial object.
39 | // serializedObject.Update(); must go at the start of an inspector gui, and
40 | // serializedObject.ApplyModifiedProperties(); goes at the end.
41 | serializedObject.Update();
42 | string[] excludes = { "m_Script", "graph", "position", "ports" };
43 |
44 | #if ODIN_INSPECTOR
45 | try
46 | {
47 | #if ODIN_INSPECTOR_3
48 | objectTree.BeginDraw( true );
49 | #else
50 | InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
51 | #endif
52 | }
53 | catch ( ArgumentNullException )
54 | {
55 | #if ODIN_INSPECTOR_3
56 | objectTree.EndDraw();
57 | #else
58 | InspectorUtilities.EndDrawPropertyTree(objectTree);
59 | #endif
60 | NodeEditor.DestroyEditor(this.target);
61 | return;
62 | }
63 |
64 | GUIHelper.PushLabelWidth( 84 );
65 | objectTree.Draw( true );
66 | #if ODIN_INSPECTOR_3
67 | objectTree.EndDraw();
68 | #else
69 | InspectorUtilities.EndDrawPropertyTree(objectTree);
70 | #endif
71 | GUIHelper.PopLabelWidth();
72 | #else
73 |
74 | // Iterate through serialized properties and draw them like the Inspector (But with ports)
75 | SerializedProperty iterator = serializedObject.GetIterator();
76 | bool enterChildren = true;
77 | while (iterator.NextVisible(enterChildren)) {
78 | enterChildren = false;
79 | if (excludes.Contains(iterator.name)) continue;
80 | NodeEditorGUILayout.PropertyField(iterator, true);
81 | }
82 | #endif
83 |
84 | // Iterate through dynamic ports and draw them in the order in which they are serialized
85 | foreach (XNode.NodePort dynamicPort in target.DynamicPorts) {
86 | if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue;
87 | NodeEditorGUILayout.PortField(dynamicPort);
88 | }
89 |
90 | serializedObject.ApplyModifiedProperties();
91 |
92 | #if ODIN_INSPECTOR
93 | // Call repaint so that the graph window elements respond properly to layout changes coming from Odin
94 | if (GUIHelper.RepaintRequested) {
95 | GUIHelper.ClearRepaintRequest();
96 | window.Repaint();
97 | }
98 | #endif
99 |
100 | #if ODIN_INSPECTOR
101 | inNodeEditor = false;
102 | #endif
103 | }
104 |
105 | public virtual int GetWidth() {
106 | Type type = target.GetType();
107 | int width;
108 | if (type.TryGetAttributeWidth(out width)) return width;
109 | else return 208;
110 | }
111 |
112 | /// Returns color for target node
113 | public virtual Color GetTint() {
114 | // Try get color from [NodeTint] attribute
115 | Type type = target.GetType();
116 | Color color;
117 | if (type.TryGetAttributeTint(out color)) return color;
118 | // Return default color (grey)
119 | else return NodeEditorPreferences.GetSettings().tintColor;
120 | }
121 |
122 | public virtual GUIStyle GetBodyStyle() {
123 | return NodeEditorResources.styles.nodeBody;
124 | }
125 |
126 | public virtual GUIStyle GetBodyHighlightStyle() {
127 | return NodeEditorResources.styles.nodeHighlight;
128 | }
129 |
130 | /// Override to display custom node header tooltips
131 | public virtual string GetHeaderTooltip() {
132 | return null;
133 | }
134 |
135 | /// Add items for the context menu when right-clicking this node. Override to add custom menu items.
136 | public virtual void AddContextMenuItems(GenericMenu menu) {
137 | bool canRemove = true;
138 | // Actions if only one node is selected
139 | if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
140 | XNode.Node node = Selection.activeObject as XNode.Node;
141 | menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
142 | menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
143 |
144 | canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node);
145 | }
146 |
147 | // Add actions to any number of selected nodes
148 | menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
149 | menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
150 |
151 | if (canRemove) menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
152 | else menu.AddItem(new GUIContent("Remove"), false, null);
153 |
154 | // Custom sctions if only one node is selected
155 | if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
156 | XNode.Node node = Selection.activeObject as XNode.Node;
157 | menu.AddCustomContextMenuItems(node);
158 | }
159 | }
160 |
161 | /// Rename the node asset. This will trigger a reimport of the node.
162 | public void Rename(string newName) {
163 | if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType());
164 | target.name = newName;
165 | OnRename();
166 | AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
167 | }
168 |
169 | /// Called after this node's name has changed.
170 | public virtual void OnRename() { }
171 |
172 | [AttributeUsage(AttributeTargets.Class)]
173 | public class CustomNodeEditorAttribute : Attribute,
174 | XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib {
175 | private Type inspectedType;
176 | /// Tells a NodeEditor which Node type it is an editor for
177 | /// Type that this editor can edit
178 | public CustomNodeEditorAttribute(Type inspectedType) {
179 | this.inspectedType = inspectedType;
180 | }
181 |
182 | public Type GetInspectedType() {
183 | return inspectedType;
184 | }
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 712c3fc5d9eeb4c45b1e23918df6018f
3 | timeCreated: 1505462176
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorAction.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: aa7d4286bf0ad2e4086252f2893d2cf5
3 | timeCreated: 1505426655
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorAssetModProcessor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 | using System.IO;
4 |
5 | namespace XNodeEditor {
6 | /// Deals with modified assets
7 | class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor {
8 |
9 | /// Automatically delete Node sub-assets before deleting their script.
10 | /// This is important to do, because you can't delete null sub assets.
11 | /// For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete
12 | private static AssetDeleteResult OnWillDeleteAsset (string path, RemoveAssetOptions options) {
13 | // Skip processing anything without the .cs extension
14 | if (Path.GetExtension(path) != ".cs") return AssetDeleteResult.DidNotDelete;
15 |
16 | // Get the object that is requested for deletion
17 | UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath (path);
18 |
19 | // If we aren't deleting a script, return
20 | if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete;
21 |
22 | // Check script type. Return if deleting a non-node script
23 | UnityEditor.MonoScript script = obj as UnityEditor.MonoScript;
24 | System.Type scriptType = script.GetClass ();
25 | if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete;
26 |
27 | // Find all ScriptableObjects using this script
28 | string[] guids = AssetDatabase.FindAssets ("t:" + scriptType);
29 | for (int i = 0; i < guids.Length; i++) {
30 | string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
31 | Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
32 | for (int k = 0; k < objs.Length; k++) {
33 | XNode.Node node = objs[k] as XNode.Node;
34 | if (node.GetType () == scriptType) {
35 | if (node != null && node.graph != null) {
36 | // Delete the node and notify the user
37 | Debug.LogWarning (node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph);
38 | node.graph.RemoveNode (node);
39 | }
40 | }
41 | }
42 | }
43 | // We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure
44 | return AssetDeleteResult.DidNotDelete;
45 | }
46 |
47 | /// Automatically re-add loose node assets to the Graph node list
48 | [InitializeOnLoadMethod]
49 | private static void OnReloadEditor () {
50 | // Find all NodeGraph assets
51 | string[] guids = AssetDatabase.FindAssets ("t:" + typeof (XNode.NodeGraph));
52 | for (int i = 0; i < guids.Length; i++) {
53 | string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
54 | XNode.NodeGraph graph = AssetDatabase.LoadAssetAtPath (assetpath, typeof (XNode.NodeGraph)) as XNode.NodeGraph;
55 | graph.nodes.RemoveAll(x => x == null); //Remove null items
56 | Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
57 | // Ensure that all sub node assets are present in the graph node list
58 | for (int u = 0; u < objs.Length; u++) {
59 | // Ignore null sub assets
60 | if (objs[u] == null) continue;
61 | if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node);
62 | }
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e515e86efe8160243a68b7c06d730c9c
3 | timeCreated: 1507982232
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 | using UnityEditor;
6 | using UnityEngine;
7 | #if ODIN_INSPECTOR
8 | using Sirenix.OdinInspector.Editor;
9 | #endif
10 |
11 | namespace XNodeEditor.Internal {
12 | /// Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type)
13 | /// Editor Type. Should be the type of the deriving script itself (eg. NodeEditor)
14 | /// Attribute Type. The attribute used to connect with the runtime type (eg. CustomNodeEditorAttribute)
15 | /// Runtime Type. The ScriptableObject this can be an editor for (eg. Node)
16 | public abstract class NodeEditorBase where A : Attribute, NodeEditorBase.INodeEditorAttrib where T : NodeEditorBase where K : ScriptableObject {
17 | /// Custom editors defined with [CustomNodeEditor]
18 | private static Dictionary editorTypes;
19 | private static Dictionary editors = new Dictionary();
20 | public NodeEditorWindow window;
21 | public K target;
22 | public SerializedObject serializedObject;
23 | #if ODIN_INSPECTOR
24 | private PropertyTree _objectTree;
25 | public PropertyTree objectTree {
26 | get {
27 | if (this._objectTree == null){
28 | try {
29 | bool wasInEditor = NodeEditor.inNodeEditor;
30 | NodeEditor.inNodeEditor = true;
31 | this._objectTree = PropertyTree.Create(this.serializedObject);
32 | NodeEditor.inNodeEditor = wasInEditor;
33 | } catch (ArgumentException ex) {
34 | Debug.Log(ex);
35 | }
36 | }
37 | return this._objectTree;
38 | }
39 | }
40 | #endif
41 |
42 | public static T GetEditor(K target, NodeEditorWindow window) {
43 | if (target == null) return null;
44 | T editor;
45 | if (!editors.TryGetValue(target, out editor)) {
46 | Type type = target.GetType();
47 | Type editorType = GetEditorType(type);
48 | editor = Activator.CreateInstance(editorType) as T;
49 | editor.target = target;
50 | editor.serializedObject = new SerializedObject(target);
51 | editor.window = window;
52 | editor.OnCreate();
53 | editors.Add(target, editor);
54 | }
55 | if (editor.target == null) editor.target = target;
56 | if (editor.window != window) editor.window = window;
57 | if (editor.serializedObject == null) editor.serializedObject = new SerializedObject(target);
58 | return editor;
59 | }
60 |
61 | public static void DestroyEditor( K target )
62 | {
63 | if ( target == null ) return;
64 | T editor;
65 | if ( editors.TryGetValue( target, out editor ) )
66 | {
67 | editors.Remove( target );
68 | }
69 | }
70 |
71 | private static Type GetEditorType(Type type) {
72 | if (type == null) return null;
73 | if (editorTypes == null) CacheCustomEditors();
74 | Type result;
75 | if (editorTypes.TryGetValue(type, out result)) return result;
76 | //If type isn't found, try base type
77 | return GetEditorType(type.BaseType);
78 | }
79 |
80 | private static void CacheCustomEditors() {
81 | editorTypes = new Dictionary();
82 |
83 | //Get all classes deriving from NodeEditor via reflection
84 | Type[] nodeEditors = typeof(T).GetDerivedTypes();
85 | for (int i = 0; i < nodeEditors.Length; i++) {
86 | if (nodeEditors[i].IsAbstract) continue;
87 | var attribs = nodeEditors[i].GetCustomAttributes(typeof(A), false);
88 | if (attribs == null || attribs.Length == 0) continue;
89 | A attrib = attribs[0] as A;
90 | editorTypes.Add(attrib.GetInspectedType(), nodeEditors[i]);
91 | }
92 | }
93 |
94 | /// Called on creation, after references have been set
95 | public virtual void OnCreate() { }
96 |
97 | public interface INodeEditorAttrib {
98 | Type GetInspectedType();
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorBase.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e85122ded59aceb4eb4b1bd9d9202642
3 | timeCreated: 1511353946
4 | licenseType: Free
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorGUI.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 756276bfe9a0c2f4da3930ba1964f58d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorGUILayout.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1d6c2d118d1c77948a23f2f4a34d1f64
3 | timeCreated: 1507966608
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorPreferences.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEditor;
4 | using UnityEngine;
5 | using UnityEngine.Serialization;
6 |
7 | namespace XNodeEditor {
8 | public enum NoodlePath { Curvy, Straight, Angled, ShaderLab }
9 | public enum NoodleStroke { Full, Dashed }
10 |
11 | public static class NodeEditorPreferences {
12 |
13 | /// The last editor we checked. This should be the one we modify
14 | private static XNodeEditor.NodeGraphEditor lastEditor;
15 | /// The last key we checked. This should be the one we modify
16 | private static string lastKey = "xNode.Settings";
17 |
18 | private static Dictionary typeColors = new Dictionary();
19 | private static Dictionary settings = new Dictionary();
20 |
21 | [System.Serializable]
22 | public class Settings : ISerializationCallbackReceiver {
23 | [SerializeField] private Color32 _gridLineColor = new Color(.23f, .23f, .23f);
24 | public Color32 gridLineColor { get { return _gridLineColor; } set { _gridLineColor = value; _gridTexture = null; _crossTexture = null; } }
25 |
26 | [SerializeField] private Color32 _gridBgColor = new Color(.19f, .19f, .19f);
27 | public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } }
28 |
29 | [Obsolete("Use maxZoom instead")]
30 | public float zoomOutLimit { get { return maxZoom; } set { maxZoom = value; } }
31 |
32 | [UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")]
33 | public float maxZoom = 5f;
34 | public float minZoom = 1f;
35 | public Color32 tintColor = new Color32(90, 97, 105, 255);
36 | public Color32 highlightColor = new Color32(255, 255, 255, 255);
37 | public bool gridSnap = true;
38 | public bool autoSave = true;
39 | public bool openOnCreate = true;
40 | public bool dragToCreate = true;
41 | public bool createFilter = true;
42 | public bool zoomToMouse = true;
43 | public bool portTooltips = true;
44 | [SerializeField] private string typeColorsData = "";
45 | [NonSerialized] public Dictionary typeColors = new Dictionary();
46 | [FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy;
47 | public float noodleThickness = 2f;
48 |
49 | public NoodleStroke noodleStroke = NoodleStroke.Full;
50 |
51 | private Texture2D _gridTexture;
52 | public Texture2D gridTexture {
53 | get {
54 | if (_gridTexture == null) _gridTexture = NodeEditorResources.GenerateGridTexture(gridLineColor, gridBgColor);
55 | return _gridTexture;
56 | }
57 | }
58 | private Texture2D _crossTexture;
59 | public Texture2D crossTexture {
60 | get {
61 | if (_crossTexture == null) _crossTexture = NodeEditorResources.GenerateCrossTexture(gridLineColor);
62 | return _crossTexture;
63 | }
64 | }
65 |
66 | public void OnAfterDeserialize() {
67 | // Deserialize typeColorsData
68 | typeColors = new Dictionary();
69 | string[] data = typeColorsData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
70 | for (int i = 0; i < data.Length; i += 2) {
71 | Color col;
72 | if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) {
73 | typeColors.Add(data[i], col);
74 | }
75 | }
76 | }
77 |
78 | public void OnBeforeSerialize() {
79 | // Serialize typeColors
80 | typeColorsData = "";
81 | foreach (var item in typeColors) {
82 | typeColorsData += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ",";
83 | }
84 | }
85 | }
86 |
87 | /// Get settings of current active editor
88 | public static Settings GetSettings() {
89 | if (XNodeEditor.NodeEditorWindow.current == null) return new Settings();
90 |
91 | if (lastEditor != XNodeEditor.NodeEditorWindow.current.graphEditor) {
92 | object[] attribs = XNodeEditor.NodeEditorWindow.current.graphEditor.GetType().GetCustomAttributes(typeof(XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute), true);
93 | if (attribs.Length == 1) {
94 | XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute;
95 | lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor;
96 | lastKey = attrib.editorPrefsKey;
97 | } else return null;
98 | }
99 | if (!settings.ContainsKey(lastKey)) VerifyLoaded();
100 | return settings[lastKey];
101 | }
102 |
103 | #if UNITY_2019_1_OR_NEWER
104 | [SettingsProvider]
105 | public static SettingsProvider CreateXNodeSettingsProvider() {
106 | SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) {
107 | guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); },
108 | keywords = new HashSet(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" })
109 | };
110 | return provider;
111 | }
112 | #endif
113 |
114 | #if !UNITY_2019_1_OR_NEWER
115 | [PreferenceItem("Node Editor")]
116 | #endif
117 | private static void PreferencesGUI() {
118 | VerifyLoaded();
119 | Settings settings = NodeEditorPreferences.settings[lastKey];
120 |
121 | if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100))) Application.OpenURL("https://github.com/Siccity/xNode/wiki");
122 | EditorGUILayout.Space();
123 |
124 | NodeSettingsGUI(lastKey, settings);
125 | GridSettingsGUI(lastKey, settings);
126 | SystemSettingsGUI(lastKey, settings);
127 | TypeColorsGUI(lastKey, settings);
128 | if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120))) {
129 | ResetPrefs();
130 | }
131 | }
132 |
133 | private static void GridSettingsGUI(string key, Settings settings) {
134 | //Label
135 | EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel);
136 | settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap);
137 | settings.zoomToMouse = EditorGUILayout.Toggle(new GUIContent("Zoom to Mouse", "Zooms towards mouse position"), settings.zoomToMouse);
138 | EditorGUILayout.LabelField("Zoom");
139 | EditorGUI.indentLevel++;
140 | settings.maxZoom = EditorGUILayout.FloatField(new GUIContent("Max", "Upper limit to zoom"), settings.maxZoom);
141 | settings.minZoom = EditorGUILayout.FloatField(new GUIContent("Min", "Lower limit to zoom"), settings.minZoom);
142 | EditorGUI.indentLevel--;
143 | settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor);
144 | settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor);
145 | if (GUI.changed) {
146 | SavePrefs(key, settings);
147 |
148 | NodeEditorWindow.RepaintAll();
149 | }
150 | EditorGUILayout.Space();
151 | }
152 |
153 | private static void SystemSettingsGUI(string key, Settings settings) {
154 | //Label
155 | EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
156 | settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave);
157 | settings.openOnCreate = EditorGUILayout.Toggle(new GUIContent("Open Editor on Create", "Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate);
158 | if (GUI.changed) SavePrefs(key, settings);
159 | EditorGUILayout.Space();
160 | }
161 |
162 | private static void NodeSettingsGUI(string key, Settings settings) {
163 | //Label
164 | EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
165 | settings.tintColor = EditorGUILayout.ColorField("Tint", settings.tintColor);
166 | settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor);
167 | settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath);
168 | settings.noodleThickness = EditorGUILayout.FloatField(new GUIContent("Noodle thickness", "Noodle Thickness of the node connections"), settings.noodleThickness);
169 | settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke);
170 | settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips);
171 | 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);
172 | settings.createFilter = EditorGUILayout.Toggle(new GUIContent("Create Filter", "Only show nodes that are compatible with the selected port"), settings.createFilter);
173 |
174 | //END
175 | if (GUI.changed) {
176 | SavePrefs(key, settings);
177 | NodeEditorWindow.RepaintAll();
178 | }
179 | EditorGUILayout.Space();
180 | }
181 |
182 | private static void TypeColorsGUI(string key, Settings settings) {
183 | //Label
184 | EditorGUILayout.LabelField("Types", EditorStyles.boldLabel);
185 |
186 | //Clone keys so we can enumerate the dictionary and make changes.
187 | var typeColorKeys = new List(typeColors.Keys);
188 |
189 | //Display type colors. Save them if they are edited by the user
190 | foreach (var type in typeColorKeys) {
191 | string typeColorKey = NodeEditorUtilities.PrettyName(type);
192 | Color col = typeColors[type];
193 | EditorGUI.BeginChangeCheck();
194 | EditorGUILayout.BeginHorizontal();
195 | col = EditorGUILayout.ColorField(typeColorKey, col);
196 | EditorGUILayout.EndHorizontal();
197 | if (EditorGUI.EndChangeCheck()) {
198 | typeColors[type] = col;
199 | if (settings.typeColors.ContainsKey(typeColorKey)) settings.typeColors[typeColorKey] = col;
200 | else settings.typeColors.Add(typeColorKey, col);
201 | SavePrefs(key, settings);
202 | NodeEditorWindow.RepaintAll();
203 | }
204 | }
205 | }
206 |
207 | /// Load prefs if they exist. Create if they don't
208 | private static Settings LoadPrefs() {
209 | // Create settings if it doesn't exist
210 | if (!EditorPrefs.HasKey(lastKey)) {
211 | if (lastEditor != null) EditorPrefs.SetString(lastKey, JsonUtility.ToJson(lastEditor.GetDefaultPreferences()));
212 | else EditorPrefs.SetString(lastKey, JsonUtility.ToJson(new Settings()));
213 | }
214 | return JsonUtility.FromJson(EditorPrefs.GetString(lastKey));
215 | }
216 |
217 | /// Delete all prefs
218 | public static void ResetPrefs() {
219 | if (EditorPrefs.HasKey(lastKey)) EditorPrefs.DeleteKey(lastKey);
220 | if (settings.ContainsKey(lastKey)) settings.Remove(lastKey);
221 | typeColors = new Dictionary();
222 | VerifyLoaded();
223 | NodeEditorWindow.RepaintAll();
224 | }
225 |
226 | /// Save preferences in EditorPrefs
227 | private static void SavePrefs(string key, Settings settings) {
228 | EditorPrefs.SetString(key, JsonUtility.ToJson(settings));
229 | }
230 |
231 | /// Check if we have loaded settings for given key. If not, load them
232 | private static void VerifyLoaded() {
233 | if (!settings.ContainsKey(lastKey)) settings.Add(lastKey, LoadPrefs());
234 | }
235 |
236 | /// Return color based on type
237 | public static Color GetTypeColor(System.Type type) {
238 | VerifyLoaded();
239 | if (type == null) return Color.gray;
240 | Color col;
241 | if (!typeColors.TryGetValue(type, out col)) {
242 | string typeName = type.PrettyName();
243 | if (settings[lastKey].typeColors.ContainsKey(typeName)) typeColors.Add(type, settings[lastKey].typeColors[typeName]);
244 | else {
245 | #if UNITY_5_4_OR_NEWER
246 | UnityEngine.Random.State oldState = UnityEngine.Random.state;
247 | UnityEngine.Random.InitState(typeName.GetHashCode());
248 | #else
249 | int oldSeed = UnityEngine.Random.seed;
250 | UnityEngine.Random.seed = typeName.GetHashCode();
251 | #endif
252 | col = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value);
253 | typeColors.Add(type, col);
254 | #if UNITY_5_4_OR_NEWER
255 | UnityEngine.Random.state = oldState;
256 | #else
257 | UnityEngine.Random.seed = oldSeed;
258 | #endif
259 | }
260 | }
261 | return col;
262 | }
263 | }
264 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorPreferences.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6b1f47e387a6f714c9f2ff82a6888c85
3 | timeCreated: 1507920216
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorReflection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Reflection;
6 | using UnityEditor;
7 | using UnityEngine;
8 | #if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
9 | using GenericMenu = XNodeEditor.AdvancedGenericMenu;
10 | #endif
11 |
12 | namespace XNodeEditor {
13 | /// Contains reflection-related extensions built for xNode
14 | public static class NodeEditorReflection {
15 | [NonSerialized] private static Dictionary nodeTint;
16 | [NonSerialized] private static Dictionary nodeWidth;
17 | /// All available node types
18 | public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } }
19 |
20 | [NonSerialized] private static Type[] _nodeTypes = null;
21 |
22 | /// 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.
23 | public static Func GetIsDockedDelegate(this EditorWindow window) {
24 | BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
25 | MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true);
26 | return (Func) Delegate.CreateDelegate(typeof(Func), window, isDockedMethod);
27 | }
28 |
29 | public static Type[] GetNodeTypes() {
30 | //Get all classes deriving from Node via reflection
31 | return GetDerivedTypes(typeof(XNode.Node));
32 | }
33 |
34 | /// Custom node tint colors defined with [NodeColor(r, g, b)]
35 | public static bool TryGetAttributeTint(this Type nodeType, out Color tint) {
36 | if (nodeTint == null) {
37 | CacheAttributes(ref nodeTint, x => x.color);
38 | }
39 | return nodeTint.TryGetValue(nodeType, out tint);
40 | }
41 |
42 | /// Get custom node widths defined with [NodeWidth(width)]
43 | public static bool TryGetAttributeWidth(this Type nodeType, out int width) {
44 | if (nodeWidth == null) {
45 | CacheAttributes(ref nodeWidth, x => x.width);
46 | }
47 | return nodeWidth.TryGetValue(nodeType, out width);
48 | }
49 |
50 | private static void CacheAttributes(ref Dictionary dict, Func getter) where A : Attribute {
51 | dict = new Dictionary();
52 | for (int i = 0; i < nodeTypes.Length; i++) {
53 | object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true);
54 | if (attribs == null || attribs.Length == 0) continue;
55 | A attrib = attribs[0] as A;
56 | dict.Add(nodeTypes[i], getter(attrib));
57 | }
58 | }
59 |
60 | /// Get FieldInfo of a field, including those that are private and/or inherited
61 | public static FieldInfo GetFieldInfo(this Type type, string fieldName) {
62 | // If we can't find field in the first run, it's probably a private field in a base class.
63 | FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
64 | // Search base classes for private fields only. Public fields are found above
65 | while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
66 | return field;
67 | }
68 |
69 | /// Get all classes deriving from baseType via reflection
70 | public static Type[] GetDerivedTypes(this Type baseType) {
71 | List types = new List();
72 | System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
73 | foreach (Assembly assembly in assemblies) {
74 | try {
75 | types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
76 | } catch (ReflectionTypeLoadException) { }
77 | }
78 | return types.ToArray();
79 | }
80 |
81 | /// Find methods marked with the [ContextMenu] attribute and add them to the context menu
82 | public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) {
83 | KeyValuePair[] items = GetContextMenuMethods(obj);
84 | if (items.Length != 0) {
85 | contextMenu.AddSeparator("");
86 | List invalidatedEntries = new List();
87 | foreach (KeyValuePair checkValidate in items) {
88 | if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) {
89 | invalidatedEntries.Add(checkValidate.Key.menuItem);
90 | }
91 | }
92 | for (int i = 0; i < items.Length; i++) {
93 | KeyValuePair kvp = items[i];
94 | if (invalidatedEntries.Contains(kvp.Key.menuItem)) {
95 | contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem));
96 | } else {
97 | contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null));
98 | }
99 | }
100 | }
101 | }
102 |
103 | /// Call OnValidate on target
104 | public static void TriggerOnValidate(this UnityEngine.Object target) {
105 | System.Reflection.MethodInfo onValidate = null;
106 | if (target != null) {
107 | onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
108 | if (onValidate != null) onValidate.Invoke(target, null);
109 | }
110 | }
111 |
112 | public static KeyValuePair[] GetContextMenuMethods(object obj) {
113 | Type type = obj.GetType();
114 | MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
115 | List> kvp = new List>();
116 | for (int i = 0; i < methods.Length; i++) {
117 | ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray();
118 | if (attribs == null || attribs.Length == 0) continue;
119 | if (methods[i].GetParameters().Length != 0) {
120 | Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands.");
121 | continue;
122 | }
123 | if (methods[i].IsStatic) {
124 | Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " is static and cannot be used for context menu commands.");
125 | continue;
126 | }
127 |
128 | for (int k = 0; k < attribs.Length; k++) {
129 | kvp.Add(new KeyValuePair(attribs[k], methods[i]));
130 | }
131 | }
132 | #if UNITY_5_5_OR_NEWER
133 | //Sort menu items
134 | kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority));
135 | #endif
136 | return kvp.ToArray();
137 | }
138 |
139 | /// Very crude. Uses a lot of reflection.
140 | public static void OpenPreferences() {
141 | try {
142 | #if UNITY_2018_3_OR_NEWER
143 | SettingsService.OpenUserPreferences("Preferences/Node Editor");
144 | #else
145 | //Open preferences window
146 | Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow));
147 | Type type = assembly.GetType("UnityEditor.PreferencesWindow");
148 | type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
149 |
150 | //Get the window
151 | EditorWindow window = EditorWindow.GetWindow(type);
152 |
153 | //Make sure custom sections are added (because waiting for it to happen automatically is too slow)
154 | FieldInfo refreshField = type.GetField("m_RefreshCustomPreferences", BindingFlags.NonPublic | BindingFlags.Instance);
155 | if ((bool) refreshField.GetValue(window)) {
156 | type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null);
157 | refreshField.SetValue(window, false);
158 | }
159 |
160 | //Get sections
161 | FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic);
162 | IList sections = sectionsField.GetValue(window) as IList;
163 |
164 | //Iterate through sections and check contents
165 | Type sectionType = sectionsField.FieldType.GetGenericArguments() [0];
166 | FieldInfo sectionContentField = sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public);
167 | for (int i = 0; i < sections.Count; i++) {
168 | GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent;
169 | if (sectionContent.text == "Node Editor") {
170 | //Found contents - Set index
171 | FieldInfo sectionIndexField = type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic);
172 | sectionIndexField.SetValue(window, i);
173 | return;
174 | }
175 | }
176 | #endif
177 | } catch (Exception e) {
178 | Debug.LogError(e);
179 | Debug.LogWarning("Unity has changed around internally. Can't open properties through reflection. Please contact xNode developer and supply unity version number.");
180 | }
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorReflection.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c78a0fa4a13abcd408ebe73006b7b1bb
3 | timeCreated: 1505419458
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorResources.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 |
4 | namespace XNodeEditor {
5 | public static class NodeEditorResources {
6 | // Textures
7 | public static Texture2D dot { get { return _dot != null ? _dot : _dot = Resources.Load("xnode_dot"); } }
8 | private static Texture2D _dot;
9 | public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load("xnode_dot_outer"); } }
10 | private static Texture2D _dotOuter;
11 | public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load("xnode_node"); } }
12 | private static Texture2D _nodeBody;
13 | public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load("xnode_node_highlight"); } }
14 | private static Texture2D _nodeHighlight;
15 |
16 | // Styles
17 | public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } }
18 | public static Styles _styles = null;
19 | public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } }
20 | public class Styles {
21 | public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
22 |
23 | public Styles() {
24 | GUIStyle baseStyle = new GUIStyle("Label");
25 | baseStyle.fixedHeight = 18;
26 |
27 | inputPort = new GUIStyle(baseStyle);
28 | inputPort.alignment = TextAnchor.UpperLeft;
29 | inputPort.padding.left = 0;
30 | inputPort.active.background = dot;
31 | inputPort.normal.background = dotOuter;
32 |
33 | outputPort = new GUIStyle(baseStyle);
34 | outputPort.alignment = TextAnchor.UpperRight;
35 | outputPort.padding.right = 0;
36 | outputPort.active.background = dot;
37 | outputPort.normal.background = dotOuter;
38 |
39 | nodeHeader = new GUIStyle();
40 | nodeHeader.alignment = TextAnchor.MiddleCenter;
41 | nodeHeader.fontStyle = FontStyle.Bold;
42 | nodeHeader.normal.textColor = Color.white;
43 |
44 | nodeBody = new GUIStyle();
45 | nodeBody.normal.background = NodeEditorResources.nodeBody;
46 | nodeBody.border = new RectOffset(32, 32, 32, 32);
47 | nodeBody.padding = new RectOffset(16, 16, 4, 16);
48 |
49 | nodeHighlight = new GUIStyle();
50 | nodeHighlight.normal.background = NodeEditorResources.nodeHighlight;
51 | nodeHighlight.border = new RectOffset(32, 32, 32, 32);
52 |
53 | tooltip = new GUIStyle("helpBox");
54 | tooltip.alignment = TextAnchor.MiddleCenter;
55 | }
56 | }
57 |
58 | public static Texture2D GenerateGridTexture(Color line, Color bg) {
59 | Texture2D tex = new Texture2D(64, 64);
60 | Color[] cols = new Color[64 * 64];
61 | for (int y = 0; y < 64; y++) {
62 | for (int x = 0; x < 64; x++) {
63 | Color col = bg;
64 | if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f);
65 | if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f);
66 | cols[(y * 64) + x] = col;
67 | }
68 | }
69 | tex.SetPixels(cols);
70 | tex.wrapMode = TextureWrapMode.Repeat;
71 | tex.filterMode = FilterMode.Bilinear;
72 | tex.name = "Grid";
73 | tex.Apply();
74 | return tex;
75 | }
76 |
77 | public static Texture2D GenerateCrossTexture(Color line) {
78 | Texture2D tex = new Texture2D(64, 64);
79 | Color[] cols = new Color[64 * 64];
80 | for (int y = 0; y < 64; y++) {
81 | for (int x = 0; x < 64; x++) {
82 | Color col = line;
83 | if (y != 31 && x != 31) col.a = 0;
84 | cols[(y * 64) + x] = col;
85 | }
86 | }
87 | tex.SetPixels(cols);
88 | tex.wrapMode = TextureWrapMode.Clamp;
89 | tex.filterMode = FilterMode.Bilinear;
90 | tex.name = "Grid";
91 | tex.Apply();
92 | return tex;
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorResources.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 69f55d341299026489b29443c3dd13d1
3 | timeCreated: 1505418919
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Text;
7 | using UnityEditor;
8 | using UnityEngine;
9 | using Object = UnityEngine.Object;
10 |
11 | namespace XNodeEditor {
12 | /// A set of editor-only utilities and extensions for xNode
13 | public static class NodeEditorUtilities {
14 |
15 | /// C#'s Script Icon [The one MonoBhevaiour Scripts have].
16 | private static Texture2D scriptIcon = (EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D);
17 |
18 | /// Saves Attribute from Type+Field for faster lookup. Resets on recompiles.
19 | private static Dictionary>> typeAttributes = new Dictionary>>();
20 |
21 | /// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles.
22 | private static Dictionary>> typeOrderedPropertyAttributes = new Dictionary>>();
23 |
24 | public static bool GetAttrib(Type classType, out T attribOut) where T : Attribute {
25 | object[] attribs = classType.GetCustomAttributes(typeof(T), false);
26 | return GetAttrib(attribs, out attribOut);
27 | }
28 |
29 | public static bool GetAttrib(object[] attribs, out T attribOut) where T : Attribute {
30 | for (int i = 0; i < attribs.Length; i++) {
31 | if (attribs[i] is T) {
32 | attribOut = attribs[i] as T;
33 | return true;
34 | }
35 | }
36 | attribOut = null;
37 | return false;
38 | }
39 |
40 | public static bool GetAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute {
41 | // If we can't find field in the first run, it's probably a private field in a base class.
42 | FieldInfo field = classType.GetFieldInfo(fieldName);
43 | // This shouldn't happen. Ever.
44 | if (field == null) {
45 | Debug.LogWarning("Field " + fieldName + " couldnt be found");
46 | attribOut = null;
47 | return false;
48 | }
49 | object[] attribs = field.GetCustomAttributes(typeof(T), true);
50 | return GetAttrib(attribs, out attribOut);
51 | }
52 |
53 | public static bool HasAttrib(object[] attribs) where T : Attribute {
54 | for (int i = 0; i < attribs.Length; i++) {
55 | if (attribs[i].GetType() == typeof(T)) {
56 | return true;
57 | }
58 | }
59 | return false;
60 | }
61 |
62 | public static bool GetCachedAttrib(Type classType, string fieldName, out T attribOut) where T : Attribute {
63 | Dictionary> typeFields;
64 | if (!typeAttributes.TryGetValue(classType, out typeFields)) {
65 | typeFields = new Dictionary>();
66 | typeAttributes.Add(classType, typeFields);
67 | }
68 |
69 | Dictionary typeTypes;
70 | if (!typeFields.TryGetValue(fieldName, out typeTypes)) {
71 | typeTypes = new Dictionary();
72 | typeFields.Add(fieldName, typeTypes);
73 | }
74 |
75 | Attribute attr;
76 | if (!typeTypes.TryGetValue(typeof(T), out attr)) {
77 | if (GetAttrib(classType, fieldName, out attribOut)) {
78 | typeTypes.Add(typeof(T), attribOut);
79 | return true;
80 | } else typeTypes.Add(typeof(T), null);
81 | }
82 |
83 | if (attr == null) {
84 | attribOut = null;
85 | return false;
86 | }
87 |
88 | attribOut = attr as T;
89 | return true;
90 | }
91 |
92 | public static List GetCachedPropertyAttribs(Type classType, string fieldName) {
93 | Dictionary> typeFields;
94 | if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) {
95 | typeFields = new Dictionary>();
96 | typeOrderedPropertyAttributes.Add(classType, typeFields);
97 | }
98 |
99 | List typeAttributes;
100 | if (!typeFields.TryGetValue(fieldName, out typeAttributes)) {
101 | FieldInfo field = classType.GetFieldInfo(fieldName);
102 | object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true);
103 | typeAttributes = attribs.Cast().Reverse().ToList(); //Unity draws them in reverse
104 | typeFields.Add(fieldName, typeAttributes);
105 | }
106 |
107 | return typeAttributes;
108 | }
109 |
110 | public static bool IsMac() {
111 | #if UNITY_2017_1_OR_NEWER
112 | return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX;
113 | #else
114 | return SystemInfo.operatingSystem.StartsWith("Mac");
115 | #endif
116 | }
117 |
118 | /// Returns true if this can be casted to
119 | public static bool IsCastableTo(this Type from, Type to) {
120 | if (to.IsAssignableFrom(from)) return true;
121 | var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
122 | .Where(
123 | m => m.ReturnType == to &&
124 | (m.Name == "op_Implicit" ||
125 | m.Name == "op_Explicit")
126 | );
127 | return methods.Count() > 0;
128 | }
129 |
130 | ///
131 | /// Looking for ports with value Type compatible with a given type.
132 | ///
133 | /// Node to search
134 | /// Type to find compatiblities
135 | ///
136 | /// True if NodeType has some port with value type compatible
137 | public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
138 | Type findType = typeof(XNode.Node.InputAttribute);
139 | if (direction == XNode.NodePort.IO.Output)
140 | findType = typeof(XNode.Node.OutputAttribute);
141 |
142 | //Get All fields from node type and we go filter only field with portAttribute.
143 | //This way is possible to know the values of the all ports and if have some with compatible value tue
144 | foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) {
145 | var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault();
146 | if (portAttribute != null) {
147 | if (IsCastableTo(f.FieldType, compatibleType)) {
148 | return true;
149 | }
150 | }
151 | }
152 |
153 | return false;
154 | }
155 |
156 | ///
157 | /// Filter only node types that contains some port value type compatible with an given type
158 | ///
159 | /// List with all nodes type to filter
160 | /// Compatible Type to Filter
161 | /// Return Only Node Types with ports compatible, or an empty list
162 | public static List GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
163 | //Result List
164 | List filteredTypes = new List();
165 |
166 | //Return empty list
167 | if (nodeTypes == null) { return filteredTypes; }
168 | if (compatibleType == null) { return filteredTypes; }
169 |
170 | //Find compatiblity
171 | foreach (Type findType in nodeTypes) {
172 | if (HasCompatiblePortType(findType, compatibleType, direction)) {
173 | filteredTypes.Add(findType);
174 | }
175 | }
176 |
177 | return filteredTypes;
178 | }
179 |
180 |
181 | /// Return a prettiefied type name.
182 | public static string PrettyName(this Type type) {
183 | if (type == null) return "null";
184 | if (type == typeof(System.Object)) return "object";
185 | if (type == typeof(float)) return "float";
186 | else if (type == typeof(int)) return "int";
187 | else if (type == typeof(long)) return "long";
188 | else if (type == typeof(double)) return "double";
189 | else if (type == typeof(string)) return "string";
190 | else if (type == typeof(bool)) return "bool";
191 | else if (type.IsGenericType) {
192 | string s = "";
193 | Type genericType = type.GetGenericTypeDefinition();
194 | if (genericType == typeof(List<>)) s = "List";
195 | else s = type.GetGenericTypeDefinition().ToString();
196 |
197 | Type[] types = type.GetGenericArguments();
198 | string[] stypes = new string[types.Length];
199 | for (int i = 0; i < types.Length; i++) {
200 | stypes[i] = types[i].PrettyName();
201 | }
202 | return s + "<" + string.Join(", ", stypes) + ">";
203 | } else if (type.IsArray) {
204 | string rank = "";
205 | for (int i = 1; i < type.GetArrayRank(); i++) {
206 | rank += ",";
207 | }
208 | Type elementType = type.GetElementType();
209 | if (!elementType.IsArray) return elementType.PrettyName() + "[" + rank + "]";
210 | else {
211 | string s = elementType.PrettyName();
212 | int i = s.IndexOf('[');
213 | return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i);
214 | }
215 | } else return type.ToString();
216 | }
217 |
218 | /// Returns the default name for the node type.
219 | public static string NodeDefaultName(Type type) {
220 | string typeName = type.Name;
221 | // Automatically remove redundant 'Node' postfix
222 | if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node"));
223 | typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName);
224 | return typeName;
225 | }
226 |
227 | /// Returns the default creation path for the node type.
228 | public static string NodeDefaultPath(Type type) {
229 | string typePath = type.ToString().Replace('.', '/');
230 | // Automatically remove redundant 'Node' postfix
231 | if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node"));
232 | typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath);
233 | return typePath;
234 | }
235 |
236 | /// Creates a new C# Class.
237 | [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)]
238 | private static void CreateNode() {
239 | string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs");
240 | if (guids.Length == 0) {
241 | Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database");
242 | return;
243 | }
244 | string path = AssetDatabase.GUIDToAssetPath(guids[0]);
245 | CreateFromTemplate(
246 | "NewNode.cs",
247 | path
248 | );
249 | }
250 |
251 | /// Creates a new C# Class.
252 | [MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)]
253 | private static void CreateGraph() {
254 | string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs");
255 | if (guids.Length == 0) {
256 | Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database");
257 | return;
258 | }
259 | string path = AssetDatabase.GUIDToAssetPath(guids[0]);
260 | CreateFromTemplate(
261 | "NewNodeGraph.cs",
262 | path
263 | );
264 | }
265 |
266 | public static void CreateFromTemplate(string initialName, string templatePath) {
267 | ProjectWindowUtil.StartNameEditingIfProjectWindowExists(
268 | 0,
269 | ScriptableObject.CreateInstance(),
270 | initialName,
271 | scriptIcon,
272 | templatePath
273 | );
274 | }
275 |
276 | /// Inherits from EndNameAction, must override EndNameAction.Action
277 | public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction {
278 | public override void Action(int instanceId, string pathName, string resourceFile) {
279 | Object o = CreateScript(pathName, resourceFile);
280 | ProjectWindowUtil.ShowCreatedAsset(o);
281 | }
282 | }
283 |
284 | /// Creates Script from Template's path.
285 | internal static UnityEngine.Object CreateScript(string pathName, string templatePath) {
286 | string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty);
287 | string templateText = string.Empty;
288 |
289 | UTF8Encoding encoding = new UTF8Encoding(true, false);
290 |
291 | if (File.Exists(templatePath)) {
292 | /// Read procedures.
293 | StreamReader reader = new StreamReader(templatePath);
294 | templateText = reader.ReadToEnd();
295 | reader.Close();
296 |
297 | templateText = templateText.Replace("#SCRIPTNAME#", className);
298 | templateText = templateText.Replace("#NOTRIM#", string.Empty);
299 | /// You can replace as many tags you make on your templates, just repeat Replace function
300 | /// e.g.:
301 | /// templateText = templateText.Replace("#NEWTAG#", "MyText");
302 |
303 | /// Write procedures.
304 |
305 | StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding);
306 | writer.Write(templateText);
307 | writer.Close();
308 |
309 | AssetDatabase.ImportAsset(pathName);
310 | return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object));
311 | } else {
312 | Debug.LogError(string.Format("The template file was not found: {0}", templatePath));
313 | return null;
314 | }
315 | }
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorUtilities.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 120960fe5b50aba418a8e8ad3c4c4bc8
3 | timeCreated: 1506073499
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorWindow.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEditor;
3 | using UnityEditor.Callbacks;
4 | using UnityEngine;
5 | using System;
6 | using Object = UnityEngine.Object;
7 |
8 | namespace XNodeEditor {
9 | [InitializeOnLoad]
10 | public partial class NodeEditorWindow : EditorWindow {
11 | public static NodeEditorWindow current;
12 |
13 | /// Stores node positions for all nodePorts.
14 | public Dictionary portConnectionPoints { get { return _portConnectionPoints; } }
15 | private Dictionary _portConnectionPoints = new Dictionary();
16 | [SerializeField] private NodePortReference[] _references = new NodePortReference[0];
17 | [SerializeField] private Rect[] _rects = new Rect[0];
18 |
19 | private Func isDocked {
20 | get {
21 | if (_isDocked == null) _isDocked = this.GetIsDockedDelegate();
22 | return _isDocked;
23 | }
24 | }
25 | private Func _isDocked;
26 |
27 | [System.Serializable] private class NodePortReference {
28 | [SerializeField] private XNode.Node _node;
29 | [SerializeField] private string _name;
30 |
31 | public NodePortReference(XNode.NodePort nodePort) {
32 | _node = nodePort.node;
33 | _name = nodePort.fieldName;
34 | }
35 |
36 | public XNode.NodePort GetNodePort() {
37 | if (_node == null) {
38 | return null;
39 | }
40 | return _node.GetPort(_name);
41 | }
42 | }
43 |
44 | private void OnDisable() {
45 | // Cache portConnectionPoints before serialization starts
46 | int count = portConnectionPoints.Count;
47 | _references = new NodePortReference[count];
48 | _rects = new Rect[count];
49 | int index = 0;
50 | foreach (var portConnectionPoint in portConnectionPoints) {
51 | _references[index] = new NodePortReference(portConnectionPoint.Key);
52 | _rects[index] = portConnectionPoint.Value;
53 | index++;
54 | }
55 | }
56 |
57 | private void OnEnable() {
58 | // Reload portConnectionPoints if there are any
59 | int length = _references.Length;
60 | if (length == _rects.Length) {
61 | for (int i = 0; i < length; i++) {
62 | XNode.NodePort nodePort = _references[i].GetNodePort();
63 | if (nodePort != null)
64 | _portConnectionPoints.Add(nodePort, _rects[i]);
65 | }
66 | }
67 | }
68 |
69 | public Dictionary nodeSizes { get { return _nodeSizes; } }
70 | private Dictionary _nodeSizes = new Dictionary();
71 | public XNode.NodeGraph graph;
72 | public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } }
73 | private Vector2 _panOffset;
74 | public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } }
75 | private float _zoom = 1;
76 |
77 | void OnFocus() {
78 | current = this;
79 | ValidateGraphEditor();
80 | if (graphEditor != null) {
81 | graphEditor.OnWindowFocus();
82 | if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
83 | }
84 |
85 | dragThreshold = Math.Max(1f, Screen.width / 1000f);
86 | }
87 |
88 | void OnLostFocus() {
89 | if (graphEditor != null) graphEditor.OnWindowFocusLost();
90 | }
91 |
92 | [InitializeOnLoadMethod]
93 | private static void OnLoad() {
94 | Selection.selectionChanged -= OnSelectionChanged;
95 | Selection.selectionChanged += OnSelectionChanged;
96 | }
97 |
98 | /// Handle Selection Change events
99 | private static void OnSelectionChanged() {
100 | XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph;
101 | if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) {
102 | if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph);
103 | }
104 | }
105 |
106 | /// Make sure the graph editor is assigned and to the right object
107 | private void ValidateGraphEditor() {
108 | NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this);
109 | if (this.graphEditor != graphEditor && graphEditor != null) {
110 | this.graphEditor = graphEditor;
111 | graphEditor.OnOpen();
112 | }
113 | }
114 |
115 | /// Create editor window
116 | public static NodeEditorWindow Init() {
117 | NodeEditorWindow w = CreateInstance();
118 | w.titleContent = new GUIContent("xNode");
119 | w.wantsMouseMove = true;
120 | w.Show();
121 | return w;
122 | }
123 |
124 | public void Save() {
125 | if (AssetDatabase.Contains(graph)) {
126 | EditorUtility.SetDirty(graph);
127 | if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
128 | } else SaveAs();
129 | }
130 |
131 | public void SaveAs() {
132 | string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", "");
133 | if (string.IsNullOrEmpty(path)) return;
134 | else {
135 | XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath(path);
136 | if (existingGraph != null) AssetDatabase.DeleteAsset(path);
137 | AssetDatabase.CreateAsset(graph, path);
138 | EditorUtility.SetDirty(graph);
139 | if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
140 | }
141 | }
142 |
143 | private void DraggableWindow(int windowID) {
144 | GUI.DragWindow();
145 | }
146 |
147 | public Vector2 WindowToGridPosition(Vector2 windowPosition) {
148 | return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom;
149 | }
150 |
151 | public Vector2 GridToWindowPosition(Vector2 gridPosition) {
152 | return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom);
153 | }
154 |
155 | public Rect GridToWindowRectNoClipped(Rect gridRect) {
156 | gridRect.position = GridToWindowPositionNoClipped(gridRect.position);
157 | return gridRect;
158 | }
159 |
160 | public Rect GridToWindowRect(Rect gridRect) {
161 | gridRect.position = GridToWindowPosition(gridRect.position);
162 | gridRect.size /= zoom;
163 | return gridRect;
164 | }
165 |
166 | public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) {
167 | Vector2 center = position.size * 0.5f;
168 | // UI Sharpness complete fix - Round final offset not panOffset
169 | float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x));
170 | float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y));
171 | return new Vector2(xOffset, yOffset);
172 | }
173 |
174 | public void SelectNode(XNode.Node node, bool add) {
175 | if (add) {
176 | List selection = new List(Selection.objects);
177 | selection.Add(node);
178 | Selection.objects = selection.ToArray();
179 | } else Selection.objects = new Object[] { node };
180 | }
181 |
182 | public void DeselectNode(XNode.Node node) {
183 | List selection = new List(Selection.objects);
184 | selection.Remove(node);
185 | Selection.objects = selection.ToArray();
186 | }
187 |
188 | [OnOpenAsset(0)]
189 | public static bool OnOpen(int instanceID, int line) {
190 | XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph;
191 | if (nodeGraph != null) {
192 | Open(nodeGraph);
193 | return true;
194 | }
195 | return false;
196 | }
197 |
198 | /// Open the provided graph in the NodeEditor
199 | public static NodeEditorWindow Open(XNode.NodeGraph graph) {
200 | if (!graph) return null;
201 |
202 | NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow;
203 | w.wantsMouseMove = true;
204 | w.graph = graph;
205 | return w;
206 | }
207 |
208 | /// Repaint all open NodeEditorWindows.
209 | public static void RepaintAll() {
210 | NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll();
211 | for (int i = 0; i < windows.Length; i++) {
212 | windows[i].Repaint();
213 | }
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeEditorWindow.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5ce2bf59ec7a25c4ba691cad7819bf38
3 | timeCreated: 1505418450
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeGraphEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using UnityEditor;
4 | using UnityEngine;
5 | #if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
6 | using GenericMenu = XNodeEditor.AdvancedGenericMenu;
7 | #endif
8 |
9 | namespace XNodeEditor {
10 | /// Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor.
11 | [CustomNodeGraphEditor(typeof(XNode.NodeGraph))]
12 | public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase {
13 | [Obsolete("Use window.position instead")]
14 | public Rect position { get { return window.position; } set { window.position = value; } }
15 | /// Are we currently renaming a node?
16 | protected bool isRenaming;
17 |
18 | public virtual void OnGUI() { }
19 |
20 | /// Called when opened by NodeEditorWindow
21 | public virtual void OnOpen() { }
22 |
23 | /// Called when NodeEditorWindow gains focus
24 | public virtual void OnWindowFocus() { }
25 |
26 | /// Called when NodeEditorWindow loses focus
27 | public virtual void OnWindowFocusLost() { }
28 |
29 | public virtual Texture2D GetGridTexture() {
30 | return NodeEditorPreferences.GetSettings().gridTexture;
31 | }
32 |
33 | public virtual Texture2D GetSecondaryGridTexture() {
34 | return NodeEditorPreferences.GetSettings().crossTexture;
35 | }
36 |
37 | /// Return default settings for this graph type. This is the settings the user will load if no previous settings have been saved.
38 | public virtual NodeEditorPreferences.Settings GetDefaultPreferences() {
39 | return new NodeEditorPreferences.Settings();
40 | }
41 |
42 | /// Returns context node menu path. Null or empty strings for hidden nodes.
43 | public virtual string GetNodeMenuName(Type type) {
44 | //Check if type has the CreateNodeMenuAttribute
45 | XNode.Node.CreateNodeMenuAttribute attrib;
46 | if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
47 | return attrib.menuName;
48 | else // Return generated path
49 | return NodeEditorUtilities.NodeDefaultPath(type);
50 | }
51 |
52 | /// The order by which the menu items are displayed.
53 | public virtual int GetNodeMenuOrder(Type type) {
54 | //Check if type has the CreateNodeMenuAttribute
55 | XNode.Node.CreateNodeMenuAttribute attrib;
56 | if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
57 | return attrib.order;
58 | else
59 | return 0;
60 | }
61 |
62 | ///
63 | /// Called before connecting two ports in the graph view to see if the output port is compatible with the input port
64 | ///
65 | public virtual bool CanConnect(XNode.NodePort output, XNode.NodePort input) {
66 | return output.CanConnectTo(input);
67 | }
68 |
69 | ///
70 | /// Add items for the context menu when right-clicking this node.
71 | /// Override to add custom menu items.
72 | ///
73 | ///
74 | /// Use it to filter only nodes with ports value type, compatible with this type
75 | /// Direction of the compatiblity
76 | public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
77 | Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition);
78 |
79 | Type[] nodeTypes;
80 |
81 | if (compatibleType != null && NodeEditorPreferences.GetSettings().createFilter) {
82 | nodeTypes = NodeEditorUtilities.GetCompatibleNodesTypes(NodeEditorReflection.nodeTypes, compatibleType, direction).OrderBy(GetNodeMenuOrder).ToArray();
83 | } else {
84 | nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(GetNodeMenuOrder).ToArray();
85 | }
86 |
87 | for (int i = 0; i < nodeTypes.Length; i++) {
88 | Type type = nodeTypes[i];
89 |
90 | //Get node context menu path
91 | string path = GetNodeMenuName(type);
92 | if (string.IsNullOrEmpty(path)) continue;
93 |
94 | // Check if user is allowed to add more of given node type
95 | XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
96 | bool disallowed = false;
97 | if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) {
98 | int typeCount = target.nodes.Count(x => x.GetType() == type);
99 | if (typeCount >= disallowAttrib.max) disallowed = true;
100 | }
101 |
102 | // Add node entry to context menu
103 | if (disallowed) menu.AddItem(new GUIContent(path), false, null);
104 | else menu.AddItem(new GUIContent(path), false, () => {
105 | XNode.Node node = CreateNode(type, pos);
106 | if (node != null) NodeEditorWindow.current.AutoConnect(node); // handle null nodes to avoid nullref exceptions
107 | });
108 | }
109 | menu.AddSeparator("");
110 | if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos));
111 | else menu.AddDisabledItem(new GUIContent("Paste"));
112 | menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences());
113 | menu.AddCustomContextMenuItems(target);
114 | }
115 |
116 | /// Returned gradient is used to color noodles
117 | /// The output this noodle comes from. Never null.
118 | /// The output this noodle comes from. Can be null if we are dragging the noodle.
119 | public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) {
120 | Gradient grad = new Gradient();
121 |
122 | // If dragging the noodle, draw solid, slightly transparent
123 | if (input == null) {
124 | Color a = GetTypeColor(output.ValueType);
125 | grad.SetKeys(
126 | new GradientColorKey[] { new GradientColorKey(a, 0f) },
127 | new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) }
128 | );
129 | }
130 | // If normal, draw gradient fading from one input color to the other
131 | else {
132 | Color a = GetTypeColor(output.ValueType);
133 | Color b = GetTypeColor(input.ValueType);
134 | // If any port is hovered, tint white
135 | if (window.hoveredPort == output || window.hoveredPort == input) {
136 | a = Color.Lerp(a, Color.white, 0.8f);
137 | b = Color.Lerp(b, Color.white, 0.8f);
138 | }
139 | grad.SetKeys(
140 | new GradientColorKey[] { new GradientColorKey(a, 0f), new GradientColorKey(b, 1f) },
141 | new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }
142 | );
143 | }
144 | return grad;
145 | }
146 |
147 | /// Returned float is used for noodle thickness
148 | /// The output this noodle comes from. Never null.
149 | /// The output this noodle comes from. Can be null if we are dragging the noodle.
150 | public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) {
151 | return NodeEditorPreferences.GetSettings().noodleThickness;
152 | }
153 |
154 | public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) {
155 | return NodeEditorPreferences.GetSettings().noodlePath;
156 | }
157 |
158 | public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) {
159 | return NodeEditorPreferences.GetSettings().noodleStroke;
160 | }
161 |
162 | /// Returned color is used to color ports
163 | public virtual Color GetPortColor(XNode.NodePort port) {
164 | return GetTypeColor(port.ValueType);
165 | }
166 |
167 | ///
168 | /// The returned Style is used to configure the paddings and icon texture of the ports.
169 | /// Use these properties to customize your port style.
170 | ///
171 | /// The properties used is:
172 | /// [Left and Right], [Background] = border texture,
173 | /// and [Background] = dot texture;
174 | ///
175 | /// the owner of the style
176 | ///
177 | public virtual GUIStyle GetPortStyle(XNode.NodePort port) {
178 | if (port.direction == XNode.NodePort.IO.Input)
179 | return NodeEditorResources.styles.inputPort;
180 |
181 | return NodeEditorResources.styles.outputPort;
182 | }
183 |
184 | /// The returned color is used to color the background of the door.
185 | /// Usually used for outer edge effect
186 | public virtual Color GetPortBackgroundColor(XNode.NodePort port) {
187 | return Color.gray;
188 | }
189 |
190 | /// Returns generated color for a type. This color is editable in preferences
191 | public virtual Color GetTypeColor(Type type) {
192 | return NodeEditorPreferences.GetTypeColor(type);
193 | }
194 |
195 | /// Override to display custom tooltips
196 | public virtual string GetPortTooltip(XNode.NodePort port) {
197 | Type portType = port.ValueType;
198 | string tooltip = "";
199 | tooltip = portType.PrettyName();
200 | if (port.IsOutput) {
201 | object obj = port.node.GetValue(port);
202 | tooltip += " = " + (obj != null ? obj.ToString() : "null");
203 | }
204 | return tooltip;
205 | }
206 |
207 | /// Deal with objects dropped into the graph through DragAndDrop
208 | public virtual void OnDropObjects(UnityEngine.Object[] objects) {
209 | if (GetType() != typeof(NodeGraphEditor)) Debug.Log("No OnDropObjects override defined for " + GetType());
210 | }
211 |
212 | /// Create a node and save it in the graph asset
213 | public virtual XNode.Node CreateNode(Type type, Vector2 position) {
214 | Undo.RecordObject(target, "Create Node");
215 | XNode.Node node = target.AddNode(type);
216 | if (node == null) return null; // handle null nodes to avoid nullref exceptions
217 | Undo.RegisterCreatedObjectUndo(node, "Create Node");
218 | node.position = position;
219 | if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
220 | if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target);
221 | if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
222 | NodeEditorWindow.RepaintAll();
223 | return node;
224 | }
225 |
226 | /// Creates a copy of the original node in the graph
227 | public virtual XNode.Node CopyNode(XNode.Node original) {
228 | Undo.RecordObject(target, "Duplicate Node");
229 | XNode.Node node = target.CopyNode(original);
230 | Undo.RegisterCreatedObjectUndo(node, "Duplicate Node");
231 | node.name = original.name;
232 | if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target);
233 | if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
234 | return node;
235 | }
236 |
237 | /// Return false for nodes that can't be removed
238 | public virtual bool CanRemove(XNode.Node node) {
239 | // Check graph attributes to see if this node is required
240 | Type graphType = target.GetType();
241 | XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
242 | graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute);
243 | if (attribs.Any(x => x.Requires(node.GetType()))) {
244 | if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) {
245 | return false;
246 | }
247 | }
248 | return true;
249 | }
250 |
251 | /// Safely remove a node and all its connections.
252 | public virtual void RemoveNode(XNode.Node node) {
253 | if (!CanRemove(node)) return;
254 |
255 | // Remove the node
256 | Undo.RecordObject(node, "Delete Node");
257 | Undo.RecordObject(target, "Delete Node");
258 | foreach (var port in node.Ports)
259 | foreach (var conn in port.GetConnections())
260 | Undo.RecordObject(conn.node, "Delete Node");
261 | target.RemoveNode(node);
262 | Undo.DestroyObjectImmediate(node);
263 | if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
264 | }
265 |
266 | [AttributeUsage(AttributeTargets.Class)]
267 | public class CustomNodeGraphEditorAttribute : Attribute,
268 | XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib {
269 | private Type inspectedType;
270 | public string editorPrefsKey;
271 | /// Tells a NodeGraphEditor which Graph type it is an editor for
272 | /// Type that this editor can edit
273 | /// Define unique key for unique layout settings instance
274 | public CustomNodeGraphEditorAttribute(Type inspectedType, string editorPrefsKey = "xNode.Settings") {
275 | this.inspectedType = inspectedType;
276 | this.editorPrefsKey = editorPrefsKey;
277 | }
278 |
279 | public Type GetInspectedType() {
280 | return inspectedType;
281 | }
282 | }
283 | }
284 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeGraphEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ddcbb5432255d3247a0718b15a9c193c
3 | timeCreated: 1505462176
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/Editor/NodeGraphImporter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using UnityEditor;
5 | using UnityEditor.Experimental.AssetImporters;
6 | using UnityEngine;
7 | using XNode;
8 |
9 | namespace XNodeEditor {
10 | /// Deals with modified assets
11 | class NodeGraphImporter : AssetPostprocessor {
12 | private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
13 | foreach (string path in importedAssets) {
14 | // Skip processing anything without the .asset extension
15 | if (Path.GetExtension(path) != ".asset") continue;
16 |
17 | // Get the object that is requested for deletion
18 | NodeGraph graph = AssetDatabase.LoadAssetAtPath(path);
19 | if (graph == null) continue;
20 |
21 | // Get attributes
22 | Type graphType = graph.GetType();
23 | NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
24 | graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute);
25 |
26 | Vector2 position = Vector2.zero;
27 | foreach (NodeGraph.RequireNodeAttribute attrib in attribs) {
28 | if (attrib.type0 != null) AddRequired(graph, attrib.type0, ref position);
29 | if (attrib.type1 != null) AddRequired(graph, attrib.type1, ref position);
30 | if (attrib.type2 != null) AddRequired(graph, attrib.type2, ref position);
31 | }
32 | }
33 | }
34 |
35 | private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) {
36 | if (!graph.nodes.Any(x => x.GetType() == type)) {
37 | XNode.Node node = graph.AddNode(type);
38 | node.position = position;
39 | position.x += 200;
40 | if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
41 | if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(graph))) AssetDatabase.AddObjectToAsset(node, graph);
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/Scripts/Editor/NodeGraphImporter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7a816f2790bf3da48a2d6d0035ebc9a0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/RenamePopup.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 |
4 | namespace XNodeEditor {
5 | /// Utility for renaming assets
6 | public class RenamePopup : EditorWindow {
7 | private const string inputControlName = "nameInput";
8 |
9 | public static RenamePopup current { get; private set; }
10 | public Object target;
11 | public string input;
12 |
13 | private bool firstFrame = true;
14 |
15 | /// Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply.
16 | public static RenamePopup Show(Object target, float width = 200) {
17 | RenamePopup window = EditorWindow.GetWindow(true, "Rename " + target.name, true);
18 | if (current != null) current.Close();
19 | current = window;
20 | window.target = target;
21 | window.input = target.name;
22 | window.minSize = new Vector2(100, 44);
23 | window.position = new Rect(0, 0, width, 44);
24 | window.UpdatePositionToMouse();
25 | return window;
26 | }
27 |
28 | private void UpdatePositionToMouse() {
29 | if (Event.current == null) return;
30 | Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
31 | Rect pos = position;
32 | pos.x = mousePoint.x - position.width * 0.5f;
33 | pos.y = mousePoint.y - 10;
34 | position = pos;
35 | }
36 |
37 | private void OnLostFocus() {
38 | // Make the popup close on lose focus
39 | Close();
40 | }
41 |
42 | private void OnGUI() {
43 | if (firstFrame) {
44 | UpdatePositionToMouse();
45 | firstFrame = false;
46 | }
47 | GUI.SetNextControlName(inputControlName);
48 | input = EditorGUILayout.TextField(input);
49 | EditorGUI.FocusTextInControl(inputControlName);
50 | Event e = Event.current;
51 | // If input is empty, revert name to default instead
52 | if (input == null || input.Trim() == "") {
53 | if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) {
54 | target.name = NodeEditorUtilities.NodeDefaultName(target.GetType());
55 | NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename();
56 | if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) {
57 | AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target));
58 | AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
59 | }
60 | Close();
61 | target.TriggerOnValidate();
62 | }
63 | }
64 | // Rename asset to input text
65 | else {
66 | if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) {
67 | target.name = input;
68 | NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename();
69 | if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) {
70 | AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target));
71 | AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
72 | }
73 | Close();
74 | target.TriggerOnValidate();
75 | }
76 | }
77 |
78 | if (e.isKey && e.keyCode == KeyCode.Escape) {
79 | Close();
80 | }
81 | }
82 |
83 | private void OnDestroy() {
84 | EditorGUIUtility.editingTextField = false;
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/Scripts/Editor/RenamePopup.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4ef3ddc25518318469bce838980c64be
3 | timeCreated: 1552067957
4 | licenseType: Free
5 | MonoImporter:
6 | externalObjects: {}
7 | serializedVersion: 2
8 | defaultReferences: []
9 | executionOrder: 0
10 | icon: {instanceID: 0}
11 | userData:
12 | assetBundleName:
13 | assetBundleVariant:
14 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 964fc201163fe884ca6a20094b6f3b49
3 | folderAsset: yes
4 | timeCreated: 1506110871
5 | licenseType: Free
6 | DefaultImporter:
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/ScriptTemplates.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 86b677955452bb5449f9f4dd47b6ddfe
3 | folderAsset: yes
4 | timeCreated: 1519049391
5 | licenseType: Free
6 | DefaultImporter:
7 | externalObjects: {}
8 | userData:
9 | assetBundleName:
10 | assetBundleVariant:
11 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using XNode;
5 |
6 | [CreateAssetMenu]
7 | public class #SCRIPTNAME# : NodeGraph {
8 | #NOTRIM#
9 | }
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8165767f64da7d94e925f61a38da668c
3 | timeCreated: 1519049802
4 | licenseType: Free
5 | TextScriptImporter:
6 | externalObjects: {}
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using XNode;
5 |
6 | public class #SCRIPTNAME# : Node {
7 |
8 | // Use this for initialization
9 | protected override void Init() {
10 | base.Init();
11 | #NOTRIM#
12 | }
13 |
14 | // Return the correct value of an output port when requested
15 | public override object GetValue(NodePort port) {
16 | return null; // Replace this
17 | }
18 | }
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 85f6f570600a1a44d8e734cb111a8b89
3 | timeCreated: 1519049802
4 | licenseType: Free
5 | TextScriptImporter:
6 | externalObjects: {}
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Siccity/xNode/d6effd70f5574369e3415c423ef3e621ea309564/Scripts/Editor/Resources/xnode_dot.png
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_dot.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 75a1fe0b102226a418486ed823c9a7fb
3 | timeCreated: 1506110357
4 | licenseType: Free
5 | TextureImporter:
6 | fileIDToRecycleName: {}
7 | serializedVersion: 4
8 | mipmaps:
9 | mipMapMode: 0
10 | enableMipMap: 0
11 | sRGBTexture: 0
12 | linearTexture: 0
13 | fadeOut: 0
14 | borderMipMap: 0
15 | mipMapsPreserveCoverage: 0
16 | alphaTestReferenceValue: 0.5
17 | mipMapFadeDistanceStart: 1
18 | mipMapFadeDistanceEnd: 3
19 | bumpmap:
20 | convertToNormalMap: 0
21 | externalNormalMap: 0
22 | heightScale: 0.25
23 | normalMapFilter: 0
24 | isReadable: 0
25 | grayScaleToAlpha: 0
26 | generateCubemap: 6
27 | cubemapConvolution: 0
28 | seamlessCubemap: 0
29 | textureFormat: 1
30 | maxTextureSize: 2048
31 | textureSettings:
32 | serializedVersion: 2
33 | filterMode: -1
34 | aniso: 1
35 | mipBias: -1
36 | wrapU: 1
37 | wrapV: -1
38 | wrapW: -1
39 | nPOTScale: 0
40 | lightmap: 0
41 | compressionQuality: 50
42 | spriteMode: 0
43 | spriteExtrude: 1
44 | spriteMeshType: 1
45 | alignment: 0
46 | spritePivot: {x: 0.5, y: 0.5}
47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
48 | spritePixelsToUnits: 100
49 | alphaUsage: 1
50 | alphaIsTransparency: 1
51 | spriteTessellationDetail: -1
52 | textureType: 2
53 | textureShape: 1
54 | maxTextureSizeSet: 0
55 | compressionQualitySet: 0
56 | textureFormatSet: 0
57 | platformSettings:
58 | - buildTarget: DefaultTexturePlatform
59 | maxTextureSize: 2048
60 | textureFormat: -1
61 | textureCompression: 1
62 | compressionQuality: 50
63 | crunchedCompression: 0
64 | allowsAlphaSplitting: 0
65 | overridden: 0
66 | - buildTarget: Standalone
67 | maxTextureSize: 2048
68 | textureFormat: -1
69 | textureCompression: 1
70 | compressionQuality: 50
71 | crunchedCompression: 0
72 | allowsAlphaSplitting: 0
73 | overridden: 0
74 | - buildTarget: Android
75 | maxTextureSize: 2048
76 | textureFormat: -1
77 | textureCompression: 1
78 | compressionQuality: 50
79 | crunchedCompression: 0
80 | allowsAlphaSplitting: 0
81 | overridden: 0
82 | - buildTarget: WebGL
83 | maxTextureSize: 2048
84 | textureFormat: -1
85 | textureCompression: 1
86 | compressionQuality: 50
87 | crunchedCompression: 0
88 | allowsAlphaSplitting: 0
89 | overridden: 0
90 | spriteSheet:
91 | serializedVersion: 2
92 | sprites: []
93 | outline: []
94 | physicsShape: []
95 | spritePackingTag:
96 | userData:
97 | assetBundleName:
98 | assetBundleVariant:
99 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_dot_outer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Siccity/xNode/d6effd70f5574369e3415c423ef3e621ea309564/Scripts/Editor/Resources/xnode_dot_outer.png
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_dot_outer.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 434ca8b4bdfa5574abb0002bbc9b65ad
3 | timeCreated: 1506110357
4 | licenseType: Free
5 | TextureImporter:
6 | fileIDToRecycleName: {}
7 | serializedVersion: 4
8 | mipmaps:
9 | mipMapMode: 0
10 | enableMipMap: 0
11 | sRGBTexture: 0
12 | linearTexture: 0
13 | fadeOut: 0
14 | borderMipMap: 0
15 | mipMapsPreserveCoverage: 0
16 | alphaTestReferenceValue: 0.5
17 | mipMapFadeDistanceStart: 1
18 | mipMapFadeDistanceEnd: 3
19 | bumpmap:
20 | convertToNormalMap: 0
21 | externalNormalMap: 0
22 | heightScale: 0.25
23 | normalMapFilter: 0
24 | isReadable: 0
25 | grayScaleToAlpha: 0
26 | generateCubemap: 6
27 | cubemapConvolution: 0
28 | seamlessCubemap: 0
29 | textureFormat: 1
30 | maxTextureSize: 2048
31 | textureSettings:
32 | serializedVersion: 2
33 | filterMode: -1
34 | aniso: 1
35 | mipBias: -1
36 | wrapU: 1
37 | wrapV: -1
38 | wrapW: -1
39 | nPOTScale: 0
40 | lightmap: 0
41 | compressionQuality: 50
42 | spriteMode: 0
43 | spriteExtrude: 1
44 | spriteMeshType: 1
45 | alignment: 0
46 | spritePivot: {x: 0.5, y: 0.5}
47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
48 | spritePixelsToUnits: 100
49 | alphaUsage: 1
50 | alphaIsTransparency: 1
51 | spriteTessellationDetail: -1
52 | textureType: 2
53 | textureShape: 1
54 | maxTextureSizeSet: 0
55 | compressionQualitySet: 0
56 | textureFormatSet: 0
57 | platformSettings:
58 | - buildTarget: DefaultTexturePlatform
59 | maxTextureSize: 2048
60 | textureFormat: -1
61 | textureCompression: 1
62 | compressionQuality: 50
63 | crunchedCompression: 0
64 | allowsAlphaSplitting: 0
65 | overridden: 0
66 | - buildTarget: Standalone
67 | maxTextureSize: 2048
68 | textureFormat: -1
69 | textureCompression: 1
70 | compressionQuality: 50
71 | crunchedCompression: 0
72 | allowsAlphaSplitting: 0
73 | overridden: 0
74 | - buildTarget: Android
75 | maxTextureSize: 2048
76 | textureFormat: -1
77 | textureCompression: 1
78 | compressionQuality: 50
79 | crunchedCompression: 0
80 | allowsAlphaSplitting: 0
81 | overridden: 0
82 | - buildTarget: WebGL
83 | maxTextureSize: 2048
84 | textureFormat: -1
85 | textureCompression: 1
86 | compressionQuality: 50
87 | crunchedCompression: 0
88 | allowsAlphaSplitting: 0
89 | overridden: 0
90 | spriteSheet:
91 | serializedVersion: 2
92 | sprites: []
93 | outline: []
94 | physicsShape: []
95 | spritePackingTag:
96 | userData:
97 | assetBundleName:
98 | assetBundleVariant:
99 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Siccity/xNode/d6effd70f5574369e3415c423ef3e621ea309564/Scripts/Editor/Resources/xnode_node.png
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_node.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2fea1dcb24935ef4ca514d534eb6aa3d
3 | timeCreated: 1507454532
4 | licenseType: Free
5 | TextureImporter:
6 | fileIDToRecycleName: {}
7 | serializedVersion: 4
8 | mipmaps:
9 | mipMapMode: 0
10 | enableMipMap: 0
11 | sRGBTexture: 0
12 | linearTexture: 0
13 | fadeOut: 0
14 | borderMipMap: 0
15 | mipMapsPreserveCoverage: 0
16 | alphaTestReferenceValue: 0.5
17 | mipMapFadeDistanceStart: 1
18 | mipMapFadeDistanceEnd: 3
19 | bumpmap:
20 | convertToNormalMap: 0
21 | externalNormalMap: 0
22 | heightScale: 0.25
23 | normalMapFilter: 0
24 | isReadable: 0
25 | grayScaleToAlpha: 0
26 | generateCubemap: 6
27 | cubemapConvolution: 0
28 | seamlessCubemap: 0
29 | textureFormat: 1
30 | maxTextureSize: 2048
31 | textureSettings:
32 | serializedVersion: 2
33 | filterMode: -1
34 | aniso: 1
35 | mipBias: -1
36 | wrapU: 1
37 | wrapV: 1
38 | wrapW: 1
39 | nPOTScale: 0
40 | lightmap: 0
41 | compressionQuality: 50
42 | spriteMode: 0
43 | spriteExtrude: 1
44 | spriteMeshType: 1
45 | alignment: 0
46 | spritePivot: {x: 0.5, y: 0.5}
47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
48 | spritePixelsToUnits: 100
49 | alphaUsage: 1
50 | alphaIsTransparency: 1
51 | spriteTessellationDetail: -1
52 | textureType: 2
53 | textureShape: 1
54 | maxTextureSizeSet: 0
55 | compressionQualitySet: 0
56 | textureFormatSet: 0
57 | platformSettings:
58 | - buildTarget: DefaultTexturePlatform
59 | maxTextureSize: 2048
60 | textureFormat: -1
61 | textureCompression: 1
62 | compressionQuality: 50
63 | crunchedCompression: 0
64 | allowsAlphaSplitting: 0
65 | overridden: 0
66 | - buildTarget: Standalone
67 | maxTextureSize: 2048
68 | textureFormat: -1
69 | textureCompression: 1
70 | compressionQuality: 50
71 | crunchedCompression: 0
72 | allowsAlphaSplitting: 0
73 | overridden: 0
74 | - buildTarget: Android
75 | maxTextureSize: 2048
76 | textureFormat: -1
77 | textureCompression: 1
78 | compressionQuality: 50
79 | crunchedCompression: 0
80 | allowsAlphaSplitting: 0
81 | overridden: 0
82 | - buildTarget: WebGL
83 | maxTextureSize: 2048
84 | textureFormat: -1
85 | textureCompression: 1
86 | compressionQuality: 50
87 | crunchedCompression: 0
88 | allowsAlphaSplitting: 0
89 | overridden: 0
90 | spriteSheet:
91 | serializedVersion: 2
92 | sprites: []
93 | outline: []
94 | physicsShape: []
95 | spritePackingTag:
96 | userData:
97 | assetBundleName:
98 | assetBundleVariant:
99 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_node_highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Siccity/xNode/d6effd70f5574369e3415c423ef3e621ea309564/Scripts/Editor/Resources/xnode_node_highlight.png
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_node_highlight.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5
3 | timeCreated: 1516610730
4 | licenseType: Free
5 | TextureImporter:
6 | fileIDToRecycleName: {}
7 | externalObjects: {}
8 | serializedVersion: 4
9 | mipmaps:
10 | mipMapMode: 0
11 | enableMipMap: 0
12 | sRGBTexture: 0
13 | linearTexture: 0
14 | fadeOut: 0
15 | borderMipMap: 0
16 | mipMapsPreserveCoverage: 0
17 | alphaTestReferenceValue: 0.5
18 | mipMapFadeDistanceStart: 1
19 | mipMapFadeDistanceEnd: 3
20 | bumpmap:
21 | convertToNormalMap: 0
22 | externalNormalMap: 0
23 | heightScale: 0.25
24 | normalMapFilter: 0
25 | isReadable: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: 1
36 | mipBias: -1
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: -1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 0
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
49 | spritePixelsToUnits: 100
50 | alphaUsage: 1
51 | alphaIsTransparency: 1
52 | spriteTessellationDetail: -1
53 | textureType: 2
54 | textureShape: 1
55 | maxTextureSizeSet: 0
56 | compressionQualitySet: 0
57 | textureFormatSet: 0
58 | platformSettings:
59 | - buildTarget: DefaultTexturePlatform
60 | maxTextureSize: 2048
61 | resizeAlgorithm: 0
62 | textureFormat: -1
63 | textureCompression: 1
64 | compressionQuality: 50
65 | crunchedCompression: 0
66 | allowsAlphaSplitting: 0
67 | overridden: 0
68 | androidETC2FallbackOverride: 0
69 | - buildTarget: Standalone
70 | maxTextureSize: 2048
71 | resizeAlgorithm: 0
72 | textureFormat: -1
73 | textureCompression: 1
74 | compressionQuality: 50
75 | crunchedCompression: 0
76 | allowsAlphaSplitting: 0
77 | overridden: 0
78 | androidETC2FallbackOverride: 0
79 | spriteSheet:
80 | serializedVersion: 2
81 | sprites: []
82 | outline: []
83 | physicsShape: []
84 | spritePackingTag:
85 | userData:
86 | assetBundleName:
87 | assetBundleVariant:
88 |
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_node_workfile.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Siccity/xNode/d6effd70f5574369e3415c423ef3e621ea309564/Scripts/Editor/Resources/xnode_node_workfile.psd
--------------------------------------------------------------------------------
/Scripts/Editor/Resources/xnode_node_workfile.psd.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2267efa6e1e349348ae0b28fb659a6e2
3 | timeCreated: 1507454532
4 | licenseType: Free
5 | TextureImporter:
6 | fileIDToRecycleName: {}
7 | serializedVersion: 4
8 | mipmaps:
9 | mipMapMode: 0
10 | enableMipMap: 0
11 | sRGBTexture: 0
12 | linearTexture: 0
13 | fadeOut: 0
14 | borderMipMap: 0
15 | mipMapsPreserveCoverage: 0
16 | alphaTestReferenceValue: 0.5
17 | mipMapFadeDistanceStart: 1
18 | mipMapFadeDistanceEnd: 3
19 | bumpmap:
20 | convertToNormalMap: 0
21 | externalNormalMap: 0
22 | heightScale: 0.25
23 | normalMapFilter: 0
24 | isReadable: 0
25 | grayScaleToAlpha: 0
26 | generateCubemap: 6
27 | cubemapConvolution: 0
28 | seamlessCubemap: 0
29 | textureFormat: 1
30 | maxTextureSize: 2048
31 | textureSettings:
32 | serializedVersion: 2
33 | filterMode: -1
34 | aniso: 1
35 | mipBias: -1
36 | wrapU: 1
37 | wrapV: -1
38 | wrapW: -1
39 | nPOTScale: 0
40 | lightmap: 0
41 | compressionQuality: 50
42 | spriteMode: 0
43 | spriteExtrude: 1
44 | spriteMeshType: 1
45 | alignment: 0
46 | spritePivot: {x: 0.5, y: 0.5}
47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
48 | spritePixelsToUnits: 100
49 | alphaUsage: 1
50 | alphaIsTransparency: 1
51 | spriteTessellationDetail: -1
52 | textureType: 2
53 | textureShape: 1
54 | maxTextureSizeSet: 0
55 | compressionQualitySet: 0
56 | textureFormatSet: 0
57 | platformSettings:
58 | - buildTarget: DefaultTexturePlatform
59 | maxTextureSize: 2048
60 | textureFormat: -1
61 | textureCompression: 1
62 | compressionQuality: 50
63 | crunchedCompression: 0
64 | allowsAlphaSplitting: 0
65 | overridden: 0
66 | - buildTarget: Standalone
67 | maxTextureSize: 2048
68 | textureFormat: -1
69 | textureCompression: 1
70 | compressionQuality: 50
71 | crunchedCompression: 0
72 | allowsAlphaSplitting: 0
73 | overridden: 0
74 | - buildTarget: Android
75 | maxTextureSize: 2048
76 | textureFormat: -1
77 | textureCompression: 1
78 | compressionQuality: 50
79 | crunchedCompression: 0
80 | allowsAlphaSplitting: 0
81 | overridden: 0
82 | - buildTarget: WebGL
83 | maxTextureSize: 2048
84 | textureFormat: -1
85 | textureCompression: 1
86 | compressionQuality: 50
87 | crunchedCompression: 0
88 | allowsAlphaSplitting: 0
89 | overridden: 0
90 | spriteSheet:
91 | serializedVersion: 2
92 | sprites: []
93 | outline: []
94 | physicsShape: []
95 | spritePackingTag:
96 | userData:
97 | assetBundleName:
98 | assetBundleVariant:
99 |
--------------------------------------------------------------------------------
/Scripts/Editor/SceneGraphEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEditor;
5 | using UnityEngine;
6 | using XNode;
7 |
8 | namespace XNodeEditor {
9 | [CustomEditor(typeof(SceneGraph), true)]
10 | public class SceneGraphEditor : Editor {
11 | private SceneGraph sceneGraph;
12 | private bool removeSafely;
13 | private Type graphType;
14 |
15 | public override void OnInspectorGUI() {
16 | if (sceneGraph.graph == null) {
17 | if (GUILayout.Button("New graph", GUILayout.Height(40))) {
18 | if (graphType == null) {
19 | Type[] graphTypes = NodeEditorReflection.GetDerivedTypes(typeof(NodeGraph));
20 | GenericMenu menu = new GenericMenu();
21 | for (int i = 0; i < graphTypes.Length; i++) {
22 | Type graphType = graphTypes[i];
23 | menu.AddItem(new GUIContent(graphType.Name), false, () => CreateGraph(graphType));
24 | }
25 | menu.ShowAsContext();
26 | } else {
27 | CreateGraph(graphType);
28 | }
29 | }
30 | } else {
31 | if (GUILayout.Button("Open graph", GUILayout.Height(40))) {
32 | NodeEditorWindow.Open(sceneGraph.graph);
33 | }
34 | if (removeSafely) {
35 | GUILayout.BeginHorizontal();
36 | GUILayout.Label("Really remove graph?");
37 | GUI.color = new Color(1, 0.8f, 0.8f);
38 | if (GUILayout.Button("Remove")) {
39 | removeSafely = false;
40 | Undo.RecordObject(sceneGraph, "Removed graph");
41 | sceneGraph.graph = null;
42 | }
43 | GUI.color = Color.white;
44 | if (GUILayout.Button("Cancel")) {
45 | removeSafely = false;
46 | }
47 | GUILayout.EndHorizontal();
48 | } else {
49 | GUI.color = new Color(1, 0.8f, 0.8f);
50 | if (GUILayout.Button("Remove graph")) {
51 | removeSafely = true;
52 | }
53 | GUI.color = Color.white;
54 | }
55 | }
56 | DrawDefaultInspector();
57 | }
58 |
59 | private void OnEnable() {
60 | sceneGraph = target as SceneGraph;
61 | Type sceneGraphType = sceneGraph.GetType();
62 | if (sceneGraphType == typeof(SceneGraph)) {
63 | graphType = null;
64 | } else {
65 | Type baseType = sceneGraphType.BaseType;
66 | if (baseType.IsGenericType) {
67 | graphType = sceneGraphType = baseType.GetGenericArguments() [0];
68 | }
69 | }
70 | }
71 |
72 | public void CreateGraph(Type type) {
73 | Undo.RecordObject(sceneGraph, "Create graph");
74 | sceneGraph.graph = ScriptableObject.CreateInstance(type) as NodeGraph;
75 | sceneGraph.graph.name = sceneGraph.name + "-graph";
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/Scripts/Editor/SceneGraphEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: aea725adabc311f44b5ea8161360a915
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/Editor/XNodeEditor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "XNodeEditor",
3 | "references": [
4 | "XNode"
5 | ],
6 | "optionalUnityReferences": [],
7 | "includePlatforms": [
8 | "Editor"
9 | ],
10 | "excludePlatforms": [],
11 | "allowUnsafeCode": false,
12 | "overrideReferences": false,
13 | "precompiledReferences": [],
14 | "autoReferenced": true,
15 | "defineConstraints": [],
16 | "versionDefines": []
17 | }
--------------------------------------------------------------------------------
/Scripts/Editor/XNodeEditor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 002c1bbed08fa44d282ef34fd5edb138
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Scripts/Node.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f26231e5ab9368746948d0ea49e8178a
3 | timeCreated: 1505419984
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/NodeDataCache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Reflection;
4 | using UnityEngine;
5 |
6 | namespace XNode {
7 | /// Precaches reflection data in editor so we won't have to do it runtime
8 | public static class NodeDataCache {
9 | private static PortDataCache portDataCache;
10 | private static Dictionary> formerlySerializedAsCache;
11 | private static Dictionary typeQualifiedNameCache;
12 | private static bool Initialized { get { return portDataCache != null; } }
13 |
14 | public static string GetTypeQualifiedName(System.Type type) {
15 | if(typeQualifiedNameCache == null) typeQualifiedNameCache = new Dictionary();
16 |
17 | string name;
18 | if (!typeQualifiedNameCache.TryGetValue(type, out name)) {
19 | name = type.AssemblyQualifiedName;
20 | typeQualifiedNameCache.Add(type, name);
21 | }
22 | return name;
23 | }
24 |
25 | /// Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields.
26 | public static void UpdatePorts(Node node, Dictionary ports) {
27 | if (!Initialized) BuildCache();
28 |
29 | Dictionary> removedPorts = new Dictionary>();
30 | System.Type nodeType = node.GetType();
31 |
32 | Dictionary formerlySerializedAs = null;
33 | if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs);
34 |
35 | List dynamicListPorts = new List();
36 |
37 | Dictionary staticPorts;
38 | if (!portDataCache.TryGetValue(nodeType, out staticPorts)) {
39 | staticPorts = new Dictionary();
40 | }
41 |
42 | // Cleanup port dict - Remove nonexisting static ports - update static port types
43 | // AND update dynamic ports (albeit only those in lists) too, in order to enforce proper serialisation.
44 | // Loop through current node ports
45 | foreach (NodePort port in ports.Values.ToArray()) {
46 | // If port still exists, check it it has been changed
47 | NodePort staticPort;
48 | if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
49 | // If port exists but with wrong settings, remove it. Re-add it later.
50 | if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
51 | // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections.
52 | if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
53 | port.ClearConnections();
54 | ports.Remove(port.fieldName);
55 | } else port.ValueType = staticPort.ValueType;
56 | }
57 | // If port doesn't exist anymore, remove it
58 | else if (port.IsStatic) {
59 | //See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts
60 | // so it can be reconnected in missing ports stage.
61 | string newName = null;
62 | if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections());
63 |
64 | port.ClearConnections();
65 | ports.Remove(port.fieldName);
66 | }
67 | // If the port is dynamic and is managed by a dynamic port list, flag it for reference updates
68 | else if (IsDynamicListPort(port)) {
69 | dynamicListPorts.Add(port);
70 | }
71 | }
72 | // Add missing ports
73 | foreach (NodePort staticPort in staticPorts.Values) {
74 | if (!ports.ContainsKey(staticPort.fieldName)) {
75 | NodePort port = new NodePort(staticPort, node);
76 | //If we just removed the port, try re-adding the connections
77 | List reconnectConnections;
78 | if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) {
79 | for (int i = 0; i < reconnectConnections.Count; i++) {
80 | NodePort connection = reconnectConnections[i];
81 | if (connection == null) continue;
82 | // CAVEAT: Ports connected under special conditions defined in graphEditor.CanConnect overrides will not auto-connect.
83 | // To fix this, this code would need to be moved to an editor script and call graphEditor.CanConnect instead of port.CanConnectTo.
84 | // This is only a problem in the rare edge case where user is using non-standard CanConnect overrides and changes port type of an already connected port
85 | if (port.CanConnectTo(connection)) port.Connect(connection);
86 | }
87 | }
88 | ports.Add(staticPort.fieldName, port);
89 | }
90 | }
91 |
92 | // Finally, make sure dynamic list port settings correspond to the settings of their "backing port"
93 | foreach (NodePort listPort in dynamicListPorts) {
94 | // At this point we know that ports here are dynamic list ports
95 | // which have passed name/"backing port" checks, ergo we can proceed more safely.
96 | string backingPortName = listPort.fieldName.Substring(0, listPort.fieldName.IndexOf(' '));
97 | NodePort backingPort = staticPorts[backingPortName];
98 |
99 | // Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
100 | listPort.ValueType = GetBackingValueType(backingPort.ValueType);
101 | listPort.direction = backingPort.direction;
102 | listPort.connectionType = backingPort.connectionType;
103 | listPort.typeConstraint = backingPort.typeConstraint;
104 | }
105 | }
106 |
107 | ///
108 | /// Extracts the underlying types from arrays and lists, the only collections for dynamic port lists
109 | /// currently supported. If the given type is not applicable (i.e. if the dynamic list port was not
110 | /// defined as an array or a list), returns the given type itself.
111 | ///
112 | private static System.Type GetBackingValueType(System.Type portValType) {
113 | if (portValType.HasElementType) {
114 | return portValType.GetElementType();
115 | }
116 | if (portValType.IsGenericType && portValType.GetGenericTypeDefinition() == typeof(List<>)) {
117 | return portValType.GetGenericArguments()[0];
118 | }
119 | return portValType;
120 | }
121 |
122 | /// Returns true if the given port is in a dynamic port list.
123 | private static bool IsDynamicListPort(NodePort port) {
124 | // Ports flagged as "dynamicPortList = true" end up having a "backing port" and a name with an index, but we have
125 | // no guarantee that a dynamic port called "output 0" is an element in a list backed by a static "output" port.
126 | // Thus, we need to check for attributes... (but at least we don't need to look at all fields this time)
127 | string[] fieldNameParts = port.fieldName.Split(' ');
128 | if (fieldNameParts.Length != 2) return false;
129 |
130 | FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]);
131 | if (backingPortInfo == null) return false;
132 |
133 | object[] attribs = backingPortInfo.GetCustomAttributes(true);
134 | return attribs.Any(x => {
135 | Node.InputAttribute inputAttribute = x as Node.InputAttribute;
136 | Node.OutputAttribute outputAttribute = x as Node.OutputAttribute;
137 | return inputAttribute != null && inputAttribute.dynamicPortList ||
138 | outputAttribute != null && outputAttribute.dynamicPortList;
139 | });
140 | }
141 |
142 | /// Cache node types
143 | private static void BuildCache() {
144 | portDataCache = new PortDataCache();
145 | System.Type baseType = typeof(Node);
146 | List nodeTypes = new List();
147 | System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
148 |
149 | // Loop through assemblies and add node types to list
150 | foreach (Assembly assembly in assemblies) {
151 | // Skip certain dlls to improve performance
152 | string assemblyName = assembly.GetName().Name;
153 | int index = assemblyName.IndexOf('.');
154 | if (index != -1) assemblyName = assemblyName.Substring(0, index);
155 | switch (assemblyName) {
156 | // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped
157 | case "UnityEditor":
158 | case "UnityEngine":
159 | case "Unity":
160 | case "System":
161 | case "mscorlib":
162 | case "Microsoft":
163 | continue;
164 | default:
165 | nodeTypes.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
166 | break;
167 | }
168 | }
169 |
170 | for (int i = 0; i < nodeTypes.Count; i++) {
171 | CachePorts(nodeTypes[i]);
172 | }
173 | }
174 |
175 | public static List GetNodeFields(System.Type nodeType) {
176 | List fieldInfo = new List(nodeType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
177 |
178 | // GetFields doesnt return inherited private fields, so walk through base types and pick those up
179 | System.Type tempType = nodeType;
180 | while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
181 | FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
182 | for (int i = 0; i < parentFields.Length; i++) {
183 | // Ensure that we do not already have a member with this type and name
184 | FieldInfo parentField = parentFields[i];
185 | if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) {
186 | fieldInfo.Add(parentField);
187 | }
188 | }
189 | }
190 | return fieldInfo;
191 | }
192 |
193 | private static void CachePorts(System.Type nodeType) {
194 | List fieldInfo = GetNodeFields(nodeType);
195 |
196 | for (int i = 0; i < fieldInfo.Count; i++) {
197 |
198 | //Get InputAttribute and OutputAttribute
199 | object[] attribs = fieldInfo[i].GetCustomAttributes(true);
200 | Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
201 | Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
202 | UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute;
203 |
204 | if (inputAttrib == null && outputAttrib == null) continue;
205 |
206 | if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
207 | else {
208 | if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new Dictionary());
209 | NodePort port = new NodePort(fieldInfo[i]);
210 | portDataCache[nodeType].Add(port.fieldName, port);
211 | }
212 |
213 | if (formerlySerializedAsAttribute != null) {
214 | if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary>();
215 | if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary());
216 |
217 | if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node.");
218 | else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name);
219 | }
220 | }
221 | }
222 |
223 | [System.Serializable]
224 | private class PortDataCache : Dictionary> { }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/Scripts/NodeDataCache.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 64ea6af1e195d024d8df0ead1921e517
3 | timeCreated: 1507566823
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/NodeGraph.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace XNode {
6 | /// Base class for all node graphs
7 | [Serializable]
8 | public abstract class NodeGraph : ScriptableObject {
9 |
10 | /// All nodes in the graph.
11 | /// See:
12 | [SerializeField] public List nodes = new List();
13 |
14 | /// Add a node to the graph by type (convenience method - will call the System.Type version)
15 | public T AddNode() where T : Node {
16 | return AddNode(typeof(T)) as T;
17 | }
18 |
19 | /// Add a node to the graph by type
20 | public virtual Node AddNode(Type type) {
21 | Node.graphHotfix = this;
22 | Node node = ScriptableObject.CreateInstance(type) as Node;
23 | node.graph = this;
24 | nodes.Add(node);
25 | return node;
26 | }
27 |
28 | /// Creates a copy of the original node in the graph
29 | public virtual Node CopyNode(Node original) {
30 | Node.graphHotfix = this;
31 | Node node = ScriptableObject.Instantiate(original);
32 | node.graph = this;
33 | node.ClearConnections();
34 | nodes.Add(node);
35 | return node;
36 | }
37 |
38 | /// Safely remove a node and all its connections
39 | /// The node to remove
40 | public virtual void RemoveNode(Node node) {
41 | node.ClearConnections();
42 | nodes.Remove(node);
43 | if (Application.isPlaying) Destroy(node);
44 | }
45 |
46 | /// Remove all nodes and connections from the graph
47 | public virtual void Clear() {
48 | if (Application.isPlaying) {
49 | for (int i = 0; i < nodes.Count; i++) {
50 | if (nodes[i] != null) Destroy(nodes[i]);
51 | }
52 | }
53 | nodes.Clear();
54 | }
55 |
56 | /// Create a new deep copy of this graph
57 | public virtual XNode.NodeGraph Copy() {
58 | // Instantiate a new nodegraph instance
59 | NodeGraph graph = Instantiate(this);
60 | // Instantiate all nodes inside the graph
61 | for (int i = 0; i < nodes.Count; i++) {
62 | if (nodes[i] == null) continue;
63 | Node.graphHotfix = graph;
64 | Node node = Instantiate(nodes[i]) as Node;
65 | node.graph = graph;
66 | graph.nodes[i] = node;
67 | }
68 |
69 | // Redirect all connections
70 | for (int i = 0; i < graph.nodes.Count; i++) {
71 | if (graph.nodes[i] == null) continue;
72 | foreach (NodePort port in graph.nodes[i].Ports) {
73 | port.Redirect(nodes, graph.nodes);
74 | }
75 | }
76 |
77 | return graph;
78 | }
79 |
80 | protected virtual void OnDestroy() {
81 | // Remove all nodes prior to graph destruction
82 | Clear();
83 | }
84 |
85 | #region Attributes
86 | /// Automatically ensures the existance of a certain node type, and prevents it from being deleted.
87 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
88 | public class RequireNodeAttribute : Attribute {
89 | public Type type0;
90 | public Type type1;
91 | public Type type2;
92 |
93 | /// Automatically ensures the existance of a certain node type, and prevents it from being deleted
94 | public RequireNodeAttribute(Type type) {
95 | this.type0 = type;
96 | this.type1 = null;
97 | this.type2 = null;
98 | }
99 |
100 | /// Automatically ensures the existance of a certain node type, and prevents it from being deleted
101 | public RequireNodeAttribute(Type type, Type type2) {
102 | this.type0 = type;
103 | this.type1 = type2;
104 | this.type2 = null;
105 | }
106 |
107 | /// Automatically ensures the existance of a certain node type, and prevents it from being deleted
108 | public RequireNodeAttribute(Type type, Type type2, Type type3) {
109 | this.type0 = type;
110 | this.type1 = type2;
111 | this.type2 = type3;
112 | }
113 |
114 | public bool Requires(Type type) {
115 | if (type == null) return false;
116 | if (type == type0) return true;
117 | else if (type == type1) return true;
118 | else if (type == type2) return true;
119 | return false;
120 | }
121 | }
122 | #endregion
123 | }
124 | }
--------------------------------------------------------------------------------
/Scripts/NodeGraph.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 093f68ef2455d544fa2d14b80c811322
3 | timeCreated: 1505461376
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/NodePort.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using UnityEngine;
5 |
6 | namespace XNode {
7 | [Serializable]
8 | public class NodePort {
9 | public enum IO { Input, Output }
10 |
11 | public int ConnectionCount { get { return connections.Count; } }
12 | /// Return the first non-null connection
13 | public NodePort Connection {
14 | get {
15 | for (int i = 0; i < connections.Count; i++) {
16 | if (connections[i] != null) return connections[i].Port;
17 | }
18 | return null;
19 | }
20 | }
21 |
22 | public IO direction {
23 | get { return _direction; }
24 | internal set { _direction = value; }
25 | }
26 | public Node.ConnectionType connectionType {
27 | get { return _connectionType; }
28 | internal set { _connectionType = value; }
29 | }
30 | public Node.TypeConstraint typeConstraint {
31 | get { return _typeConstraint; }
32 | internal set { _typeConstraint = value; }
33 | }
34 |
35 | /// Is this port connected to anytihng?
36 | public bool IsConnected { get { return connections.Count != 0; } }
37 | public bool IsInput { get { return direction == IO.Input; } }
38 | public bool IsOutput { get { return direction == IO.Output; } }
39 |
40 | public string fieldName { get { return _fieldName; } }
41 | public Node node { get { return _node; } }
42 | public bool IsDynamic { get { return _dynamic; } }
43 | public bool IsStatic { get { return !_dynamic; } }
44 | public Type ValueType {
45 | get {
46 | if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false);
47 | return valueType;
48 | }
49 | set {
50 | if (valueType == value) return;
51 | valueType = value;
52 | if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value);
53 | }
54 | }
55 | private Type valueType;
56 |
57 | [SerializeField] private string _fieldName;
58 | [SerializeField] private Node _node;
59 | [SerializeField] private string _typeQualifiedName;
60 | [SerializeField] private List connections = new List();
61 | [SerializeField] private IO _direction;
62 | [SerializeField] private Node.ConnectionType _connectionType;
63 | [SerializeField] private Node.TypeConstraint _typeConstraint;
64 | [SerializeField] private bool _dynamic;
65 |
66 | /// Construct a static targetless nodeport. Used as a template.
67 | public NodePort(FieldInfo fieldInfo) {
68 | _fieldName = fieldInfo.Name;
69 | ValueType = fieldInfo.FieldType;
70 | _dynamic = false;
71 | var attribs = fieldInfo.GetCustomAttributes(false);
72 | for (int i = 0; i < attribs.Length; i++) {
73 | if (attribs[i] is Node.InputAttribute) {
74 | _direction = IO.Input;
75 | _connectionType = (attribs[i] as Node.InputAttribute).connectionType;
76 | _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint;
77 | } else if (attribs[i] is Node.OutputAttribute) {
78 | _direction = IO.Output;
79 | _connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
80 | _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint;
81 | }
82 | // Override ValueType of the Port
83 | if(attribs[i] is PortTypeOverrideAttribute) {
84 | ValueType = (attribs[i] as PortTypeOverrideAttribute).type;
85 | }
86 | }
87 | }
88 |
89 | /// Copy a nodePort but assign it to another node.
90 | public NodePort(NodePort nodePort, Node node) {
91 | _fieldName = nodePort._fieldName;
92 | ValueType = nodePort.valueType;
93 | _direction = nodePort.direction;
94 | _dynamic = nodePort._dynamic;
95 | _connectionType = nodePort._connectionType;
96 | _typeConstraint = nodePort._typeConstraint;
97 | _node = node;
98 | }
99 |
100 | /// Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports.
101 | public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) {
102 | _fieldName = fieldName;
103 | this.ValueType = type;
104 | _direction = direction;
105 | _node = node;
106 | _dynamic = true;
107 | _connectionType = connectionType;
108 | _typeConstraint = typeConstraint;
109 | }
110 |
111 | /// Checks all connections for invalid references, and removes them.
112 | public void VerifyConnections() {
113 | for (int i = connections.Count - 1; i >= 0; i--) {
114 | if (connections[i].node != null &&
115 | !string.IsNullOrEmpty(connections[i].fieldName) &&
116 | connections[i].node.GetPort(connections[i].fieldName) != null)
117 | continue;
118 | connections.RemoveAt(i);
119 | }
120 | }
121 |
122 | /// Return the output value of this node through its parent nodes GetValue override method.
123 | ///
124 | public object GetOutputValue() {
125 | if (direction == IO.Input) return null;
126 | return node.GetValue(this);
127 | }
128 |
129 | /// Return the output value of the first connected port. Returns null if none found or invalid.
130 | ///
131 | public object GetInputValue() {
132 | NodePort connectedPort = Connection;
133 | if (connectedPort == null) return null;
134 | return connectedPort.GetOutputValue();
135 | }
136 |
137 | /// Return the output values of all connected ports.
138 | ///
139 | public object[] GetInputValues() {
140 | object[] objs = new object[ConnectionCount];
141 | for (int i = 0; i < ConnectionCount; i++) {
142 | NodePort connectedPort = connections[i].Port;
143 | if (connectedPort == null) { // if we happen to find a null port, remove it and look again
144 | connections.RemoveAt(i);
145 | i--;
146 | continue;
147 | }
148 | objs[i] = connectedPort.GetOutputValue();
149 | }
150 | return objs;
151 | }
152 |
153 | /// Return the output value of the first connected port. Returns null if none found or invalid.
154 | ///
155 | public T GetInputValue() {
156 | object obj = GetInputValue();
157 | return obj is T ? (T) obj : default(T);
158 | }
159 |
160 | /// Return the output values of all connected ports.
161 | ///
162 | public T[] GetInputValues() {
163 | object[] objs = GetInputValues();
164 | T[] ts = new T[objs.Length];
165 | for (int i = 0; i < objs.Length; i++) {
166 | if (objs[i] is T) ts[i] = (T) objs[i];
167 | }
168 | return ts;
169 | }
170 |
171 | /// Return true if port is connected and has a valid input.
172 | ///
173 | public bool TryGetInputValue(out T value) {
174 | object obj = GetInputValue();
175 | if (obj is T) {
176 | value = (T) obj;
177 | return true;
178 | } else {
179 | value = default(T);
180 | return false;
181 | }
182 | }
183 |
184 | /// Return the sum of all inputs.
185 | ///
186 | public float GetInputSum(float fallback) {
187 | object[] objs = GetInputValues();
188 | if (objs.Length == 0) return fallback;
189 | float result = 0;
190 | for (int i = 0; i < objs.Length; i++) {
191 | if (objs[i] is float) result += (float) objs[i];
192 | }
193 | return result;
194 | }
195 |
196 | /// Return the sum of all inputs.
197 | ///
198 | public int GetInputSum(int fallback) {
199 | object[] objs = GetInputValues();
200 | if (objs.Length == 0) return fallback;
201 | int result = 0;
202 | for (int i = 0; i < objs.Length; i++) {
203 | if (objs[i] is int) result += (int) objs[i];
204 | }
205 | return result;
206 | }
207 |
208 | /// Connect this to another
209 | /// The to connect to
210 | public void Connect(NodePort port) {
211 | if (connections == null) connections = new List();
212 | if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; }
213 | if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; }
214 | if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; }
215 | if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; }
216 | #if UNITY_EDITOR
217 | UnityEditor.Undo.RecordObject(node, "Connect Port");
218 | UnityEditor.Undo.RecordObject(port.node, "Connect Port");
219 | #endif
220 | if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
221 | if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
222 | connections.Add(new PortConnection(port));
223 | if (port.connections == null) port.connections = new List();
224 | if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this));
225 | node.OnCreateConnection(this, port);
226 | port.node.OnCreateConnection(this, port);
227 | }
228 |
229 | public List GetConnections() {
230 | List result = new List();
231 | for (int i = 0; i < connections.Count; i++) {
232 | NodePort port = GetConnection(i);
233 | if (port != null) result.Add(port);
234 | }
235 | return result;
236 | }
237 |
238 | public NodePort GetConnection(int i) {
239 | //If the connection is broken for some reason, remove it.
240 | if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) {
241 | connections.RemoveAt(i);
242 | return null;
243 | }
244 | NodePort port = connections[i].node.GetPort(connections[i].fieldName);
245 | if (port == null) {
246 | connections.RemoveAt(i);
247 | return null;
248 | }
249 | return port;
250 | }
251 |
252 | /// Get index of the connection connecting this and specified ports
253 | public int GetConnectionIndex(NodePort port) {
254 | for (int i = 0; i < ConnectionCount; i++) {
255 | if (connections[i].Port == port) return i;
256 | }
257 | return -1;
258 | }
259 |
260 | public bool IsConnectedTo(NodePort port) {
261 | for (int i = 0; i < connections.Count; i++) {
262 | if (connections[i].Port == port) return true;
263 | }
264 | return false;
265 | }
266 |
267 | /// Returns true if this port can connect to specified port
268 | public bool CanConnectTo(NodePort port) {
269 | // Figure out which is input and which is output
270 | NodePort input = null, output = null;
271 | if (IsInput) input = this;
272 | else output = this;
273 | if (port.IsInput) input = port;
274 | else output = port;
275 | // If there isn't one of each, they can't connect
276 | if (input == null || output == null) return false;
277 | // Check input type constraints
278 | if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
279 | if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
280 | if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
281 | if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
282 | // Check output type constraints
283 | if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
284 | if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
285 | if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
286 | if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
287 | // Success
288 | return true;
289 | }
290 |
291 | /// Disconnect this port from another port
292 | public void Disconnect(NodePort port) {
293 | // Remove this ports connection to the other
294 | for (int i = connections.Count - 1; i >= 0; i--) {
295 | if (connections[i].Port == port) {
296 | connections.RemoveAt(i);
297 | }
298 | }
299 | if (port != null) {
300 | // Remove the other ports connection to this port
301 | for (int i = 0; i < port.connections.Count; i++) {
302 | if (port.connections[i].Port == this) {
303 | port.connections.RemoveAt(i);
304 | // Trigger OnRemoveConnection from this side port
305 | port.node.OnRemoveConnection(port);
306 | }
307 | }
308 | }
309 | // Trigger OnRemoveConnection
310 | node.OnRemoveConnection(this);
311 | }
312 |
313 | /// Disconnect this port from another port
314 | public void Disconnect(int i) {
315 | // Remove the other ports connection to this port
316 | NodePort otherPort = connections[i].Port;
317 | if (otherPort != null) {
318 | otherPort.connections.RemoveAll(it => { return it.Port == this; });
319 | }
320 | // Remove this ports connection to the other
321 | connections.RemoveAt(i);
322 |
323 | // Trigger OnRemoveConnection
324 | node.OnRemoveConnection(this);
325 | if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort);
326 | }
327 |
328 | public void ClearConnections() {
329 | while (connections.Count > 0) {
330 | Disconnect(connections[0].Port);
331 | }
332 | }
333 |
334 | /// Get reroute points for a given connection. This is used for organization
335 | public List GetReroutePoints(int index) {
336 | return connections[index].reroutePoints;
337 | }
338 |
339 | /// Swap connections with another node
340 | public void SwapConnections(NodePort targetPort) {
341 | int aConnectionCount = connections.Count;
342 | int bConnectionCount = targetPort.connections.Count;
343 |
344 | List portConnections = new List();
345 | List targetPortConnections = new List();
346 |
347 | // Cache port connections
348 | for (int i = 0; i < aConnectionCount; i++)
349 | portConnections.Add(connections[i].Port);
350 |
351 | // Cache target port connections
352 | for (int i = 0; i < bConnectionCount; i++)
353 | targetPortConnections.Add(targetPort.connections[i].Port);
354 |
355 | ClearConnections();
356 | targetPort.ClearConnections();
357 |
358 | // Add port connections to targetPort
359 | for (int i = 0; i < portConnections.Count; i++)
360 | targetPort.Connect(portConnections[i]);
361 |
362 | // Add target port connections to this one
363 | for (int i = 0; i < targetPortConnections.Count; i++)
364 | Connect(targetPortConnections[i]);
365 |
366 | }
367 |
368 | /// Copy all connections pointing to a node and add them to this one
369 | public void AddConnections(NodePort targetPort) {
370 | int connectionCount = targetPort.ConnectionCount;
371 | for (int i = 0; i < connectionCount; i++) {
372 | PortConnection connection = targetPort.connections[i];
373 | NodePort otherPort = connection.Port;
374 | Connect(otherPort);
375 | }
376 | }
377 |
378 | /// Move all connections pointing to this node, to another node
379 | public void MoveConnections(NodePort targetPort) {
380 | int connectionCount = connections.Count;
381 |
382 | // Add connections to target port
383 | for (int i = 0; i < connectionCount; i++) {
384 | PortConnection connection = targetPort.connections[i];
385 | NodePort otherPort = connection.Port;
386 | Connect(otherPort);
387 | }
388 | ClearConnections();
389 | }
390 |
391 | /// Swap connected nodes from the old list with nodes from the new list
392 | public void Redirect(List oldNodes, List newNodes) {
393 | foreach (PortConnection connection in connections) {
394 | int index = oldNodes.IndexOf(connection.node);
395 | if (index >= 0) connection.node = newNodes[index];
396 | }
397 | }
398 |
399 | [Serializable]
400 | private class PortConnection {
401 | [SerializeField] public string fieldName;
402 | [SerializeField] public Node node;
403 | public NodePort Port { get { return port != null ? port : port = GetPort(); } }
404 |
405 | [NonSerialized] private NodePort port;
406 | /// Extra connection path points for organization
407 | [SerializeField] public List reroutePoints = new List();
408 |
409 | public PortConnection(NodePort port) {
410 | this.port = port;
411 | node = port.node;
412 | fieldName = port.fieldName;
413 | }
414 |
415 | /// Returns the port that this points to
416 | private NodePort GetPort() {
417 | if (node == null || string.IsNullOrEmpty(fieldName)) return null;
418 | return node.GetPort(fieldName);
419 | }
420 | }
421 | }
422 | }
423 |
--------------------------------------------------------------------------------
/Scripts/NodePort.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7dd2f76ac25c6f44c9426dff3e7491a3
3 | timeCreated: 1505734054
4 | licenseType: Free
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Scripts/SceneGraph.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 | using XNode;
5 |
6 | namespace XNode {
7 | /// Lets you instantiate a node graph in the scene. This allows you to reference in-scene objects.
8 | public class SceneGraph : MonoBehaviour {
9 | public NodeGraph graph;
10 | }
11 |
12 | /// Derive from this class to create a SceneGraph with a specific graph type.
13 | ///
14 | ///
15 | /// public class MySceneGraph : SceneGraph {
16 | ///
17 | /// }
18 | ///
19 | ///
20 | public class SceneGraph : SceneGraph where T : NodeGraph {
21 | public new T graph { get { return base.graph as T; } set { base.graph = value; } }
22 | }
23 | }
--------------------------------------------------------------------------------
/Scripts/SceneGraph.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7915171fc13472a40a0162003052d2db
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Scripts/XNode.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "XNode",
3 | "references": [],
4 | "optionalUnityReferences": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": [],
12 | "versionDefines": []
13 | }
14 |
--------------------------------------------------------------------------------
/Scripts/XNode.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b8e24fd1eb19b4226afebb2810e3c19b
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.github.siccity.xnode",
3 | "description": "xNode provides a set of APIs and an editor interface for creating and editing custom node graphs.",
4 | "version": "1.8.0",
5 | "unity": "2018.1",
6 | "displayName": "xNode"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e9869d68f06b74538a01e9b8e406159e
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------