├── Docs ├── Node Editor Documentation.html ├── NodeEditorDoc_Resources │ ├── ConnectionTypes.png │ ├── NodeEditorDoc.css │ └── NodeEditorTitle.png └── license.md ├── Editor └── Node_Editor │ ├── NodeEditorWindow.cs │ ├── RTNE_InspectorGUI.cs │ └── Resources │ └── .gitkeep ├── Node_Editor ├── Framework │ ├── ConnectionTypes.cs │ ├── ConnectionTypes.cs.meta │ ├── Node.cs │ ├── Node.cs.meta │ ├── NodeCanvas.cs │ ├── NodeCanvas.cs.meta │ ├── NodeEditor.cs │ ├── NodeEditor.cs.meta │ ├── NodeEditorCallbackReceiver.cs │ ├── NodeEditorCallbackReceiver.cs.meta │ ├── NodeEditorGUI.cs │ ├── NodeEditorGUI.cs.meta │ ├── NodeEditorSaveManager.cs │ ├── NodeEditorSaveManager.cs.meta │ ├── NodeEditorState.cs │ ├── NodeEditorState.cs.meta │ ├── NodeInput.cs │ ├── NodeInput.cs.meta │ ├── NodeKnob.cs │ ├── NodeKnob.cs.meta │ ├── NodeOutput.cs │ ├── NodeOutput.cs.meta │ ├── NodeTypes.cs │ └── NodeTypes.cs.meta ├── Nodes │ ├── Example │ │ ├── AllAroundNode.cs │ │ ├── AllAroundNode.cs.meta │ │ ├── ExampleNode.cs │ │ └── ExampleNode.cs.meta │ └── FloatCalc │ │ ├── CalcNode.cs │ │ ├── CalcNode.cs.meta │ │ ├── DisplayNode.cs │ │ ├── DisplayNode.cs.meta │ │ ├── InputNode.cs │ │ └── InputNode.cs.meta ├── Resources │ ├── LineShader.shader │ ├── Saves │ │ ├── Calculation Canvas.asset │ │ ├── Calculation Canvas.asset.meta │ │ ├── Recursion Test Canvas.asset │ │ ├── Recursion Test Canvas.asset.meta │ │ ├── State Canvas.asset │ │ └── State Canvas.asset.meta │ └── Textures │ │ ├── AALine.png │ │ ├── AALine.png.meta │ │ ├── Icon_Dark.png │ │ ├── Icon_Dark.png.meta │ │ ├── Icon_Light.png │ │ ├── Icon_Light.png.meta │ │ ├── In_Knob.png │ │ ├── In_Knob.png.meta │ │ ├── Knobs.xcf │ │ ├── Knobs.xcf.meta │ │ ├── NE_Box.png │ │ ├── NE_Box.png.meta │ │ ├── NE_Button.png │ │ ├── NE_Button.png.meta │ │ ├── NE_GUI.xcf │ │ ├── NE_GUI.xcf.meta │ │ ├── Out_Knob.png │ │ ├── Out_Knob.png.meta │ │ ├── background.png │ │ ├── background.png.meta │ │ ├── expandRight.png │ │ └── expandRight.png.meta ├── RuntimeNodeEditor.cs ├── RuntimeNodeEditor.cs.meta └── Utilities │ ├── EditorLoadingControl.cs │ ├── GUIScaleUtility.cs │ ├── OverlayGUI.cs │ ├── RTEditorGUI.cs │ ├── ResourceManager.cs │ └── link.xml └── README.md /Docs/Node Editor Documentation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Node Editor Documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 17 |
18 |

NODE EDITOR

19 |

Free and versatile Node Editor Framework for Unity 3D

20 | 21 |
22 | 23 |

24 | 25 |
26 | (Texture Composer, an example avaiable on the forums) 27 |

28 | 29 |
30 | 31 |

32 | Documentation by Seneral 33 |
34 | Version 1.02 (30.01.16) 35 |

36 | 37 |
38 | 39 |

40 | Repository 41 | - 42 | Forums 43 |

44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 |
52 |

Contents

53 | 54 |

55 | Preface
56 | Features
57 | Examples
58 |
59 | 60 | Getting Started
61 | Custom Nodes
62 | Custom ConnectionTypes
63 |
64 | 65 | Customization
66 | Interface
67 | Framework Overview
68 |
69 | 70 | Events
71 |
72 | 73 | Conclusion 74 |

75 | 76 |
77 | 78 |
79 |
80 |
81 |
82 | 83 |
84 |

Preface

85 |

86 | This Documentation intends to give you an overview of the Node Editor. It was initially posted by me, Seneral, 87 | as a personal project on the Unity forums. 88 | After receiving a great amount of positive feedback I continued improving and supporting it, now featuring alot of major things a Node Editor needs to have!
89 | The GitHub repository was set up by Baste during the early developement, 90 | and now it's the main platform to share and contribute to Node Editor. 91 |

92 |
93 | 94 |

Features

95 |

The Node Editor is special in that it's open source, but still is packed full of features, some of which are very unique to this project and which we're proud of featuring:

96 | 105 |

The framework also shines with clean and easy to modify code. It's clearly seperated in multiple parts, some of which can even be taken and used somewhere else, such as the unique, generic scaling approach!

106 |
107 | 108 |

Examples

109 |

110 | You can start off by checking out the Editor Window at 'Window/Node Editor' and loading one of the example canvases, such as the CalculationCanvas. 111 | Do that by either loading it with the button at the top right or by locating it in the project folder and double-clicking it.
112 | With right-click you can add additional nodes, using drag'n'drop you can connect node Outputs and Inputs with each other. 113 |

114 |
115 | 116 |
117 |
118 |
119 |
120 | 121 |
122 |

Getting Started

123 |

124 | Here you'll find some help on how to get you started creating custom Nodes and ConnectionTypes in the simplest form, without touching the Framework code. 125 | These two methods already can help you make little extensions requiring the Node Editor Framework to be installed seperately. 126 |

127 |
128 | 129 |

Custom Nodes

130 |

131 | The implementation of additional, custom nodes is fairly easy. You have to create a script anywhere in the project extending the Node class of the NodeEditorFramework namespace. 132 | It will provide the Framework all needed information about the node itself, the optional Node Attribute contains information about the presentation in the editor. 133 | The Framework will search all script assemblies for additional nodes, so extra setup is not required.
134 | In the following are the necessary Node members outlined, based upon the ExampleNode found at 'Plugins/Node_Editor/Nodes'. 135 | First to mention is that even though the Framework is programmed in C#, you can add nodes in UnityScript with the limitation that they have to be compiled in phase 2,3 or 4, 136 | as described here. Therefore the following members are described language independently. 137 |

138 | 182 |
183 | 184 |

Custom ConnectionTypes

185 |

186 | Implementing custom ConnectionTypes is similar to Node implementation, as it uses the same fetching system: 187 | Declare a class inheriting from the ITypeDeclaration interface and specify it's properties. 188 |

189 | 190 |

191 | 192 |
193 | ConnectionTypes.cs: Top Block: ITypeDeclaration; Bottom Block: Built-in Float type 194 |

195 | 196 | 205 |
206 | 207 |
208 |
209 |
210 |
211 | 212 |
213 |

Customizing

214 |

215 | Even though you can already built small extensions with methods described above pretty well, to natively integrate Node Editor into your 216 | own editor extension you may want to cutomize it, from building your own editor interface to modifying and extending the framework itself. 217 |

218 |
219 | 220 |

Interface

221 |

222 | The provided Editor Window basically serves as the default Node Canvas explorer for all dependant extensions and single canvases, 223 | but also as a starting point to develop a custom Editor Window from. That means, you can savely delete it if you don't want it in your extension. 224 | In the following I'll outline all things you need to consider to build a basic Node Editor Interface in both Runtime and the Editor. 225 |

226 |
227 | 228 |

Saving and Loading Canvas and Editor State

229 |

230 | The Editor needs to store the currently opened Canvas (NodeCanvas) and it's Editor State (NodeEditorState). 231 | For an explanation of these, please look up the Framework Overview. You can save both using NodeEditor.SaveNodeCanvas 232 | and load them with NodeEditor.LoadNodeCanvas and NodeEditor.LoadEditorStates. 233 | Take reference from the default NodeEditorWindow to see how exactly these functions are integrated. 234 | The function AutoOpenCanvas also shows how to automatically open a canvas by double-clicking it's asset in the Editor. 235 |

236 |
237 | 238 |

Bringing the Canvas on screen

239 |

240 | First, you need to make sure that the NodeEditor is initiated using NodeEditor.checkInit, and that there is always a canvas loaded, else creating a new one. 241 | Before drawing you'll want to define the rect in which the canvas is drawn. No boundaries are set anymore on where the canvas is set, in how many subgroups, etc. 242 | Only the case of modifying the GUI.matrix scale before is not yet supported. You currently must assign the rect to the canvasRect property of the EditorState. 243 | In order to best account for errors, the following drawing statement is embedded in a try-catch block catching only UnityExceptions, unloading the old and creating a new canvas when an error occurs. 244 | Draw the canvas by passing both the NodeCanvas and the EditorState into NodeEditor.DrawCanvas, which will behave like an ordinary GUI control in most cases.

245 |
246 | 247 |

Custom GUISkin

248 |

249 | The GUISkin of the Node Editor can currently only be changed by modifying the NodeEditorGUI.cs source file or by simply replacing the textures. 250 | For the future a more extensive and seperated control over the GUISkin is planned. 251 |

252 | 253 |

Framework Overview

254 |

255 | This section aims to bring you a decent overview on how the framework is structured, so you can get to modify it quickly. 256 | This does not necessarily include implementation details – code sections that need extra detailing are commented. 257 | Also, this section is not only for those planning to get into the code, but for everyone to get an overview what he's working with:) 258 |

259 |
260 | 261 |

NodeCanvas and NodeEditorState

262 |

263 | Those two components essentially make up something you can load up into the Editor. Basically, the canvas is the important part with all the nodes and any additional information directly related to the Canvas. 264 | In contrary, the EditorState holds all information on the state, or in other words, on how the Canvas is presented. This includes zoom and pan values, selected Nodes, the canvasRect, etc. 265 | Not all of these values are actually saved with the asset, though. That structure allows for multiple 'views' on the same Canvas and editing it simultaneously.

266 |
267 | 268 |

The DrawCanvas function

269 |

270 | This function acts very similar to any other GUI control, with a few exceptions, and is responsible for drawing the Canvas. 271 | On the first glance it's just a wrapper for DrawSubCanvas, with the exception that it holds the OverlayGUI and NodeGUISkin code. 272 | DrawSubCanvas is used in the future for Nested Canvases, as the name proposes.
273 | In the first major block, the background texture is sliced and placd across the screen where needed, accounting for pan and zoom, relative to the rect center.
274 | In the function InputEvents all inputs are catched. It's well commented, so no further explanation is necessary here. It accounts for Rects that should be ignored for input.
275 | Then, the scale area gets initiated with a call to my custom solution GUIScaleUtility.BeginScale. Any GUI code afterwards is getting scaled appropriately.
276 | In the following, everything that needs to be scaled gets drawn, including temporal connections, node transitions, connections, bodies and knobs.
277 | Thereafter, the scale area gets closed again with another call to GUIScaleUtility.EndScale. The LateEvents function checks all inputs with secondary priority (after the nodes) 278 | just like InputEvents does, in this case it only makes sure the node can only be dragged when not clicking on a control (GUI.hotControl is 0). 279 |

280 |
281 | 282 |
283 |
284 |
285 |
286 | 287 |
288 |

Events

289 |

290 | (WIP)
291 | The Framework supports a multitude of Events which might be important during the editing process. Those Events can either be received 292 | by subscribing to the appropriate delegate in the NodeEditorCallbacks class or by extending from NodeEditorCallbackReceiver (which is a MonoBehaviour) 293 | and overriding the appropriate method. Both classes can be found in NodeEditorCallbackReceiver.cs.
294 | Current Events include: 295 |

296 | 310 |
311 | 312 |
313 |
314 |
315 |
316 | 317 |
318 |

Conclusion

319 |

320 | (WIP)
321 | I'm happy that the Node Editor has received so much positive comments and helpful critism since I posted it back in May 2015.
322 | Since then a few people took the time and motivation to contribute to the project which I appreciate very much! 323 | You can check all contributions out on the contributions page. 324 | But also those who use, test and report bugs are very important for this project.
325 | If you wish to contribute, you may take a look at the roadmap as a rough guideline what is planned and how you can help. Of course, own ideas are just as fine.
326 | Make sure to post or tell me if you are making an extension using Node Editor, may it be big or small, and notify me about any problems you may encounter:) 327 | This is vital to the project! Just make sure to account for the MIT License included with Node Editor.
328 | Also, you can always contact Seneral with a PM on the Forums or per Email at lev.gaeher@gmail.com:)
329 | I hope this Documentation has helped you understanding the NodeEditor, else feel free to suggest imrovements and ask me! 330 |

331 |
332 | 333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 | 342 | -------------------------------------------------------------------------------- /Docs/NodeEditorDoc_Resources/ConnectionTypes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Docs/NodeEditorDoc_Resources/ConnectionTypes.png -------------------------------------------------------------------------------- /Docs/NodeEditorDoc_Resources/NodeEditorDoc.css: -------------------------------------------------------------------------------- 1 | 2 | * body {max-width: 65%; margin: auto;} 3 | 4 | .page {} 5 | 6 | /* Special */ 7 | .pMid {text-align: center;} 8 | .img {width: 60%; object-fit: contain;} 9 | .ftImgDesc {font: 16px 'Calibri Light'; text-color: 'gray';} 10 | 11 | /* labels */ 12 | .ftLrgLbl {font: 28px 'Calibri Light';} 13 | .ftNrmLbl {font: 18px 'Calibri Light';} 14 | 15 | /* headers */ 16 | * h1 {text-align: center; font: 48px 'Calibri Light';} /* document title */ 17 | * h2 {text-align: center; font: 32px 'Calibri Light'; color: #0064FF;} /* header */ 18 | * h3 {text-align: center; font: 22px 'Calibri Light'; text-decoration: underline; } /* sub-header */ 19 | * h4 {text-align: left; font: 16px 'Calibri Light'; font-weight: bold;} /* section header */ 20 | 21 | /* standard defaults */ 22 | * p {font: 18px 'Calibri Light';} 23 | * li {font: 18px 'Calibri Light';} 24 | -------------------------------------------------------------------------------- /Docs/NodeEditorDoc_Resources/NodeEditorTitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Docs/NodeEditorDoc_Resources/NodeEditorTitle.png -------------------------------------------------------------------------------- /Docs/license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Baste Nesse Buanes 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Editor/Node_Editor/NodeEditorWindow.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System; 4 | using System.IO; 5 | using System.Collections.Generic; 6 | 7 | using NodeEditorFramework; 8 | using NodeEditorFramework.Utilities; 9 | 10 | namespace NodeEditorFramework 11 | { 12 | public class NodeEditorWindow : EditorWindow 13 | { 14 | // Information about current instance 15 | private static NodeEditorWindow _editor; 16 | public static NodeEditorWindow editor { get { AssureEditor (); return _editor; } } 17 | public static void AssureEditor () { if (_editor == null) CreateEditor (); } 18 | 19 | // Opened Canvas 20 | public NodeCanvas mainNodeCanvas; 21 | public NodeEditorState mainEditorState; 22 | public static NodeCanvas MainNodeCanvas { get { return editor.mainNodeCanvas; } } 23 | public static NodeEditorState MainEditorState { get { return editor.mainEditorState; } } 24 | public void AssureCanvas () { if (mainNodeCanvas == null) NewNodeCanvas (); } 25 | public static string openedCanvasPath; 26 | public string tempSessionPath; 27 | 28 | // GUI 29 | public static int sideWindowWidth = 400; 30 | private static Texture iconTexture; 31 | public Rect sideWindowRect { get { return new Rect (position.width - sideWindowWidth, 0, sideWindowWidth, position.height); } } 32 | public Rect canvasWindowRect { get { return new Rect (0, 0, position.width - sideWindowWidth, position.height); } } 33 | 34 | #region General 35 | 36 | [MenuItem ("Window/Node Editor")] 37 | public static void CreateEditor () 38 | { 39 | _editor = GetWindow (); 40 | _editor.minSize = new Vector2 (800, 600); 41 | NodeEditor.ClientRepaints += _editor.Repaint; 42 | NodeEditor.initiated = NodeEditor.InitiationError = false; 43 | 44 | iconTexture = ResourceManager.LoadTexture (EditorGUIUtility.isProSkin? "Textures/Icon_Dark.png" : "Textures/Icon_Light.png"); 45 | _editor.titleContent = new GUIContent ("Node Editor", iconTexture); 46 | } 47 | 48 | /// 49 | /// Handle opening canvas when double-clicking asset 50 | /// 51 | [UnityEditor.Callbacks.OnOpenAsset(1)] 52 | public static bool AutoOpenCanvas (int instanceID, int line) 53 | { 54 | if (Selection.activeObject != null && Selection.activeObject.GetType () == typeof(NodeCanvas)) 55 | { 56 | string NodeCanvasPath = AssetDatabase.GetAssetPath (instanceID); 57 | NodeEditorWindow.CreateEditor (); 58 | EditorWindow.GetWindow ().LoadNodeCanvas (NodeCanvasPath); 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | public void OnDestroy () 65 | { 66 | NodeEditor.ClientRepaints -= _editor.Repaint; 67 | 68 | #if UNITY_EDITOR 69 | // Remove callbacks 70 | EditorLoadingControl.lateEnteredPlayMode -= LoadCache; 71 | EditorLoadingControl.justLeftPlayMode -= LoadCache; 72 | EditorLoadingControl.justOpenedNewScene -= LoadCache; 73 | 74 | NodeEditorCallbacks.OnAddNode -= SaveNewNode; 75 | #endif 76 | } 77 | 78 | // Following section is all about caching the last editor session 79 | 80 | private void OnEnable () 81 | { 82 | tempSessionPath = Path.GetDirectoryName (AssetDatabase.GetAssetPath (MonoScript.FromScriptableObject (this))) + "/Resources"; 83 | LoadCache (); 84 | 85 | #if UNITY_EDITOR 86 | // This makes sure the Node Editor is reinitiated after the Playmode changed 87 | EditorLoadingControl.lateEnteredPlayMode -= LoadCache; 88 | EditorLoadingControl.lateEnteredPlayMode += LoadCache; 89 | 90 | EditorLoadingControl.justLeftPlayMode -= LoadCache; 91 | EditorLoadingControl.justLeftPlayMode += LoadCache; 92 | 93 | EditorLoadingControl.justOpenedNewScene -= LoadCache; 94 | EditorLoadingControl.justOpenedNewScene += LoadCache; 95 | 96 | NodeEditorCallbacks.OnAddNode -= SaveNewNode; 97 | NodeEditorCallbacks.OnAddNode += SaveNewNode; 98 | #endif 99 | } 100 | 101 | #endregion 102 | 103 | #region GUI 104 | 105 | private void OnGUI () 106 | { 107 | // Initiation 108 | NodeEditor.checkInit (); 109 | if (NodeEditor.InitiationError) 110 | { 111 | GUILayout.Label ("Node Editor Initiation failed! Check console for more information!"); 112 | return; 113 | } 114 | AssureEditor (); 115 | AssureCanvas (); 116 | 117 | // Specify the Canvas rect in the EditorState 118 | mainEditorState.canvasRect = canvasWindowRect; 119 | // If you want to use GetRect: 120 | // Rect canvasRect = GUILayoutUtility.GetRect (600, 600); 121 | // if (Event.current.type != EventType.Layout) 122 | // mainEditorState.canvasRect = canvasRect; 123 | 124 | // Perform drawing with error-handling 125 | try 126 | { 127 | NodeEditor.DrawCanvas (mainNodeCanvas, mainEditorState); 128 | } 129 | catch (UnityException e) 130 | { // on exceptions in drawing flush the canvas to avoid locking the ui. 131 | NewNodeCanvas (); 132 | NodeEditor.ReInit (true); 133 | Debug.LogError ("Unloaded Canvas due to exception when drawing!"); 134 | Debug.LogException (e); 135 | } 136 | 137 | // Draw Side Window 138 | sideWindowWidth = Math.Min (600, Math.Max (200, (int)(position.width / 5))); 139 | NodeEditorGUI.StartNodeGUI (); 140 | GUILayout.BeginArea (sideWindowRect, GUI.skin.box); 141 | DrawSideWindow (); 142 | GUILayout.EndArea (); 143 | NodeEditorGUI.EndNodeGUI (); 144 | } 145 | 146 | private void DrawSideWindow () 147 | { 148 | GUILayout.Label (new GUIContent ("Node Editor (" + mainNodeCanvas.name + ")", "Opened Canvas path: " + openedCanvasPath), NodeEditorGUI.nodeLabelBold); 149 | 150 | if (GUILayout.Button (new GUIContent ("Save Canvas", "Saves the Canvas to a Canvas Save File in the Assets Folder"))) 151 | { 152 | string path = EditorUtility.SaveFilePanelInProject ("Save Node Canvas", "Node Canvas", "asset", "", NodeEditor.editorPath + "Resources/Saves/"); 153 | if (!string.IsNullOrEmpty (path)) 154 | SaveNodeCanvas (path); 155 | } 156 | 157 | if (GUILayout.Button (new GUIContent ("Load Canvas", "Loads the Canvas from a Canvas Save File in the Assets Folder"))) 158 | { 159 | string path = EditorUtility.OpenFilePanel ("Load Node Canvas", NodeEditor.editorPath + "Resources/Saves/", "asset"); 160 | if (!path.Contains (Application.dataPath)) 161 | { 162 | if (!string.IsNullOrEmpty (path)) 163 | ShowNotification (new GUIContent ("You should select an asset inside your project folder!")); 164 | } 165 | else 166 | { 167 | path = path.Replace (Application.dataPath, "Assets"); 168 | LoadNodeCanvas (path); 169 | } 170 | } 171 | 172 | if (GUILayout.Button (new GUIContent ("New Canvas", "Loads an empty Canvas"))) 173 | NewNodeCanvas (); 174 | 175 | if (GUILayout.Button (new GUIContent ("Recalculate All", "Initiates complete recalculate. Usually does not need to be triggered manually."))) 176 | NodeEditor.RecalculateAll (mainNodeCanvas); 177 | 178 | if (GUILayout.Button ("Force Re-Init")) 179 | NodeEditor.ReInit (true); 180 | 181 | NodeEditorGUI.knobSize = EditorGUILayout.IntSlider (new GUIContent ("Handle Size", "The size of the Node Input/Output handles"), NodeEditorGUI.knobSize, 12, 20); 182 | mainEditorState.zoom = EditorGUILayout.Slider (new GUIContent ("Zoom", "Use the Mousewheel. Seriously."), mainEditorState.zoom, 0.6f, 2); 183 | 184 | if (mainEditorState.selectedNode != null) 185 | if (Event.current.type != EventType.Ignore) 186 | mainEditorState.selectedNode.DrawNodePropertyEditor(); 187 | } 188 | 189 | #endregion 190 | 191 | #region Cache 192 | 193 | private void SaveNewNode (Node node) 194 | { 195 | if (!mainNodeCanvas.nodes.Contains (node)) 196 | throw new UnityException ("Cache system: Writing new Node to save file failed as Node is not part of the Cache!"); 197 | string path = tempSessionPath + "/LastSession.asset"; 198 | if (AssetDatabase.GetAssetPath (mainNodeCanvas) != path) 199 | throw new UnityException ("Cache system error: Current Canvas is not saved as the temporary cache!"); 200 | NodeEditorSaveManager.AddSubAsset (node, path); 201 | foreach (NodeKnob knob in node.nodeKnobs) 202 | NodeEditorSaveManager.AddSubAsset (knob, path); 203 | 204 | AssetDatabase.SaveAssets (); 205 | AssetDatabase.Refresh (); 206 | } 207 | 208 | private void SaveCache () 209 | { 210 | string canvasName = mainNodeCanvas.name; 211 | EditorPrefs.SetString ("NodeEditorLastSession", canvasName); 212 | NodeEditorSaveManager.SaveNodeCanvas (tempSessionPath + "/LastSession.asset", false, mainNodeCanvas, mainEditorState); 213 | mainNodeCanvas.name = canvasName; 214 | 215 | AssetDatabase.SaveAssets (); 216 | AssetDatabase.Refresh (); 217 | } 218 | 219 | private void LoadCache () 220 | { 221 | string lastSessionName = EditorPrefs.GetString ("NodeEditorLastSession"); 222 | string path = tempSessionPath + "/LastSession.asset"; 223 | mainNodeCanvas = NodeEditorSaveManager.LoadNodeCanvas (path, false); 224 | if (mainNodeCanvas == null) 225 | NewNodeCanvas (); 226 | else 227 | { 228 | mainNodeCanvas.name = lastSessionName; 229 | List editorStates = NodeEditorSaveManager.LoadEditorStates (path, false); 230 | if (editorStates == null || editorStates.Count == 0 || (mainEditorState = editorStates.Find (x => x.name == "MainEditorState")) == null ) 231 | { // New NodeEditorState 232 | mainEditorState = CreateInstance (); 233 | mainEditorState.canvas = mainNodeCanvas; 234 | mainEditorState.name = "MainEditorState"; 235 | NodeEditorSaveManager.AddSubAsset (mainEditorState, path); 236 | AssetDatabase.SaveAssets (); 237 | AssetDatabase.Refresh (); 238 | } 239 | } 240 | } 241 | 242 | private void DeleteCache () 243 | { 244 | string lastSession = EditorPrefs.GetString ("NodeEditorLastSession"); 245 | if (!String.IsNullOrEmpty (lastSession)) 246 | { 247 | AssetDatabase.DeleteAsset (tempSessionPath + "/" + lastSession); 248 | AssetDatabase.Refresh (); 249 | } 250 | EditorPrefs.DeleteKey ("NodeEditorLastSession"); 251 | } 252 | 253 | #endregion 254 | 255 | #region Save/Load 256 | 257 | /// 258 | /// Saves the mainNodeCanvas and it's associated mainEditorState as an asset at path 259 | /// 260 | public void SaveNodeCanvas (string path) 261 | { 262 | NodeEditorSaveManager.SaveNodeCanvas (path, true, mainNodeCanvas, mainEditorState); 263 | Repaint (); 264 | } 265 | 266 | /// 267 | /// Loads the mainNodeCanvas and it's associated mainEditorState from an asset at path 268 | /// 269 | public void LoadNodeCanvas (string path) 270 | { 271 | // Load the NodeCanvas 272 | mainNodeCanvas = NodeEditorSaveManager.LoadNodeCanvas (path, true); 273 | if (mainNodeCanvas == null) 274 | { 275 | Debug.Log ("Error loading NodeCanvas from '" + path + "'!"); 276 | NewNodeCanvas (); 277 | return; 278 | } 279 | 280 | // Load the associated MainEditorState 281 | List editorStates = NodeEditorSaveManager.LoadEditorStates (path, true); 282 | if (editorStates.Count == 0) 283 | { 284 | mainEditorState = ScriptableObject.CreateInstance (); 285 | Debug.LogError ("The save file '" + path + "' did not contain an associated NodeEditorState!"); 286 | } 287 | else 288 | { 289 | mainEditorState = editorStates.Find (x => x.name == "MainEditorState"); 290 | if (mainEditorState == null) mainEditorState = editorStates[0]; 291 | } 292 | mainEditorState.canvas = mainNodeCanvas; 293 | 294 | openedCanvasPath = path; 295 | NodeEditor.RecalculateAll (mainNodeCanvas); 296 | SaveCache (); 297 | Repaint (); 298 | } 299 | 300 | /// 301 | /// Creates and opens a new empty node canvas 302 | /// 303 | public void NewNodeCanvas () 304 | { 305 | // New NodeCanvas 306 | mainNodeCanvas = CreateInstance (); 307 | mainNodeCanvas.name = "New Canvas"; 308 | // New NodeEditorState 309 | mainEditorState = CreateInstance (); 310 | mainEditorState.canvas = mainNodeCanvas; 311 | mainEditorState.name = "MainEditorState"; 312 | 313 | openedCanvasPath = ""; 314 | SaveCache (); 315 | } 316 | 317 | #endregion 318 | } 319 | } -------------------------------------------------------------------------------- /Editor/Node_Editor/RTNE_InspectorGUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Collections.Generic; 4 | using NodeEditorFramework; 5 | 6 | namespace NodeEditorFramework 7 | { 8 | [CustomEditor (typeof(RuntimeNodeEditor))] 9 | public class RTNE_InspectorGUI : Editor 10 | { 11 | public RuntimeNodeEditor RTNE; 12 | 13 | public void OnEnable () 14 | { 15 | RTNE = (RuntimeNodeEditor)target; 16 | RTNE.canvasPath = RTNE.canvas == null? "" : AssetDatabase.GetAssetPath (RTNE.canvas); 17 | } 18 | 19 | public override void OnInspectorGUI () 20 | { 21 | NodeCanvas canvas = EditorGUILayout.ObjectField ("Canvas", RTNE.canvas, typeof(NodeCanvas), false) as NodeCanvas; 22 | if (canvas != RTNE.canvas) 23 | { 24 | RTNE.canvas = canvas; 25 | RTNE.canvasPath = RTNE.canvas == null? "" : AssetDatabase.GetAssetPath (RTNE.canvas); 26 | } 27 | 28 | RTNE.screenSize = !EditorGUILayout.BeginToggleGroup (new GUIContent ("Specify Rect", "Specify Rects explicitly instead of adapting to the screen size"), !RTNE.screenSize); 29 | RTNE.specifiedRootRect = EditorGUILayout.RectField (new GUIContent ("Root Rect", "The root/group rect of the actual canvas rect. If left blank it is ignored."), RTNE.specifiedRootRect); 30 | RTNE.specifiedCanvasRect = EditorGUILayout.RectField (new GUIContent ("Canvas Rect", "The rect of the canvas."), RTNE.specifiedCanvasRect); 31 | EditorGUILayout.EndToggleGroup (); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Editor/Node_Editor/Resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Editor/Node_Editor/Resources/.gitkeep -------------------------------------------------------------------------------- /Node_Editor/Framework/ConnectionTypes.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Collections.Generic; 6 | 7 | using NodeEditorFramework.Utilities; 8 | 9 | namespace NodeEditorFramework 10 | { 11 | public enum ConnectionDrawMethod { Bezier, StraightLine } 12 | 13 | /// 14 | /// Handles fetching and storing of all ConnecionTypes 15 | /// 16 | public static class ConnectionTypes 17 | { 18 | private static Type NullType { get { return typeof(ConnectionTypes); } } 19 | 20 | // Static consistent information about types 21 | private static Dictionary types; 22 | 23 | /// 24 | /// Gets the Type the specified identifier representates or, if not declared and checked, creates a new type data for the passed type 25 | /// 26 | public static Type GetType (string typeName, bool createIfNotDeclared) 27 | { 28 | TypeData data = GetTypeData (typeName, createIfNotDeclared); 29 | return data != null? data.Type : NullType; 30 | } 31 | 32 | /// 33 | /// Gets the type data with the specified identifier or, if not declared and checked, creates a new one when a valid type name with namespace is passed 34 | /// 35 | public static TypeData GetTypeData (string typeName, bool createIfNotDeclared) 36 | { 37 | if (types == null || types.Count == 0) 38 | NodeEditor.ReInit (false); 39 | TypeData typeData; 40 | if (!types.TryGetValue (typeName, out typeData)) 41 | { 42 | if (createIfNotDeclared) 43 | { 44 | Type type = Type.GetType (typeName); 45 | if (type == null) 46 | { 47 | typeData = types.First ().Value; 48 | Debug.LogError ("No TypeData defined for: " + typeName + " and type could not be found either!"); 49 | } 50 | else 51 | { 52 | typeData = new TypeData (type); 53 | types.Add (typeName, typeData); 54 | } 55 | } 56 | else 57 | { 58 | typeData = types.First ().Value; 59 | Debug.LogError ("No TypeData defined for: " + typeName + "!"); 60 | } 61 | } 62 | return typeData; 63 | } 64 | 65 | /// 66 | /// Gets the type data for the specified type or, if not declared and checked, creates a new one for that type 67 | /// 68 | public static TypeData GetTypeData (Type type, bool createIfNotDeclared) 69 | { 70 | if (types == null || types.Count == 0) 71 | NodeEditor.ReInit (false); 72 | TypeData typeData = types.Values.First ((TypeData tData) => tData.Type == type); 73 | if (typeData == null) 74 | { 75 | if (createIfNotDeclared) 76 | { 77 | typeData = new TypeData (type); 78 | types.Add (type.FullName, typeData); 79 | } 80 | else 81 | { 82 | typeData = types.First ().Value; 83 | Debug.LogError ("No TypeData defined for: " + type.FullName + "!"); 84 | } 85 | } 86 | return typeData; 87 | } 88 | 89 | /// 90 | /// Fetches every Type Declaration in the script assemblies and the executing one, if the NodeEditor is packed into a .dll 91 | /// 92 | internal static void FetchTypes () 93 | { 94 | types = new Dictionary { { "None", new TypeData () } }; 95 | 96 | IEnumerable scriptAssemblies = AppDomain.CurrentDomain.GetAssemblies ().Where ((Assembly assembly) => assembly.FullName.Contains ("Assembly")); 97 | foreach (Assembly assembly in scriptAssemblies) 98 | { // Iterate through each script assembly 99 | IEnumerable typeDeclarations = assembly.GetTypes ().Where (T => T.IsClass && !T.IsAbstract && T.GetInterface (typeof (IConnectionTypeDeclaration).FullName) != null); 100 | foreach (Type type in typeDeclarations) 101 | { // get all type declarations and create a typeData for them 102 | IConnectionTypeDeclaration typeDecl = assembly.CreateInstance (type.FullName) as IConnectionTypeDeclaration; 103 | if (typeDecl == null) 104 | throw new UnityException ("Error with Type Declaration " + type.FullName); 105 | types.Add (typeDecl.Identifier, new TypeData (typeDecl)); 106 | } 107 | } 108 | } 109 | } 110 | 111 | public class TypeData 112 | { 113 | private IConnectionTypeDeclaration declaration; 114 | public Type Type { get; private set; } 115 | public Color Color { get; private set; } 116 | public Texture2D InKnobTex { get; private set; } 117 | public Texture2D OutKnobTex { get; private set; } 118 | 119 | internal TypeData (IConnectionTypeDeclaration typeDecl) 120 | { 121 | declaration = typeDecl; 122 | Type = declaration.Type; 123 | Color = declaration.Color; 124 | 125 | InKnobTex = ResourceManager.GetTintedTexture (declaration.InKnobTex, Color); 126 | OutKnobTex = ResourceManager.GetTintedTexture (declaration.OutKnobTex, Color); 127 | 128 | if (InKnobTex == null || InKnobTex == null) 129 | throw new UnityException ("Invalid textures for default typeData " + declaration.Identifier + "!"); 130 | } 131 | 132 | internal TypeData (Type type) 133 | { 134 | declaration = null; 135 | Type = type; 136 | Color = Color.white;//(float)type.GetHashCode() / (int.MaxValue/3); 137 | 138 | InKnobTex = ResourceManager.GetTintedTexture ("Textures/In_Knob.png", Color); 139 | OutKnobTex = ResourceManager.GetTintedTexture ("Textures/Out_Knob.png", Color); 140 | 141 | if (InKnobTex == null || InKnobTex == null) 142 | throw new UnityException ("Invalid textures for default typeData " + type.ToString () + "!"); 143 | } 144 | 145 | internal TypeData () 146 | { 147 | declaration = null; 148 | Type = typeof(object); 149 | Color = Color.white; 150 | InKnobTex = ResourceManager.LoadTexture ("Textures/In_Knob.png"); 151 | OutKnobTex = ResourceManager.LoadTexture ("Textures/Out_Knob.png"); 152 | 153 | if (InKnobTex == null || InKnobTex == null) 154 | throw new UnityException ("Invalid textures for default typeData!"); 155 | } 156 | 157 | public bool isValid () 158 | { 159 | return Type != null && InKnobTex != null && OutKnobTex != null; 160 | } 161 | } 162 | 163 | public interface IConnectionTypeDeclaration 164 | { 165 | string Identifier { get; } 166 | Type Type { get; } 167 | Color Color { get; } 168 | string InKnobTex { get; } 169 | string OutKnobTex { get; } 170 | } 171 | 172 | // TODO: Node Editor: Built-In Connection Types 173 | public class FloatType : IConnectionTypeDeclaration 174 | { 175 | public string Identifier { get { return "Float"; } } 176 | public Type Type { get { return typeof(float); } } 177 | public Color Color { get { return Color.cyan; } } 178 | public string InKnobTex { get { return "Textures/In_Knob.png"; } } 179 | public string OutKnobTex { get { return "Textures/Out_Knob.png"; } } 180 | } 181 | 182 | 183 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/ConnectionTypes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4be5c7fe89350d249b9fd20d52cfb700 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/Node.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | 6 | using NodeEditorFramework; 7 | using NodeEditorFramework.Utilities; 8 | 9 | namespace NodeEditorFramework 10 | { 11 | public abstract class Node : ScriptableObject 12 | { 13 | public Rect rect = new Rect (); 14 | internal Vector2 contentOffset = Vector2.zero; 15 | [SerializeField] 16 | public List nodeKnobs = new List (); 17 | 18 | // Calculation graph 19 | // [NonSerialized] 20 | [SerializeField, HideInInspector] 21 | public List Inputs = new List(); 22 | // [NonSerialized] 23 | [SerializeField, HideInInspector] 24 | public List Outputs = new List(); 25 | [HideInInspector] 26 | [NonSerialized] 27 | internal bool calculated = true; 28 | 29 | #region General 30 | 31 | /// 32 | /// Init the Node Base after the Node has been created. This includes adding to canvas, and to calculate for the first time 33 | /// 34 | protected internal void InitBase () 35 | { 36 | Calculate (); 37 | if (!NodeEditor.curNodeCanvas.nodes.Contains (this)) 38 | NodeEditor.curNodeCanvas.nodes.Add (this); 39 | #if UNITY_EDITOR 40 | if (name == "") 41 | name = UnityEditor.ObjectNames.NicifyVariableName (GetID); 42 | #endif 43 | } 44 | 45 | /// 46 | /// Deletes this Node from curNodeCanvas and the save file 47 | /// 48 | public void Delete () 49 | { 50 | if (!NodeEditor.curNodeCanvas.nodes.Contains (this)) 51 | throw new UnityException ("The Node " + name + " does not exist on the Canvas " + NodeEditor.curNodeCanvas.name + "!"); 52 | NodeEditorCallbacks.IssueOnDeleteNode (this); 53 | NodeEditor.curNodeCanvas.nodes.Remove (this); 54 | for (int outCnt = 0; outCnt < Outputs.Count; outCnt++) 55 | { 56 | NodeOutput output = Outputs [outCnt]; 57 | while (output.connections.Count != 0) 58 | output.connections[0].RemoveConnection (); 59 | DestroyImmediate (output, true); 60 | } 61 | for (int inCnt = 0; inCnt < Inputs.Count; inCnt++) 62 | { 63 | NodeInput input = Inputs [inCnt]; 64 | if (input.connection != null) 65 | input.connection.connections.Remove (input); 66 | DestroyImmediate (input, true); 67 | } 68 | for (int knobCnt = 0; knobCnt < nodeKnobs.Count; knobCnt++) 69 | { // Inputs/Outputs need specific treatment, unfortunately 70 | if (nodeKnobs[knobCnt] != null) 71 | DestroyImmediate (nodeKnobs[knobCnt], true); 72 | } 73 | DestroyImmediate (this, true); 74 | } 75 | 76 | public static Node Create (string nodeID, Vector2 position) 77 | { 78 | Node node = NodeTypes.getDefaultNode (nodeID); 79 | if (node == null) 80 | throw new UnityException ("Cannot create Node with id " + nodeID + " as no such Node type is registered!"); 81 | 82 | node = node.Create (position); 83 | node.InitBase (); 84 | 85 | NodeEditorCallbacks.IssueOnAddNode (node); 86 | return node; 87 | } 88 | 89 | /// 90 | /// Makes sure this Node has migrated from the previous save version of NodeKnobs to the current mixed and generic one 91 | /// 92 | internal void CheckNodeKnobMigration () 93 | { // TODO: Migration from previous NodeKnob system; Remove later on 94 | if (nodeKnobs.Count == 0 && (Inputs.Count != 0 || Outputs.Count != 0)) 95 | { 96 | nodeKnobs.AddRange (Inputs.Cast ()); 97 | nodeKnobs.AddRange (Outputs.Cast ()); 98 | } 99 | } 100 | 101 | #endregion 102 | 103 | #region Node Type methods (abstract) 104 | 105 | /// 106 | /// Get the ID of the Node 107 | /// 108 | public abstract string GetID { get; } 109 | 110 | /// 111 | /// Create an instance of this Node at the given position 112 | /// 113 | public abstract Node Create (Vector2 pos); 114 | 115 | /// 116 | /// Draw the Node immediately 117 | /// 118 | protected internal abstract void NodeGUI (); 119 | 120 | /// 121 | /// Used to display a custom node property editor in the side window of the NodeEditorWindow 122 | /// Optionally override this to implement 123 | /// 124 | public virtual void DrawNodePropertyEditor() { } 125 | 126 | /// 127 | /// Calculate the outputs of this Node 128 | /// Return Success/Fail 129 | /// Might be dependant on previous nodes 130 | /// 131 | public abstract bool Calculate (); 132 | 133 | #endregion 134 | 135 | #region Node Type Properties 136 | 137 | /// 138 | /// Does this node allow recursion? Recursion is allowed if atleast a single Node in the loop allows for recursion 139 | /// 140 | public virtual bool AllowRecursion { get { return false; } } 141 | 142 | /// 143 | /// Should the following Nodes be calculated after finishing the Calculation function of this node? 144 | /// 145 | public virtual bool ContinueCalculation { get { return true; } } 146 | 147 | /// 148 | /// Does this Node accepts Transitions? 149 | /// 150 | public virtual bool AcceptsTranstitions { get { return false; } } 151 | 152 | #endregion 153 | 154 | #region Protected Callbacks 155 | 156 | /// 157 | /// Callback when the node is deleted 158 | /// 159 | protected internal virtual void OnDelete () {} 160 | 161 | /// 162 | /// Callback when the NodeInput was assigned a new connection 163 | /// 164 | protected internal virtual void OnAddInputConnection (NodeInput input) {} 165 | 166 | /// 167 | /// Callback when the NodeOutput was assigned a new connection (the last in the list) 168 | /// 169 | protected internal virtual void OnAddOutputConnection (NodeOutput output) {} 170 | 171 | #endregion 172 | 173 | #region Additional Serialization 174 | 175 | /// 176 | /// Returns all additional ScriptableObjects this Node holds. 177 | /// That means only the actual SOURCES, simple REFERENCES will not be returned 178 | /// This means all SciptableObjects returned here do not have it's source elsewhere 179 | /// 180 | protected internal virtual ScriptableObject[] GetScriptableObjects () { return new ScriptableObject[0]; } 181 | 182 | /// 183 | /// Replaces all REFERENCES aswell as SOURCES of any ScriptableObjects this Node holds with the cloned versions in the serialization process. 184 | /// 185 | protected internal virtual void CopyScriptableObjects (System.Func replaceSerializableObject) {} 186 | 187 | #endregion 188 | 189 | #region Node and Knob Drawing 190 | 191 | /// 192 | /// Draws the node frame and calls NodeGUI. Can be overridden to customize drawing. 193 | /// 194 | protected internal virtual void DrawNode () 195 | { 196 | // TODO: Node Editor Feature: Custom Windowing System 197 | // Create a rect that is adjusted to the editor zoom 198 | Rect nodeRect = rect; 199 | nodeRect.position += NodeEditor.curEditorState.zoomPanAdjust; 200 | contentOffset = new Vector2 (0, 20); 201 | 202 | // Create a headerRect out of the previous rect and draw it, marking the selected node as such by making the header bold 203 | Rect headerRect = new Rect (nodeRect.x, nodeRect.y, nodeRect.width, contentOffset.y); 204 | GUI.Label (headerRect, name, NodeEditor.curEditorState.selectedNode == this? NodeEditorGUI.nodeBoxBold : NodeEditorGUI.nodeBox); 205 | 206 | // Begin the body frame around the NodeGUI 207 | Rect bodyRect = new Rect (nodeRect.x, nodeRect.y + contentOffset.y, nodeRect.width, nodeRect.height - contentOffset.y); 208 | GUI.BeginGroup (bodyRect, GUI.skin.box); 209 | bodyRect.position = Vector2.zero; 210 | GUILayout.BeginArea (bodyRect, GUI.skin.box); 211 | // Call NodeGUI 212 | GUI.changed = false; 213 | NodeGUI (); 214 | // End NodeGUI frame 215 | GUILayout.EndArea (); 216 | GUI.EndGroup (); 217 | } 218 | 219 | /// 220 | /// Draws the nodeKnobs 221 | /// 222 | protected internal virtual void DrawKnobs () 223 | { 224 | CheckNodeKnobMigration (); 225 | foreach (NodeKnob knob in nodeKnobs) 226 | knob.DrawKnob (); 227 | } 228 | 229 | /// 230 | /// Draws the node curves 231 | /// 232 | protected internal virtual void DrawConnections () 233 | { 234 | CheckNodeKnobMigration (); 235 | if (Event.current.type != EventType.Repaint) 236 | return; 237 | foreach (NodeOutput output in Outputs) 238 | { 239 | Vector2 startPos = output.GetGUIKnob ().center; 240 | Vector2 startDir = output.GetDirection (); 241 | 242 | foreach (NodeInput input in output.connections) 243 | { 244 | NodeEditorGUI.DrawConnection (startPos, 245 | startDir, 246 | input.GetGUIKnob ().center, 247 | input.GetDirection (), 248 | ConnectionTypes.GetTypeData (output.type, true).Color); 249 | } 250 | } 251 | } 252 | 253 | #endregion 254 | 255 | #region Node Calculation Utility 256 | 257 | /// 258 | /// Checks if there are no unassigned and no null-value inputs. 259 | /// 260 | protected internal bool allInputsReady () 261 | { 262 | foreach (NodeInput input in Inputs) 263 | { 264 | if (input.connection == null || input.connection.IsValueNull) 265 | return false; 266 | } 267 | return true; 268 | } 269 | /// 270 | /// Checks if there are any unassigned inputs. 271 | /// 272 | protected internal bool hasUnassignedInputs () 273 | { 274 | foreach (NodeInput input in Inputs) 275 | { 276 | if (input.connection == null) 277 | return true; 278 | } 279 | return false; 280 | } 281 | 282 | /// 283 | /// Returns whether every direct dexcendant has been calculated 284 | /// 285 | protected internal bool descendantsCalculated () 286 | { 287 | foreach (NodeInput input in Inputs) 288 | { 289 | if (input.connection != null && !input.connection.body.calculated) 290 | return false; 291 | } 292 | return true; 293 | } 294 | 295 | /// 296 | /// Returns whether the node acts as an input (no inputs or no inputs assigned) 297 | /// 298 | protected internal bool isInput () 299 | { 300 | foreach (NodeInput input in Inputs) 301 | { 302 | if (input.connection != null) 303 | return false; 304 | } 305 | return true; 306 | } 307 | 308 | #endregion 309 | 310 | #region Node Knob Utility 311 | 312 | // -- OUTPUTS -- 313 | 314 | /// 315 | /// Creates and output on your Node of the given type. 316 | /// 317 | public void CreateOutput (string outputName, string outputType) 318 | { 319 | NodeOutput.Create (this, outputName, outputType); 320 | } 321 | /// 322 | /// Creates and output on this Node of the given type at the specified NodeSide. 323 | /// 324 | public void CreateOutput (string outputName, string outputType, NodeSide nodeSide) 325 | { 326 | NodeOutput.Create (this, outputName, outputType, nodeSide); 327 | } 328 | /// 329 | /// Creates and output on this Node of the given type at the specified NodeSide and position. 330 | /// 331 | public void CreateOutput (string outputName, string outputType, NodeSide nodeSide, float sidePosition) 332 | { 333 | NodeOutput.Create (this, outputName, outputType, nodeSide, sidePosition); 334 | } 335 | 336 | /// 337 | /// Aligns the OutputKnob on it's NodeSide with the last GUILayout control drawn. 338 | /// 339 | /// The index of the output in the Node's Outputs list 340 | protected void OutputKnob (int outputIdx) 341 | { 342 | if (Event.current.type == EventType.Repaint) 343 | Outputs[outputIdx].SetPosition (); 344 | } 345 | 346 | /// 347 | /// Returns the output knob that is at the position on this node or null 348 | /// 349 | public NodeOutput GetOutputAtPos (Vector2 pos) 350 | { 351 | foreach (NodeOutput output in Outputs) 352 | { // Search for an output at the position 353 | if (output.GetScreenKnob ().Contains (new Vector3 (pos.x, pos.y))) 354 | return output; 355 | } 356 | return null; 357 | } 358 | 359 | 360 | // -- INPUTS -- 361 | 362 | /// 363 | /// Creates and input on your Node of the given type. 364 | /// 365 | public void CreateInput (string inputName, string inputType) 366 | { 367 | NodeInput.Create (this, inputName, inputType); 368 | } 369 | /// 370 | /// Creates and input on this Node of the given type at the specified NodeSide. 371 | /// 372 | public void CreateInput (string inputName, string inputType, NodeSide nodeSide) 373 | { 374 | NodeInput.Create (this, inputName, inputType, nodeSide); 375 | } 376 | /// 377 | /// Creates and input on this Node of the given type at the specified NodeSide and position. 378 | /// 379 | public void CreateInput (string inputName, string inputType, NodeSide nodeSide, float sidePosition) 380 | { 381 | NodeInput.Create (this, inputName, inputType, nodeSide, sidePosition); 382 | } 383 | 384 | /// 385 | /// Aligns the InputKnob on it's NodeSide with the last GUILayout control drawn. 386 | /// 387 | /// The index of the input in the Node's Inputs list 388 | protected void InputKnob (int inputIdx) 389 | { 390 | if (Event.current.type == EventType.Repaint) 391 | Inputs[inputIdx].SetPosition (); 392 | } 393 | 394 | /// 395 | /// Returns the input knob that is at the position on this node or null 396 | /// 397 | public NodeInput GetInputAtPos (Vector2 pos) 398 | { 399 | foreach (NodeInput input in Inputs) 400 | { // Search for an input at the position 401 | if (input.GetScreenKnob ().Contains (new Vector3 (pos.x, pos.y))) 402 | return input; 403 | } 404 | return null; 405 | } 406 | 407 | #endregion 408 | 409 | #region Recursive Search Utility 410 | 411 | /// 412 | /// Recursively checks whether this node is a child of the other node 413 | /// 414 | public bool isChildOf (Node otherNode) 415 | { 416 | if (otherNode == null || otherNode == this) 417 | return false; 418 | if (BeginRecursiveSearchLoop ()) return false; 419 | foreach (NodeInput input in Inputs) 420 | { 421 | NodeOutput connection = input.connection; 422 | if (connection != null && connection.body != startRecursiveSearchNode) 423 | { 424 | if (connection.body == otherNode || connection.body.isChildOf (otherNode)) 425 | { 426 | StopRecursiveSearchLoop (); 427 | return true; 428 | } 429 | } 430 | } 431 | EndRecursiveSearchLoop (); 432 | return false; 433 | } 434 | 435 | /// 436 | /// Recursively checks whether this node is in a loop 437 | /// 438 | internal bool isInLoop () 439 | { 440 | if (BeginRecursiveSearchLoop ()) return this == startRecursiveSearchNode; 441 | foreach (NodeInput input in Inputs) 442 | { 443 | NodeOutput connection = input.connection; 444 | if (connection != null && connection.body.isInLoop ()) 445 | { 446 | StopRecursiveSearchLoop (); 447 | return true; 448 | } 449 | } 450 | EndRecursiveSearchLoop (); 451 | return false; 452 | } 453 | 454 | /// 455 | /// Recursively checks whether any node in the loop to be made allows recursion. 456 | /// Other node is the node this node needs connect to in order to fill the loop (other node being the node coming AFTER this node). 457 | /// That means isChildOf has to be confirmed before calling this! 458 | /// 459 | internal bool allowsLoopRecursion (Node otherNode) 460 | { 461 | if (AllowRecursion) 462 | return true; 463 | if (otherNode == null) 464 | return false; 465 | if (BeginRecursiveSearchLoop ()) return false; 466 | foreach (NodeInput input in Inputs) 467 | { 468 | NodeOutput connection = input.connection; 469 | if (connection != null && connection.body != startRecursiveSearchNode) 470 | { 471 | if (connection.body.allowsLoopRecursion (otherNode)) 472 | { 473 | StopRecursiveSearchLoop (); 474 | return true; 475 | } 476 | } 477 | } 478 | EndRecursiveSearchLoop (); 479 | return false; 480 | } 481 | 482 | /// 483 | /// A recursive function to clear all calculations depending on this node. 484 | /// Usually does not need to be called manually 485 | /// 486 | public void ClearCalculation () 487 | { 488 | if (BeginRecursiveSearchLoop ()) return; 489 | calculated = false; 490 | foreach (NodeOutput output in Outputs) 491 | { 492 | foreach (NodeInput input in output.connections) 493 | input.body.ClearCalculation (); 494 | } 495 | EndRecursiveSearchLoop (); 496 | } 497 | 498 | #region Recursive Search Helpers 499 | 500 | private List recursiveSearchSurpassed; 501 | private Node startRecursiveSearchNode; // Temporary start node for recursive searches 502 | 503 | /// 504 | /// Begins the recursive search loop and returns whether this node has already been searched 505 | /// 506 | internal bool BeginRecursiveSearchLoop () 507 | { 508 | if (startRecursiveSearchNode == null || recursiveSearchSurpassed == null) 509 | { // Start search 510 | recursiveSearchSurpassed = new List (); 511 | startRecursiveSearchNode = this; 512 | } 513 | 514 | if (recursiveSearchSurpassed.Contains (this)) 515 | return true; 516 | recursiveSearchSurpassed.Add (this); 517 | return false; 518 | } 519 | 520 | /// 521 | /// Ends the recursive search loop if this was the start node 522 | /// 523 | internal void EndRecursiveSearchLoop () 524 | { 525 | if (startRecursiveSearchNode == this) 526 | { // End search 527 | recursiveSearchSurpassed = null; 528 | startRecursiveSearchNode = null; 529 | } 530 | } 531 | 532 | /// 533 | /// Stops the recursive search loop immediately. Call when you found what you needed. 534 | /// 535 | internal void StopRecursiveSearchLoop () 536 | { 537 | recursiveSearchSurpassed = null; 538 | startRecursiveSearchNode = null; 539 | } 540 | 541 | #endregion 542 | 543 | #endregion 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /Node_Editor/Framework/Node.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6b5fe394e83d344c990206132b84a28 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeCanvas.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | using NodeEditorFramework; 4 | 5 | namespace NodeEditorFramework 6 | { 7 | public class NodeCanvas : ScriptableObject 8 | { // Just contains the nodes and global canvas stuff; an associated NodeEditorState holds the actual state now 9 | public List nodes = new List (); 10 | } 11 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeCanvas.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e0c2324a9ab1224ebe3edad393e3544 3 | timeCreated: 1437395312 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b01efe70ff3f8d84da16ee5d254540a9 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorCallbackReceiver.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | using NodeEditorFramework; 6 | 7 | namespace NodeEditorFramework 8 | { 9 | public abstract class NodeEditorCallbackReceiver : MonoBehaviour 10 | { 11 | // Editor 12 | public virtual void OnEditorStartUp () {} 13 | // Save and Load 14 | public virtual void OnLoadCanvas (NodeCanvas canvas) {} 15 | public virtual void OnLoadEditorState (NodeEditorState editorState) {} 16 | public virtual void OnSaveCanvas (NodeCanvas canvas) {} 17 | public virtual void OnSaveEditorState (NodeEditorState editorState) {} 18 | // Node 19 | public virtual void OnAddNode (Node node) {} 20 | public virtual void OnDeleteNode (Node node) {} 21 | public virtual void OnMoveNode (Node node) {} 22 | // Connection 23 | public virtual void OnAddConnection (NodeInput input) {} 24 | public virtual void OnRemoveConnection (NodeInput input) {} 25 | } 26 | 27 | public static class NodeEditorCallbacks 28 | { 29 | private static int receiverCount; 30 | private static List callbackReceiver; 31 | 32 | public static void SetupReceivers () 33 | { 34 | callbackReceiver = new List (MonoBehaviour.FindObjectsOfType ()); 35 | receiverCount = callbackReceiver.Count; 36 | } 37 | 38 | #region Editor (1) 39 | 40 | public static Action OnEditorStartUp = null; 41 | public static void IssueOnEditorStartUp () 42 | { 43 | if (OnEditorStartUp != null) 44 | OnEditorStartUp.Invoke (); 45 | for (int cnt = 0; cnt < receiverCount; cnt++) 46 | { 47 | if (callbackReceiver [cnt] == null) 48 | callbackReceiver.RemoveAt (cnt--); 49 | else 50 | callbackReceiver [cnt].OnEditorStartUp (); 51 | } 52 | } 53 | 54 | #endregion 55 | 56 | #region Save and Load (4) 57 | 58 | public static Action OnLoadCanvas; 59 | public static void IssueOnLoadCanvas (NodeCanvas canvas) 60 | { 61 | if (OnLoadCanvas != null) 62 | OnLoadCanvas.Invoke (canvas); 63 | for (int cnt = 0; cnt < receiverCount; cnt++) 64 | { 65 | if (callbackReceiver [cnt] == null) 66 | callbackReceiver.RemoveAt (cnt--); 67 | else 68 | callbackReceiver [cnt].OnLoadCanvas (canvas) ; 69 | } 70 | } 71 | 72 | public static Action OnLoadEditorState; 73 | public static void IssueOnLoadEditorState (NodeEditorState editorState) 74 | { 75 | if (OnLoadEditorState != null) 76 | OnLoadEditorState.Invoke (editorState); 77 | for (int cnt = 0; cnt < receiverCount; cnt++) 78 | { 79 | if (callbackReceiver [cnt] == null) 80 | callbackReceiver.RemoveAt (cnt--); 81 | else 82 | callbackReceiver [cnt].OnLoadEditorState (editorState) ; 83 | } 84 | } 85 | 86 | public static Action OnSaveCanvas; 87 | public static void IssueOnSaveCanvas (NodeCanvas canvas) 88 | { 89 | if (OnSaveCanvas != null) 90 | OnSaveCanvas.Invoke (canvas); 91 | for (int cnt = 0; cnt < receiverCount; cnt++) 92 | { 93 | if (callbackReceiver [cnt] == null) 94 | callbackReceiver.RemoveAt (cnt--); 95 | else 96 | callbackReceiver [cnt].OnSaveCanvas (canvas) ; 97 | } 98 | } 99 | 100 | public static Action OnSaveEditorState; 101 | public static void IssueOnSaveEditorState (NodeEditorState editorState) 102 | { 103 | if (OnSaveEditorState != null) 104 | OnSaveEditorState.Invoke (editorState); 105 | for (int cnt = 0; cnt < receiverCount; cnt++) 106 | { 107 | if (callbackReceiver [cnt] == null) 108 | callbackReceiver.RemoveAt (cnt--); 109 | else 110 | callbackReceiver [cnt].OnSaveEditorState (editorState) ; 111 | } 112 | } 113 | 114 | #endregion 115 | 116 | #region Node (3) 117 | 118 | public static Action OnAddNode; 119 | public static void IssueOnAddNode (Node node) 120 | { 121 | if (OnAddNode != null) 122 | OnAddNode.Invoke (node); 123 | for (int cnt = 0; cnt < receiverCount; cnt++) 124 | { 125 | if (callbackReceiver [cnt] == null) 126 | callbackReceiver.RemoveAt (cnt--); 127 | else 128 | callbackReceiver [cnt].OnAddNode (node); 129 | } 130 | } 131 | 132 | public static Action OnDeleteNode; 133 | public static void IssueOnDeleteNode (Node node) 134 | { 135 | if (OnDeleteNode != null) 136 | OnDeleteNode.Invoke (node); 137 | for (int cnt = 0; cnt < receiverCount; cnt++) 138 | { 139 | if (callbackReceiver [cnt] == null) 140 | callbackReceiver.RemoveAt (cnt--); 141 | else 142 | { 143 | callbackReceiver [cnt].OnDeleteNode (node); 144 | node.OnDelete (); 145 | } 146 | } 147 | } 148 | 149 | public static Action OnMoveNode; 150 | public static void IssueOnMoveNode (Node node) 151 | { 152 | if (OnMoveNode != null) 153 | OnMoveNode.Invoke (node); 154 | for (int cnt = 0; cnt < receiverCount; cnt++) 155 | { 156 | if (callbackReceiver [cnt] == null) 157 | callbackReceiver.RemoveAt (cnt--); 158 | else 159 | callbackReceiver [cnt].OnMoveNode (node); 160 | } 161 | } 162 | 163 | #endregion 164 | 165 | #region Connection (2) 166 | 167 | public static Action OnAddConnection; 168 | public static void IssueOnAddConnection (NodeInput input) 169 | { 170 | if (OnAddConnection != null) 171 | OnAddConnection.Invoke (input); 172 | for (int cnt = 0; cnt < receiverCount; cnt++) 173 | { 174 | if (callbackReceiver [cnt] == null) 175 | callbackReceiver.RemoveAt (cnt--); 176 | else 177 | callbackReceiver [cnt].OnAddConnection (input); 178 | } 179 | } 180 | 181 | public static Action OnRemoveConnection; 182 | public static void IssueOnRemoveConnection (NodeInput input) 183 | { 184 | if (OnRemoveConnection != null) 185 | OnRemoveConnection.Invoke (input); 186 | for (int cnt = 0; cnt < receiverCount; cnt++) 187 | { 188 | if (callbackReceiver [cnt] == null) 189 | callbackReceiver.RemoveAt (cnt--); 190 | else 191 | callbackReceiver [cnt].OnRemoveConnection (input); 192 | } 193 | } 194 | 195 | #endregion 196 | } 197 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorCallbackReceiver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f72444d2da0ade14aba9ff3102d17243 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorGUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using NodeEditorFramework; 4 | using NodeEditorFramework.Utilities; 5 | 6 | namespace NodeEditorFramework 7 | { 8 | public static class NodeEditorGUI 9 | { 10 | // static GUI settings, textures and styles 11 | public static int knobSize = 16; 12 | 13 | public static Color NE_LightColor = new Color (0.4f, 0.4f, 0.4f); 14 | public static Color NE_TextColor = new Color (0.7f, 0.7f, 0.7f); 15 | 16 | public static Texture2D Background; 17 | public static Texture2D AALineTex; 18 | public static Texture2D GUIBox; 19 | public static Texture2D GUIButton; 20 | public static Texture2D GUIBoxSelection; 21 | 22 | public static GUISkin nodeSkin; 23 | public static GUISkin defaultSkin; 24 | 25 | public static GUIStyle nodeLabel; 26 | public static GUIStyle nodeLabelBold; 27 | public static GUIStyle nodeLabelSelected; 28 | 29 | public static GUIStyle nodeBox; 30 | public static GUIStyle nodeBoxBold; 31 | 32 | public static bool Init (bool GUIFunction) 33 | { 34 | // Textures 35 | Background = ResourceManager.LoadTexture ("Textures/background.png"); 36 | AALineTex = ResourceManager.LoadTexture ("Textures/AALine.png"); 37 | GUIBox = ResourceManager.LoadTexture ("Textures/NE_Box.png"); 38 | GUIButton = ResourceManager.LoadTexture ("Textures/NE_Button.png"); 39 | GUIBoxSelection = ResourceManager.LoadTexture ("Textures/BoxSelection.png"); 40 | 41 | if (!Background || !AALineTex || !GUIBox || !GUIButton) 42 | return false; 43 | if (!GUIFunction) 44 | return true; 45 | 46 | // Skin & Styles 47 | nodeSkin = Object.Instantiate (GUI.skin); 48 | 49 | // Label 50 | nodeSkin.label.normal.textColor = NE_TextColor; 51 | nodeLabel = nodeSkin.label; 52 | // Box 53 | nodeSkin.box.normal.textColor = NE_TextColor; 54 | nodeSkin.box.normal.background = GUIBox; 55 | nodeBox = nodeSkin.box; 56 | // Button 57 | nodeSkin.button.normal.textColor = NE_TextColor; 58 | nodeSkin.button.normal.background = GUIButton; 59 | // TextArea 60 | nodeSkin.textArea.normal.background = GUIBox; 61 | nodeSkin.textArea.active.background = GUIBox; 62 | // Bold Label 63 | nodeLabelBold = new GUIStyle (nodeLabel); 64 | nodeLabelBold.fontStyle = FontStyle.Bold; 65 | // Selected Label 66 | nodeLabelSelected = new GUIStyle (nodeLabel); 67 | nodeLabelSelected.normal.background = RTEditorGUI.ColorToTex (1, NE_LightColor); 68 | // Bold Box 69 | nodeBoxBold = new GUIStyle (nodeBox); 70 | nodeBoxBold.fontStyle = FontStyle.Bold; 71 | 72 | return true; 73 | } 74 | 75 | public static void StartNodeGUI () 76 | { 77 | defaultSkin = GUI.skin; 78 | if (nodeSkin == null) 79 | Init (true); 80 | GUI.skin = nodeSkin; 81 | } 82 | 83 | public static void EndNodeGUI () 84 | { 85 | GUI.skin = defaultSkin; 86 | } 87 | 88 | #region Connection Drawing 89 | 90 | /// 91 | /// Draws a node connection from start to end, horizontally 92 | /// 93 | public static void DrawConnection (Vector2 startPos, Vector2 endPos, Color col) 94 | { 95 | Vector2 startVector = startPos.x <= endPos.x? Vector2.right : Vector2.left; 96 | DrawConnection (startPos, startVector, endPos, -startVector, col); 97 | } 98 | /// 99 | /// Draws a node connection from start to end with specified vectors 100 | /// 101 | public static void DrawConnection (Vector2 startPos, Vector2 startDir, Vector2 endPos, Vector2 endDir, Color col) 102 | { 103 | #if NODE_EDITOR_LINE_CONNECTION 104 | DrawConnection (startPos, startDir, endPos, endDir, ConnectionDrawMethod.StraightLine, col); 105 | #else 106 | DrawConnection (startPos, startDir, endPos, endDir, ConnectionDrawMethod.Bezier, col); 107 | #endif 108 | } 109 | /// 110 | /// Draws a node connection from start to end with specified vectors 111 | /// 112 | public static void DrawConnection (Vector2 startPos, Vector2 startDir, Vector2 endPos, Vector2 endDir, ConnectionDrawMethod drawMethod, Color col) 113 | { 114 | if (drawMethod == ConnectionDrawMethod.Bezier) 115 | { 116 | float dirFactor = 80;//Mathf.Pow ((startPos-endPos).magnitude, 0.3f) * 20; 117 | //Debug.Log ("DirFactor is " + dirFactor + "with a bezier lenght of " + (startPos-endPos).magnitude); 118 | RTEditorGUI.DrawBezier (startPos, endPos, startPos + startDir * dirFactor, endPos + endDir * dirFactor, col * Color.gray, null, 3); 119 | } 120 | else if (drawMethod == ConnectionDrawMethod.StraightLine) 121 | RTEditorGUI.DrawLine (startPos, endPos, col * Color.gray, null, 3); 122 | } 123 | 124 | /// 125 | /// Gets the second connection vector that matches best, accounting for positions 126 | /// 127 | internal static Vector2 GetSecondConnectionVector (Vector2 startPos, Vector2 endPos, Vector2 firstVector) 128 | { 129 | if (firstVector.x != 0 && firstVector.y == 0) 130 | return startPos.x <= endPos.x? -firstVector : firstVector; 131 | else if (firstVector.y != 0 && firstVector.x == 0) 132 | return startPos.y <= endPos.y? -firstVector : firstVector; 133 | else 134 | return -firstVector; 135 | } 136 | 137 | #endregion 138 | } 139 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorGUI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d45bec52de54d834e818ca0ccb994c1f 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorSaveManager.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | 5 | using NodeEditorFramework; 6 | using NodeEditorFramework.Utilities; 7 | 8 | namespace NodeEditorFramework 9 | { 10 | /// 11 | /// Manager handling all save and load operations on NodeCanvases and NodeEditorStates of the Node Editor 12 | /// 13 | public static class NodeEditorSaveManager 14 | { 15 | #region Save 16 | 17 | /// 18 | /// Saves a working copy of the given NodeCanvas as a new asset along with working copies of the given NodeEditorStates 19 | /// 20 | public static void SaveNodeCanvas (string path, NodeCanvas nodeCanvas, params NodeEditorState[] editorStates) 21 | { 22 | SaveNodeCanvas (path, true, nodeCanvas, editorStates); 23 | } 24 | 25 | /// 26 | /// Saves the the given NodeCanvas or, if specified, a working copy of it, as a new asset along with working copies, if specified, of the given NodeEditorStates 27 | /// 28 | public static void SaveNodeCanvas (string path, bool createWorkingCopy, NodeCanvas nodeCanvas, params NodeEditorState[] editorStates) 29 | { 30 | if (string.IsNullOrEmpty (path)) 31 | throw new UnityException ("Cannot save NodeCanvas: No spath specified to save the NodeCanvas " + (nodeCanvas != null? nodeCanvas.name : "") + " to!"); 32 | if (nodeCanvas == null) 33 | throw new UnityException ("Cannot save NodeCanvas: The specified NodeCanvas that should be saved to path " + path + " is null!"); 34 | 35 | foreach (NodeEditorState state in editorStates) 36 | { 37 | if (state == null) 38 | Debug.LogError ("A NodeEditorState that should be saved to path " + path + " is null!"); 39 | } 40 | 41 | path = path.Replace (Application.dataPath, "Assets"); 42 | 43 | #if UNITY_EDITOR 44 | 45 | if (createWorkingCopy) 46 | { // Copy canvas and editorStates 47 | CreateWorkingCopy (ref nodeCanvas, editorStates, true); 48 | } 49 | 50 | // Write canvas and editorStates 51 | UnityEditor.AssetDatabase.CreateAsset (nodeCanvas, path); 52 | foreach (NodeEditorState state in editorStates) 53 | { 54 | if (state != null) 55 | AddSubAsset (state, nodeCanvas); 56 | } 57 | 58 | // Write nodes + contents 59 | foreach (Node node in nodeCanvas.nodes) 60 | { // Every node and each ScriptableObject in it 61 | AddSubAsset (node, nodeCanvas); 62 | foreach (NodeKnob knob in node.nodeKnobs) 63 | { 64 | AddSubAsset (knob, node); 65 | // Add additional scriptableObjects 66 | ScriptableObject[] additionalKnobSOs = knob.GetScriptableObjects (); 67 | foreach (ScriptableObject so in additionalKnobSOs) 68 | AddSubAsset (so, knob); 69 | } 70 | // Add additional scriptableObjects 71 | ScriptableObject[] additionalNodeSOs = node.GetScriptableObjects (); 72 | foreach (ScriptableObject so in additionalNodeSOs) 73 | AddSubAsset (so, node); 74 | } 75 | 76 | UnityEditor.AssetDatabase.SaveAssets (); 77 | UnityEditor.AssetDatabase.Refresh (); 78 | #else 79 | // TODO: Node Editor: Need to implement ingame-saving (Resources, AsssetBundles, ... won't work) 80 | #endif 81 | 82 | NodeEditorCallbacks.IssueOnSaveCanvas (nodeCanvas); 83 | } 84 | 85 | #if UNITY_EDITOR 86 | 87 | /// 88 | /// Adds a hidden sub asset to the main asset 89 | /// 90 | public static void AddSubAsset (ScriptableObject subAsset, ScriptableObject mainAsset) 91 | { 92 | UnityEditor.AssetDatabase.AddObjectToAsset (subAsset, mainAsset); 93 | subAsset.hideFlags = HideFlags.HideInHierarchy; 94 | } 95 | 96 | /// 97 | /// Adds a hidden sub asset to the main asset 98 | /// 99 | public static void AddSubAsset (ScriptableObject subAsset, string path) 100 | { 101 | UnityEditor.AssetDatabase.AddObjectToAsset (subAsset, path); 102 | subAsset.hideFlags = HideFlags.HideInHierarchy; 103 | } 104 | 105 | #endif 106 | 107 | #endregion 108 | 109 | #region Load 110 | 111 | /// 112 | /// Loads the NodeCanvas from the asset file at path and returns a working copy of it 113 | /// 114 | public static NodeCanvas LoadNodeCanvas (string path) 115 | { 116 | return LoadNodeCanvas (path, true); 117 | } 118 | 119 | /// 120 | /// Loads the NodeCanvas from the asset file at path and optionally creates a working copy of it before returning 121 | /// 122 | public static NodeCanvas LoadNodeCanvas (string path, bool createWorkingCopy) 123 | { 124 | if (string.IsNullOrEmpty (path)) 125 | throw new UnityException ("Cannot Load NodeCanvas: No path specified to load the NodeCanvas from!"); 126 | 127 | // Fetch all objects in the save file 128 | ScriptableObject[] objects = ResourceManager.LoadResources (path); 129 | if (objects == null || objects.Length == 0) 130 | throw new UnityException ("Cannot Load NodeCanvas: The specified path '" + path + "' does not point to a save file!"); 131 | 132 | // Filter out the NodeCanvas out of these objects 133 | NodeCanvas nodeCanvas = objects.Single ((ScriptableObject obj) => (obj as NodeCanvas) != null) as NodeCanvas; 134 | if (nodeCanvas == null) 135 | throw new UnityException ("Cannot Load NodeCanvas: The file at the specified path '" + path + "' is no valid save file as it does not contain a NodeCanvas!"); 136 | 137 | #if UNITY_EDITOR // Create a working copy of it 138 | if (createWorkingCopy) 139 | { 140 | CreateWorkingCopy (ref nodeCanvas, false); 141 | if (nodeCanvas == null) 142 | throw new UnityException ("Cannot Load NodeCanvas: Failed to create a working copy for the NodeCanvas at path '" + path + "' during the loading process!"); 143 | } 144 | UnityEditor.AssetDatabase.Refresh (); 145 | #endif 146 | NodeEditorCallbacks.IssueOnLoadCanvas (nodeCanvas); 147 | return nodeCanvas; 148 | } 149 | 150 | /// 151 | /// Loads the editorStates found in the nodeCanvas asset file at path and returns a working copy of it 152 | /// 153 | public static List LoadEditorStates (string path) 154 | { 155 | return LoadEditorStates (path, true); 156 | } 157 | /// 158 | /// Loads the editorStates found in the nodeCanvas asset file at path and optionally creates a working copy of it before returning 159 | /// 160 | public static List LoadEditorStates (string path, bool createWorkingCopy) 161 | { 162 | if (string.IsNullOrEmpty (path)) 163 | throw new UnityException ("Cannot load NodeEditorStates: No path specified to load the EditorStates from!"); 164 | 165 | // Fetch all objects in the save file 166 | ScriptableObject[] objects = ResourceManager.LoadResources (path); 167 | if (objects == null || objects.Length == 0) 168 | throw new UnityException ("Cannot load NodeEditorStates: The specified path '" + path + "' does not point to a save file!"); 169 | 170 | // Obtain the editorStates in that asset file and create a working copy of them 171 | List editorStates = objects.OfType ().ToList (); 172 | #if UNITY_EDITOR 173 | if (createWorkingCopy) 174 | { 175 | for (int cnt = 0; cnt < editorStates.Count; cnt++) 176 | { // create working copies for editor states 177 | NodeEditorState state = editorStates[cnt]; 178 | CreateWorkingCopy (ref state); 179 | editorStates[cnt] = state; 180 | if (state == null) 181 | throw new UnityException ("Failed to create a working copy for an NodeEditorState at path '" + path + "' during the loading process!"); 182 | } 183 | } 184 | #endif 185 | 186 | // Perform Event and Database refresh 187 | foreach (NodeEditorState state in editorStates) 188 | NodeEditorCallbacks.IssueOnLoadEditorState (state); 189 | #if UNITY_EDITOR 190 | UnityEditor.AssetDatabase.Refresh (); 191 | #endif 192 | return editorStates; 193 | } 194 | 195 | #endregion 196 | 197 | #region Working Copy Creation 198 | 199 | // 200 | /// Gets a working copy of the editor state. This will break the link to the asset and thus all changes made to the working copy have to be explicitly saved. 201 | /// NOTE: If possible, create the working copy with the associated editor state. Else, you have to manually assign the working copy canvas! 202 | /// 203 | public static void CreateWorkingCopy (ref NodeEditorState editorState) 204 | { 205 | if (editorState == null) 206 | return; 207 | 208 | editorState = Clone (editorState); 209 | editorState.focusedNode = null; 210 | editorState.selectedNode = null; 211 | editorState.makeTransition = null; 212 | editorState.connectOutput = null; 213 | } 214 | 215 | /// 216 | /// Creates a working copy of the canvas. This will break the link to the canvas asset and thus all changes made to the working copy have to be explicitly saved. 217 | /// Check compressed if the copy is not intended for useage but for storage, this will leave the Inputs and Outputs list of Node empty 218 | /// 219 | public static void CreateWorkingCopy (ref NodeCanvas nodeCanvas, bool compressed) 220 | { 221 | CreateWorkingCopy (ref nodeCanvas, null, compressed); 222 | } 223 | 224 | /// 225 | /// CreateWorkingCopy a working copy of the canvas and each editorState. This will break the link to the canvas asset and thus all changes made to the working copy have to be explicitly saved. 226 | /// Check compressed if the copy is not intended for useage but for storage, this will leave the Inputs and Outputs list of Node empty 227 | /// 228 | public static void CreateWorkingCopy (ref NodeCanvas nodeCanvas, NodeEditorState[] editorStates, bool compressed) 229 | { 230 | nodeCanvas = Clone (nodeCanvas); 231 | 232 | // Take each SO, make a clone of it and store both versions in the respective list 233 | // This will only iterate over the 'source instances' 234 | List allSOs = new List (); 235 | List clonedSOs = new List (); 236 | foreach (Node node in nodeCanvas.nodes) 237 | { 238 | node.CheckNodeKnobMigration (); 239 | Node clonedNode = AddClonedSO (allSOs, clonedSOs, node); 240 | foreach (NodeKnob knob in clonedNode.nodeKnobs) 241 | { // Clone NodeKnobs 242 | NodeKnob clonedKnob = AddClonedSO (allSOs, clonedSOs, knob); 243 | // Clone additional scriptableObjects 244 | ScriptableObject[] additionalKnobSOs = clonedKnob.GetScriptableObjects (); 245 | foreach (ScriptableObject so in additionalKnobSOs) 246 | AddClonedSO (allSOs, clonedSOs, so); 247 | } 248 | // Clone additional scriptableObjects 249 | ScriptableObject[] additionalNodeSOs = clonedNode.GetScriptableObjects (); 250 | foreach (ScriptableObject so in additionalNodeSOs) 251 | AddClonedSO (allSOs, clonedSOs, so); 252 | } 253 | 254 | // Replace every reference to any of the initial SOs of the first list with the respective clones of the second list 255 | 256 | for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++) 257 | { // Clone Nodes, structural content and additional scriptableObjects 258 | Node clonedNode = nodeCanvas.nodes[nodeCnt] = ReplaceSO (allSOs, clonedSOs, nodeCanvas.nodes[nodeCnt]); 259 | // We're going to restore these from NodeKnobs if desired (!compressed) 260 | clonedNode.Inputs = new List (); 261 | clonedNode.Outputs = new List (); 262 | for (int knobCnt = 0; knobCnt < clonedNode.nodeKnobs.Count; knobCnt++) 263 | { // Clone generic NodeKnobs 264 | NodeKnob clonedKnob = clonedNode.nodeKnobs[knobCnt] = ReplaceSO (allSOs, clonedSOs, clonedNode.nodeKnobs[knobCnt]); 265 | clonedKnob.body = clonedNode; 266 | if (!compressed) 267 | { // Add NodeInputs and NodeOutputs to the apropriate lists in Node if desired (!compressed) 268 | if (clonedKnob is NodeInput) 269 | clonedNode.Inputs.Add (clonedKnob as NodeInput); 270 | else if (clonedKnob is NodeOutput) 271 | clonedNode.Outputs.Add (clonedKnob as NodeOutput); 272 | } 273 | // Replace additional scriptableObjects in the NodeKnob 274 | clonedKnob.CopyScriptableObjects ((ScriptableObject so) => ReplaceSO (allSOs, clonedSOs, so)); 275 | } 276 | // Replace additional scriptableObjects in the Node 277 | clonedNode.CopyScriptableObjects ((ScriptableObject so) => ReplaceSO (allSOs, clonedSOs, so)); 278 | } 279 | 280 | // Also create working copies for specified editorStates, if any 281 | // Needs to be in the same function as the EditorState references nodes from the NodeCanvas 282 | if (editorStates != null) 283 | { 284 | for (int stateCnt = 0; stateCnt < editorStates.Length; stateCnt++) 285 | { 286 | if (editorStates[stateCnt] == null) 287 | continue; 288 | 289 | NodeEditorState clonedState = editorStates[stateCnt] = Clone (editorStates[stateCnt]); 290 | clonedState.canvas = nodeCanvas; 291 | clonedState.focusedNode = null; 292 | clonedState.selectedNode = clonedState.selectedNode != null? ReplaceSO (allSOs, clonedSOs, clonedState.selectedNode) : null; 293 | clonedState.makeTransition = null; 294 | clonedState.connectOutput = null; 295 | } 296 | } 297 | } 298 | 299 | /// 300 | /// Clones the specified SO, preserving it's name 301 | /// 302 | private static T Clone (T SO) where T : ScriptableObject 303 | { 304 | string soName = SO.name; 305 | SO = Object.Instantiate (SO); 306 | SO.name = soName; 307 | return SO; 308 | } 309 | 310 | /// 311 | /// Clones SO and writes both versions into the respective list 312 | /// 313 | private static T AddClonedSO (List scriptableObjects, List clonedScriptableObjects, T initialSO) where T : ScriptableObject 314 | { 315 | if (initialSO == null) 316 | return null; 317 | scriptableObjects.Add (initialSO); 318 | T clonedSO = Clone (initialSO); 319 | clonedScriptableObjects.Add (clonedSO); 320 | return clonedSO; 321 | } 322 | 323 | /// 324 | /// First two parameters are SOs and their respective clones. Will return the saved clone of initialSO 325 | /// 326 | private static T ReplaceSO (List scriptableObjects, List clonedScriptableObjects, T initialSO) where T : ScriptableObject 327 | { 328 | if (initialSO == null) 329 | return null; 330 | int soInd = scriptableObjects.IndexOf (initialSO); 331 | if (soInd == -1) 332 | Debug.LogError ("GetWorkingCopy: ScriptableObject " + initialSO.name + " was not copied before! It will be null!"); 333 | return soInd == -1? null : (T)clonedScriptableObjects[soInd]; 334 | } 335 | 336 | #endregion 337 | } 338 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorSaveManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 536538853a8430243a2f38355c7d4bbd 3 | timeCreated: 1453840956 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorState.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections.Generic; 4 | using NodeEditorFramework; 5 | 6 | namespace NodeEditorFramework 7 | { 8 | public class NodeEditorState : ScriptableObject 9 | { // holds the state of a NodeCanvas inside a NodeEditor 10 | public NodeCanvas canvas; 11 | public NodeEditorState parentEditor; 12 | 13 | // Canvas options 14 | public bool drawing = true; // whether to draw the canvas 15 | 16 | // Selection State 17 | public Node selectedNode; // selected Node 18 | [NonSerialized] 19 | public Node focusedNode; // Node under mouse 20 | 21 | // Current Action 22 | [NonSerialized] 23 | public bool dragNode = false; 24 | [NonSerialized] 25 | public Node makeTransition; // make transition from node 26 | [NonSerialized] 27 | public NodeOutput connectOutput; // connection this output 28 | 29 | // Navigation State 30 | public Vector2 panOffset = new Vector2 (); // pan offset 31 | public float zoom = 1; // zoom; Ranges in 0.2er-steps from 0.6-2.0; applied 1/zoom; 32 | 33 | // Temporary Navigation State 34 | [NonSerialized] 35 | public bool navigate = false; // navigation ('N') 36 | [NonSerialized] 37 | public bool panWindow = false; // window panning 38 | 39 | // Temporary State 40 | [NonSerialized] 41 | public Rect canvasRect; // canvas Rect 42 | public Vector2 zoomPos { get { return canvasRect.size/2; } } // zoom center in canvas space 43 | [NonSerialized] 44 | public Vector2 zoomPanAdjust; // calculated value to offset elements with when zooming 45 | [NonSerialized] 46 | public List ignoreInput = new List (); // Rects inside the canvas to ignore input in (nested canvases, fE) 47 | } 48 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeEditorState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6ab6487237ff124ea4c2aa5de9ce3fb 3 | timeCreated: 1437395312 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeInput.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | namespace NodeEditorFramework 5 | { 6 | /// 7 | /// NodeInput accepts one connection to a NodeOutput by default 8 | /// 9 | public class NodeInput : NodeKnob 10 | { 11 | // NodeKnob Members 12 | protected override NodeSide defaultSide { get { return NodeSide.Left; } } 13 | 14 | // NodeInput Members 15 | public NodeOutput connection; 16 | public string type; 17 | [System.NonSerialized] 18 | internal TypeData typeData; 19 | // Multiple connections 20 | // public List connections; 21 | 22 | #region Contructors 23 | 24 | /// 25 | /// Creates a new NodeInput in NodeBody of specified type 26 | /// 27 | public static NodeInput Create (Node nodeBody, string inputName, string inputType) 28 | { 29 | return Create (nodeBody, inputName, inputType, NodeSide.Left, 20); 30 | } 31 | 32 | /// 33 | /// Creates a new NodeInput in NodeBody of specified type at the specified NodeSide 34 | /// 35 | public static NodeInput Create (Node nodeBody, string inputName, string inputType, NodeSide nodeSide) 36 | { 37 | return Create (nodeBody, inputName, inputType, nodeSide, 20); 38 | } 39 | 40 | /// 41 | /// Creates a new NodeInput in NodeBody of specified type at the specified NodeSide and position 42 | /// 43 | public static NodeInput Create (Node nodeBody, string inputName, string inputType, NodeSide nodeSide, float sidePosition) 44 | { 45 | NodeInput input = CreateInstance (); 46 | input.type = inputType; 47 | input.InitBase (nodeBody, nodeSide, sidePosition, inputName); 48 | nodeBody.Inputs.Add (input); 49 | return input; 50 | } 51 | 52 | #endregion 53 | 54 | #region Additional Serialization 55 | 56 | protected internal override void CopyScriptableObjects (System.Func replaceSerializableObject) 57 | { 58 | connection = replaceSerializableObject.Invoke (connection) as NodeOutput; 59 | // Multiple connections 60 | // for (int conCnt = 0; conCnt < connections.Count; conCnt++) 61 | // connections[conCnt] = replaceSerializableObject.Invoke (connections[conCnt]) as NodeOutput; 62 | } 63 | 64 | #endregion 65 | 66 | #region KnobType 67 | 68 | protected override void ReloadTexture () 69 | { 70 | CheckType (); 71 | knobTexture = typeData.InKnobTex; 72 | } 73 | 74 | private void CheckType () 75 | { 76 | if (typeData == null || !typeData.isValid ()) 77 | typeData = ConnectionTypes.GetTypeData (type, true); 78 | } 79 | 80 | #endregion 81 | 82 | #region Value 83 | 84 | /// 85 | /// Gets the value of the connection or the default value 86 | /// 87 | public T GetValue () 88 | { 89 | return connection != null? connection.GetValue () : NodeOutput.GetDefault (); 90 | } 91 | 92 | /// 93 | /// Sets the value of the connection if the type matches 94 | /// 95 | public void SetValue (T value) 96 | { 97 | if (connection != null) 98 | connection.SetValue (value); 99 | } 100 | 101 | #endregion 102 | 103 | #region Connecting Utility 104 | 105 | /// 106 | /// Check if the passed NodeOutput can be connected to this NodeInput 107 | /// 108 | public bool CanApplyConnection (NodeOutput output) 109 | { 110 | if (output == null || body == output.body || connection == output || typeData.Type != output.typeData.Type) 111 | return false; 112 | 113 | if (output.body.isChildOf (body)) 114 | { // Recursive 115 | if (!output.body.allowsLoopRecursion (body)) 116 | { 117 | // TODO: Generic Notification 118 | Debug.LogWarning ("Cannot apply connection: Recursion detected!"); 119 | return false; 120 | } 121 | } 122 | return true; 123 | } 124 | 125 | /// 126 | /// Applies a connection between the passed NodeOutput and this NodeInput. 'CanApplyConnection' has to be checked before to avoid interferences! 127 | /// 128 | public void ApplyConnection (NodeOutput output) 129 | { 130 | if (output == null) 131 | return; 132 | 133 | if (connection != null) 134 | { 135 | NodeEditorCallbacks.IssueOnRemoveConnection (this); 136 | connection.connections.Remove (this); 137 | } 138 | connection = output; 139 | output.connections.Add (this); 140 | 141 | NodeEditor.RecalculateFrom (body); 142 | output.body.OnAddOutputConnection (output); 143 | body.OnAddInputConnection (this); 144 | NodeEditorCallbacks.IssueOnAddConnection (this); 145 | } 146 | 147 | /// 148 | /// Removes the connection from this NodeInput 149 | /// 150 | public void RemoveConnection () 151 | { 152 | if (connection == null) 153 | return; 154 | 155 | NodeEditorCallbacks.IssueOnRemoveConnection (this); 156 | connection.connections.Remove (this); 157 | connection = null; 158 | 159 | NodeEditor.RecalculateFrom (body); 160 | } 161 | 162 | 163 | #endregion 164 | } 165 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeInput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4caff27366054dd44a30ddb5be369acc 3 | timeCreated: 1437395312 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeKnob.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | 5 | using NodeEditorFramework.Utilities; 6 | 7 | namespace NodeEditorFramework 8 | { 9 | /// 10 | /// Defines a side on a Node 11 | /// 12 | public enum NodeSide { Left = 4, Top = 3, Right = 2, Bottom = 1 } 13 | 14 | /// 15 | /// Abstract knob on the side of an node that handles positioning drawing with a texture and even labeling and positioning calls 16 | /// 17 | [System.Serializable] 18 | public class NodeKnob : ScriptableObject 19 | { 20 | // Main 21 | public Node body; 22 | 23 | protected virtual GUIStyle defaultLabelStyle { get { return GUI.skin.label; } } 24 | [System.NonSerialized] 25 | protected internal Texture2D knobTexture; 26 | 27 | // Position 28 | protected virtual NodeSide defaultSide { get { return NodeSide.Right; } } 29 | public NodeSide side; 30 | public float sidePosition = 0; // Position on the side, top->bottom, left->right 31 | public float sideOffset = 0; // Offset from the side 32 | 33 | /// 34 | /// Inits the base node and subscribes it in the node body for drawing and requests to load the texture through 'ReloadTexture' 35 | /// 36 | protected void InitBase (Node nodeBody, NodeSide nodeSide, float nodeSidePosition, string knobName) 37 | { 38 | body = nodeBody; 39 | side = nodeSide; 40 | sidePosition = nodeSidePosition; 41 | name = knobName; 42 | nodeBody.nodeKnobs.Add (this); 43 | ReloadKnobTexture (); 44 | } 45 | 46 | #region Knob Texture Loading 47 | 48 | /// 49 | /// Checks the texture and requests to load it again if necessary 50 | /// 51 | internal void Check () 52 | { 53 | if (side == 0) 54 | side = defaultSide; 55 | if (knobTexture == null) 56 | ReloadKnobTexture (); 57 | } 58 | 59 | /// 60 | /// Requests to teload the knobTexture and adapts it to the position and orientation 61 | /// 62 | protected void ReloadKnobTexture () 63 | { 64 | ReloadTexture (); 65 | if (knobTexture == null) 66 | throw new UnityException ("Knob texture could not be loaded!"); 67 | if (side != defaultSide) 68 | { // Rotate Knob texture according to the side it's used on 69 | ResourceManager.SetDefaultResourcePath (NodeEditor.editorPath + "Resources/"); 70 | int rotationSteps = getRotationStepsAntiCW (defaultSide, side); 71 | 72 | // Get standard texture in memory 73 | ResourceManager.MemoryTexture memoryTex = ResourceManager.FindInMemory (knobTexture); 74 | if (memoryTex != null) 75 | { // Texture does exist in memory, so built a mod including rotation 76 | string[] mods = new string[memoryTex.modifications.Length+1]; 77 | memoryTex.modifications.CopyTo (mods, 0); 78 | mods[mods.Length-1] = "Rotation:" + rotationSteps; 79 | // Try to find the rotated version in memory 80 | Texture2D knobTextureInMemory = ResourceManager.GetTexture (memoryTex.path, mods); 81 | if (knobTextureInMemory != null) 82 | { // Rotated version does exist 83 | knobTexture = knobTextureInMemory; 84 | } 85 | else 86 | { // Rotated version does not exist, so create and reord it 87 | knobTexture = RTEditorGUI.RotateTextureCCW (knobTexture, rotationSteps); 88 | ResourceManager.AddTextureToMemory (memoryTex.path, knobTexture, mods.ToArray ()); 89 | } 90 | } 91 | else 92 | { // If it does not exist in memory, we have no path for it so we just silently rotate and use it 93 | // Note that this way we have to rotate it over and over again later on 94 | knobTexture = RTEditorGUI.RotateTextureCCW (knobTexture, rotationSteps); 95 | } 96 | } 97 | } 98 | 99 | /// 100 | /// Defines an reload. This should assign knobTexture and return the path to knobTexture. 101 | /// 102 | protected virtual void ReloadTexture () 103 | { 104 | knobTexture = RTEditorGUI.ColorToTex (1, Color.red); 105 | } 106 | 107 | #endregion 108 | 109 | #region Additional Serialization 110 | 111 | /// 112 | /// Returns all additional ScriptableObjects this NodeKnob holds. 113 | /// That means only the actual SOURCES, simple REFERENCES will not be returned 114 | /// This means all SciptableObjects returned here do not have it's source elsewhere 115 | /// 116 | protected internal virtual ScriptableObject[] GetScriptableObjects () { return new ScriptableObject[0]; } 117 | 118 | /// 119 | /// Replaces all REFERENCES aswell as SOURCES of any ScriptableObjects this NodeKnob holds with the cloned versions in the serialization process. 120 | /// 121 | protected internal virtual void CopyScriptableObjects (System.Func replaceSerializableObject) {} 122 | 123 | #endregion 124 | 125 | #region GUI drawing and Positioning 126 | 127 | /// 128 | /// Draw this knob at it's position with it's knobTexture 129 | /// 130 | public virtual void DrawKnob () 131 | { 132 | Rect knobRect = GetGUIKnob (); 133 | GUI.DrawTexture (knobRect, knobTexture); 134 | } 135 | 136 | /// 137 | /// Draws a label with the knob's name. Places the knob next to it at it's nodeSide 138 | /// 139 | public void DisplayLayout () 140 | { 141 | DisplayLayout (new GUIContent (name), defaultLabelStyle); 142 | } 143 | 144 | /// 145 | /// Draws a label with the knob's name and the given style. Places the knob next to it at it's nodeSide 146 | /// 147 | public void DisplayLayout (GUIStyle style) 148 | { 149 | DisplayLayout (new GUIContent (name), style); 150 | } 151 | 152 | /// 153 | /// Draws a label with the given GUIContent. Places the knob next to it at it's nodeSide 154 | /// 155 | public void DisplayLayout (GUIContent content) 156 | { 157 | DisplayLayout (content, defaultLabelStyle); 158 | } 159 | 160 | /// 161 | /// Draws a label with the given GUIContent and the given style. Places the knob next to it at it's nodeSide 162 | /// 163 | public void DisplayLayout (GUIContent content, GUIStyle style) 164 | { 165 | GUILayout.Label (content, style); 166 | if (Event.current.type == EventType.Repaint) 167 | SetPosition (); 168 | } 169 | 170 | /// 171 | /// Sets the knob's position at the specified nodeSide, from Top->Bottom and Left->Right 172 | /// 173 | public void SetPosition (float position, NodeSide nodeSide) 174 | { 175 | if (side != nodeSide) 176 | { 177 | side = nodeSide; 178 | ReloadKnobTexture (); 179 | } 180 | SetPosition (position); 181 | } 182 | 183 | /// 184 | /// Sets the knob's position at it's nodeSide, from Top->Bottom and Left->Right 185 | /// 186 | public void SetPosition (float position) 187 | { 188 | sidePosition = position; 189 | } 190 | 191 | /// 192 | /// Sets the knob's position at the specified nodeSide next to the last GUILayout control 193 | /// 194 | public void SetPosition () 195 | { 196 | Vector2 pos = GUILayoutUtility.GetLastRect ().center + body.contentOffset; 197 | sidePosition = side == NodeSide.Bottom || side == NodeSide.Top? pos.x : pos.y; 198 | } 199 | 200 | #endregion 201 | 202 | #region Position requests 203 | 204 | /// 205 | /// Gets the Knob rect in GUI space, NOT ZOOMED 206 | /// 207 | internal Rect GetGUIKnob () 208 | { 209 | Check (); 210 | Vector2 knobSize = new Vector2 ((knobTexture.width/knobTexture.height) * NodeEditorGUI.knobSize, 211 | (knobTexture.height/knobTexture.width) * NodeEditorGUI.knobSize); 212 | Vector2 knobCenter = new Vector2 (body.rect.x + (side == NodeSide.Bottom || side == NodeSide.Top? 213 | /* Top | Bottom */ sidePosition : 214 | (side == NodeSide.Left? 215 | /* Left */ -sideOffset-knobSize.x/2 : 216 | /* Right */ body.rect.width+sideOffset+knobSize.x/2 217 | )), 218 | body.rect.y + (side == NodeSide.Left || side == NodeSide.Right? 219 | /* Left | Right */ sidePosition : 220 | (side == NodeSide.Top? 221 | /* Top */ -sideOffset-knobSize.y/2 : 222 | /* Bottom */ body.rect.height+sideOffset+knobSize.y/2 223 | ))); 224 | return new Rect (knobCenter.x - knobSize.x/2 + NodeEditor.curEditorState.zoomPanAdjust.x, 225 | knobCenter.y - knobSize.y/2 + NodeEditor.curEditorState.zoomPanAdjust.y, 226 | knobSize.x, knobSize.y); 227 | } 228 | 229 | /// 230 | /// Get the Knob rect in screen space, ZOOMED, for Input detection purposes 231 | /// 232 | internal Rect GetScreenKnob () 233 | { 234 | Rect rect = GetGUIKnob (); 235 | rect.position -= NodeEditor.curEditorState.zoomPanAdjust; // Remove zoomPanAdjust added in GetGUIKnob 236 | return NodeEditor.CanvasGUIToScreenRect (rect); 237 | } 238 | 239 | /// 240 | /// Gets the direction of the knob (vertical inverted) for connection drawing purposes 241 | /// 242 | internal Vector2 GetDirection () 243 | { 244 | return side == NodeSide.Right? Vector2.right : 245 | (side == NodeSide.Bottom? Vector2.up : 246 | (side == NodeSide.Top? Vector2.down : 247 | /* Left */ Vector2.left)); 248 | } 249 | 250 | /// 251 | /// Gets the rotation steps anti-clockwise from NodeSide A to B 252 | /// 253 | private static int getRotationStepsAntiCW (NodeSide sideA, NodeSide sideB) 254 | { 255 | return sideB - sideA + (sideA>sideB? 4 : 0); 256 | } 257 | 258 | #endregion 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeKnob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8735025346ca7142b24d4e6d07ba742 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeOutput.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | namespace NodeEditorFramework 5 | { 6 | /// 7 | /// Node output accepts multiple connections to NodeInputs by default 8 | /// 9 | public class NodeOutput : NodeKnob 10 | { 11 | // NodeKnob Members 12 | protected override NodeSide defaultSide { get { return NodeSide.Right; } } 13 | private static GUIStyle _defaultStyle; 14 | protected override GUIStyle defaultLabelStyle { get { if (_defaultStyle == null) { _defaultStyle = new GUIStyle (GUI.skin.label); _defaultStyle.alignment = TextAnchor.MiddleRight; } return _defaultStyle; } } 15 | 16 | // NodeInput Members 17 | public List connections = new List (); 18 | public string type; 19 | [System.NonSerialized] 20 | internal TypeData typeData; 21 | [System.NonSerialized] 22 | private object value = null; 23 | 24 | #region Contructors 25 | 26 | /// 27 | /// Creates a new NodeOutput in NodeBody of specified type 28 | /// 29 | public static NodeOutput Create (Node nodeBody, string outputName, string outputType) 30 | { 31 | return Create (nodeBody, outputName, outputType, NodeSide.Right, 20); 32 | } 33 | 34 | /// 35 | /// Creates a new NodeOutput in NodeBody of specified type 36 | /// 37 | public static NodeOutput Create (Node nodeBody, string outputName, string outputType, NodeSide nodeSide) 38 | { 39 | return Create (nodeBody, outputName, outputType, nodeSide, 20); 40 | } 41 | 42 | /// 43 | /// Creates a new NodeOutput in NodeBody of specified type at the specified Node Side 44 | /// 45 | public static NodeOutput Create (Node nodeBody, string outputName, string outputType, NodeSide nodeSide, float sidePosition) 46 | { 47 | NodeOutput output = CreateInstance (); 48 | output.type = outputType; 49 | output.InitBase (nodeBody, nodeSide, sidePosition, outputName); 50 | nodeBody.Outputs.Add (output); 51 | return output; 52 | } 53 | 54 | #endregion 55 | 56 | #region Additional Serialization 57 | 58 | protected internal override void CopyScriptableObjects (System.Func replaceSerializableObject) 59 | { 60 | for (int conCnt = 0; conCnt < connections.Count; conCnt++) 61 | connections[conCnt] = replaceSerializableObject.Invoke (connections[conCnt]) as NodeInput; 62 | } 63 | 64 | #endregion 65 | 66 | #region KnobType 67 | 68 | protected override void ReloadTexture () 69 | { 70 | CheckType (); 71 | knobTexture = typeData.OutKnobTex; 72 | } 73 | 74 | private void CheckType () 75 | { 76 | if (typeData == null || !typeData.isValid ()) 77 | typeData = ConnectionTypes.GetTypeData (type, true); 78 | } 79 | 80 | #endregion 81 | 82 | #region Value 83 | 84 | public bool IsValueNull { get { return value == null; } } 85 | 86 | /// 87 | /// Gets the output value if the type matches 88 | /// 89 | /// Value, if null default(T) (-> For reference types, null. For value types, default value) 90 | public T GetValue () 91 | { 92 | CheckType (); 93 | if (typeData.Type == typeof(T)) 94 | return (T)(value?? (value = GetDefault ())); 95 | Debug.LogError ("Trying to GetValue<" + typeof(T).FullName + "> for Output Type: " + typeData.Type.FullName); 96 | return GetDefault (); 97 | } 98 | 99 | /// 100 | /// Sets the output value if the type matches 101 | /// 102 | public void SetValue (T Value) 103 | { 104 | CheckType (); 105 | if (typeData.Type == typeof(T)) 106 | value = Value; 107 | else 108 | Debug.LogError ("Trying to SetValue<" + typeof(T).FullName + "> for Output Type: " + typeData.Type.FullName); 109 | } 110 | 111 | /// 112 | /// Resets the output value to null. 113 | /// 114 | public void ResetValue () 115 | { 116 | value = null; 117 | } 118 | 119 | /// 120 | /// For value types, the default value; for reference types, the default constructor if existant, else null 121 | /// 122 | public static T GetDefault () 123 | { 124 | if (typeof(T).GetConstructor (System.Type.EmptyTypes) != null) // Try to create using an empty constructor if existant 125 | return System.Activator.CreateInstance (); 126 | else // Else try to get default. Returns null only on reference types 127 | return default(T); 128 | } 129 | 130 | #endregion 131 | } 132 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42e7026d0da7df848ab67d517ac12d74 3 | timeCreated: 1437395312 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeTypes.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Collections.Generic; 6 | using NodeEditorFramework; 7 | 8 | namespace NodeEditorFramework 9 | { 10 | /// 11 | /// Handles fetching and storing of all NodeDeclarations 12 | /// 13 | public static class NodeTypes 14 | { 15 | public static Dictionary nodes; 16 | 17 | /// 18 | /// Fetches every Node Declaration in the assembly and stores them in the nodes List. 19 | /// nodes List contains a default instance of each node type in the key and editor specific NodeData in the value 20 | /// 21 | public static void FetchNodes() 22 | { 23 | nodes = new Dictionary (); 24 | 25 | List scriptAssemblies = AppDomain.CurrentDomain.GetAssemblies ().Where ((Assembly assembly) => assembly.FullName.Contains ("Assembly")).ToList (); 26 | if (!scriptAssemblies.Contains (Assembly.GetExecutingAssembly ())) 27 | scriptAssemblies.Add (Assembly.GetExecutingAssembly ()); 28 | foreach (Assembly assembly in scriptAssemblies) 29 | { 30 | foreach (Type type in assembly.GetTypes ().Where (T => T.IsClass && !T.IsAbstract && T.IsSubclassOf (typeof (Node)))) 31 | { 32 | object[] nodeAttributes = type.GetCustomAttributes (typeof (NodeAttribute), false); 33 | NodeAttribute attr = nodeAttributes [0] as NodeAttribute; 34 | if (attr == null || !attr.hide) 35 | { 36 | Node node = ScriptableObject.CreateInstance (type.Name) as Node; // Create a 'raw' instance (not setup using the appropriate Create function) 37 | node = node.Create (Vector2.zero); // From that, call the appropriate Create Method to init the previously 'raw' instance 38 | nodes.Add (node, new NodeData (attr == null? node.name : attr.contextText)); 39 | } 40 | } 41 | } 42 | } 43 | 44 | /// 45 | /// Returns the NodeData for the given Node 46 | /// 47 | public static NodeData getNodeData (Node node) 48 | { 49 | return nodes [getDefaultNode (node.GetID)]; 50 | } 51 | 52 | /// 53 | /// Returns the default node from the given nodeID. 54 | /// The default node is a dummy used to create other nodes (Due to various limitations creation has to be performed on Node instances) 55 | /// 56 | public static Node getDefaultNode (string nodeID) 57 | { 58 | return nodes.Keys.Single ((Node node) => node.GetID == nodeID); 59 | } 60 | 61 | /// 62 | /// Returns the default node from the node type. 63 | /// The default node is a dummy used to create other nodes (Due to various limitations creation has to be performed on Node instances) 64 | /// 65 | public static T getDefaultNode () where T : Node 66 | { 67 | return nodes.Keys.Single ((Node node) => node.GetType () == typeof (T)) as T; 68 | } 69 | } 70 | 71 | /// 72 | /// The NodeData contains the additional, editor specific data of a node type 73 | /// 74 | public struct NodeData 75 | { 76 | public string adress; 77 | 78 | public NodeData (string name) 79 | { 80 | adress = name; 81 | } 82 | } 83 | 84 | /// 85 | /// The NodeAttribute is used to specify editor specific data for a node type, later stored using a NodeData 86 | /// 87 | public class NodeAttribute : Attribute 88 | { 89 | public bool hide { get; private set; } 90 | public string contextText { get; private set; } 91 | 92 | public NodeAttribute (bool HideNode, string ReplacedContextText) 93 | { 94 | hide = HideNode; 95 | contextText = ReplacedContextText; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /Node_Editor/Framework/NodeTypes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1cdfbc99cfb2a2c4db2bb4cbbec242fc 3 | timeCreated: 1449162341 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/Example/AllAroundNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using NodeEditorFramework; 3 | 4 | [Node (false, "Standard/Example/AllAround Node")] 5 | public class AllAroundNode : Node 6 | { 7 | public const string ID = "allaroundNode"; 8 | public override string GetID { get { return ID; } } 9 | 10 | public override bool AllowRecursion { get { return true; } } 11 | public override bool ContinueCalculation { get { return true; } } 12 | 13 | public override Node Create (Vector2 pos) 14 | { 15 | AllAroundNode node = CreateInstance (); 16 | 17 | node.rect = new Rect (pos.x, pos.y, 60, 60); 18 | node.name = "AllAround Node"; 19 | 20 | node.CreateInput ("Input Top", "Float", NodeSide.Top, 20); 21 | node.CreateInput ("Input Bottom", "Float", NodeSide.Bottom, 20); 22 | node.CreateInput ("Input Right", "Float", NodeSide.Right, 20); 23 | node.CreateInput ("Input Left", "Float", NodeSide.Left, 20); 24 | 25 | node.CreateOutput ("Output Top", "Float", NodeSide.Top, 40); 26 | node.CreateOutput ("Output Bottom", "Float", NodeSide.Bottom, 40); 27 | node.CreateOutput ("Output Right", "Float", NodeSide.Right, 40); 28 | node.CreateOutput ("Output Left", "Float", NodeSide.Left, 40); 29 | 30 | return node; 31 | } 32 | 33 | protected internal override void DrawNode () 34 | { 35 | Rect nodeRect = rect; 36 | nodeRect.position += NodeEditor.curEditorState.zoomPanAdjust; 37 | 38 | Rect bodyRect = new Rect (nodeRect.x, nodeRect.y, nodeRect.width, nodeRect.height); 39 | 40 | GUI.changed = false; 41 | GUILayout.BeginArea (bodyRect, GUI.skin.box); 42 | NodeGUI (); 43 | GUILayout.EndArea (); 44 | } 45 | 46 | protected internal override void NodeGUI () 47 | { 48 | 49 | } 50 | 51 | public override bool Calculate () 52 | { 53 | Outputs [0].SetValue (Inputs [0].GetValue ()); 54 | Outputs [1].SetValue (Inputs [1].GetValue ()); 55 | Outputs [2].SetValue (Inputs [2].GetValue ()); 56 | Outputs [3].SetValue (Inputs [3].GetValue ()); 57 | 58 | return true; 59 | } 60 | } -------------------------------------------------------------------------------- /Node_Editor/Nodes/Example/AllAroundNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec20c217ebe61744d82a69ad83ff2447 3 | timeCreated: 1444930712 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/Example/ExampleNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using NodeEditorFramework; 3 | using NodeEditorFramework.Utilities; 4 | 5 | [Node (false, "Standard/Example/Basic Node")] 6 | public class ExampleNode : Node 7 | { 8 | public const string ID = "exampleNode"; 9 | public override string GetID { get { return ID; } } 10 | 11 | public override Node Create (Vector2 pos) 12 | { 13 | ExampleNode node = CreateInstance (); 14 | 15 | node.rect = new Rect (pos.x, pos.y, 150, 60); 16 | node.name = "Example Node"; 17 | 18 | node.CreateInput ("Value", "Float"); 19 | node.CreateOutput ("Output val", "Float"); 20 | 21 | return node; 22 | } 23 | 24 | protected internal override void NodeGUI () 25 | { 26 | GUILayout.Label ("This is a custom Node!"); 27 | 28 | GUILayout.BeginHorizontal (); 29 | GUILayout.BeginVertical (); 30 | 31 | Inputs [0].DisplayLayout (); 32 | 33 | GUILayout.EndVertical (); 34 | GUILayout.BeginVertical (); 35 | 36 | Outputs [0].DisplayLayout (); 37 | 38 | GUILayout.EndVertical (); 39 | GUILayout.EndHorizontal (); 40 | 41 | } 42 | 43 | public override bool Calculate () 44 | { 45 | if (!allInputsReady ()) 46 | return false; 47 | Outputs[0].SetValue (Inputs[0].GetValue () * 5); 48 | return true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/Example/ExampleNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 039d2c5c84d9bf0489aecb2b0b669362 3 | timeCreated: 1447019305 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/FloatCalc/CalcNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using NodeEditorFramework; 4 | using NodeEditorFramework.Utilities; 5 | 6 | [System.Serializable] 7 | [Node (false, "Standard/Float/Calculation")] 8 | public class CalcNode : Node 9 | { 10 | public enum CalcType { Add, Substract, Multiply, Divide } 11 | public CalcType type = CalcType.Add; 12 | 13 | public const string ID = "calcNode"; 14 | public override string GetID { get { return ID; } } 15 | 16 | public float Input1Val = 1f; 17 | public float Input2Val = 1f; 18 | 19 | public override Node Create (Vector2 pos) 20 | { 21 | CalcNode node = CreateInstance (); 22 | 23 | node.name = "Calc Node"; 24 | node.rect = new Rect (pos.x, pos.y, 200, 100); 25 | 26 | node.CreateInput ("Input 1", "Float"); 27 | node.CreateInput ("Input 2", "Float"); 28 | 29 | node.CreateOutput ("Output 1", "Float"); 30 | 31 | return node; 32 | } 33 | 34 | protected internal override void NodeGUI () 35 | { 36 | GUILayout.BeginHorizontal (); 37 | GUILayout.BeginVertical (); 38 | 39 | if (Inputs [0].connection != null) 40 | GUILayout.Label (Inputs [0].name); 41 | else 42 | Input1Val = RTEditorGUI.FloatField (GUIContent.none, Input1Val); 43 | InputKnob (0); 44 | // -- 45 | if (Inputs [1].connection != null) 46 | GUILayout.Label (Inputs [1].name); 47 | else 48 | Input2Val = RTEditorGUI.FloatField (GUIContent.none, Input2Val); 49 | InputKnob (1); 50 | 51 | GUILayout.EndVertical (); 52 | GUILayout.BeginVertical (); 53 | 54 | Outputs [0].DisplayLayout (); 55 | 56 | GUILayout.EndVertical (); 57 | GUILayout.EndHorizontal (); 58 | 59 | #if UNITY_EDITOR 60 | type = (CalcType)UnityEditor.EditorGUILayout.EnumPopup (new GUIContent ("Calculation Type", "The type of calculation performed on Input 1 and Input 2"), type); 61 | #else 62 | GUILayout.Label (new GUIContent ("Calculation Type: " + type.ToString (), "The type of calculation performed on Input 1 and Input 2")); 63 | #endif 64 | 65 | if (GUI.changed) 66 | NodeEditor.RecalculateFrom (this); 67 | } 68 | 69 | public override bool Calculate () 70 | { 71 | if (Inputs[0].connection != null) 72 | Input1Val = Inputs[0].connection.GetValue (); 73 | if (Inputs[1].connection != null) 74 | Input2Val = Inputs[1].connection.GetValue (); 75 | 76 | switch (type) 77 | { 78 | case CalcType.Add: 79 | Outputs[0].SetValue (Input1Val + Input2Val); 80 | break; 81 | case CalcType.Substract: 82 | Outputs[0].SetValue (Input1Val - Input2Val); 83 | break; 84 | case CalcType.Multiply: 85 | Outputs[0].SetValue (Input1Val * Input2Val); 86 | break; 87 | case CalcType.Divide: 88 | Outputs[0].SetValue (Input1Val / Input2Val); 89 | break; 90 | } 91 | 92 | return true; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/FloatCalc/CalcNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27a65ad2b155f0d40838ed81292e4df7 3 | timeCreated: 1447019305 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/FloatCalc/DisplayNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using NodeEditorFramework; 4 | 5 | [System.Serializable] 6 | [Node (false, "Standard/Float/Display")] 7 | public class DisplayNode : Node 8 | { 9 | public const string ID = "displayNode"; 10 | public override string GetID { get { return ID; } } 11 | 12 | [HideInInspector] 13 | public bool assigned = false; 14 | public float value = 0; 15 | 16 | public override Node Create (Vector2 pos) 17 | { // This function has to be registered in Node_Editor.ContextCallback 18 | DisplayNode node = CreateInstance (); 19 | 20 | node.name = "Display Node"; 21 | node.rect = new Rect (pos.x, pos.y, 150, 50); 22 | 23 | NodeInput.Create (node, "Value", "Float"); 24 | 25 | return node; 26 | } 27 | 28 | protected internal override void NodeGUI () 29 | { 30 | Inputs [0].DisplayLayout (new GUIContent ("Value : " + (assigned? value.ToString () : ""), "The input value to display")); 31 | } 32 | 33 | public override bool Calculate () 34 | { 35 | if (!allInputsReady ()) 36 | { 37 | value = 0; 38 | assigned = false; 39 | return false; 40 | } 41 | 42 | value = Inputs[0].connection.GetValue(); 43 | assigned = true; 44 | 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/FloatCalc/DisplayNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 39f5df92d12f07d45a4fcfb96e1b36d8 3 | timeCreated: 1437395312 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Nodes/FloatCalc/InputNode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using NodeEditorFramework; 4 | using NodeEditorFramework.Utilities; 5 | 6 | [System.Serializable] 7 | [Node (false, "Standard/Float/Input")] 8 | public class InputNode : Node 9 | { 10 | public const string ID = "inputNode"; 11 | public override string GetID { get { return ID; } } 12 | 13 | public float value = 1f; 14 | 15 | public override Node Create (Vector2 pos) 16 | { // This function has to be registered in Node_Editor.ContextCallback 17 | InputNode node = CreateInstance (); 18 | 19 | node.name = "Input Node"; 20 | node.rect = new Rect (pos.x, pos.y, 200, 50);; 21 | 22 | NodeOutput.Create (node, "Value", "Float"); 23 | 24 | return node; 25 | } 26 | 27 | protected internal override void NodeGUI () 28 | { 29 | value = RTEditorGUI.FloatField (new GUIContent ("Value", "The input value of type float"), value); 30 | OutputKnob (0); 31 | 32 | if (GUI.changed) 33 | NodeEditor.RecalculateFrom (this); 34 | } 35 | 36 | public override bool Calculate () 37 | { 38 | Outputs[0].SetValue (value); 39 | return true; 40 | } 41 | } -------------------------------------------------------------------------------- /Node_Editor/Nodes/FloatCalc/InputNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3dd50b389073b148b5ec10e419dc073 3 | timeCreated: 1437395312 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Resources/LineShader.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/LineShader" 2 | { 3 | SubShader 4 | { 5 | Blend SrcAlpha OneMinusSrcAlpha 6 | ZWrite Off 7 | Cull Off 8 | Fog { Mode Off } 9 | 10 | Pass 11 | { 12 | CGPROGRAM 13 | #pragma vertex vert 14 | #pragma fragment frag 15 | 16 | uniform float4 _LineColor; 17 | uniform sampler2D _LineTexture; 18 | 19 | struct v2f 20 | { 21 | float2 texcoord : TEXCOORD0; 22 | float4 vertex : POSITION; 23 | }; 24 | 25 | v2f vert (float2 texcoord : TEXCOORD0, float4 vertex : POSITION) 26 | { 27 | v2f o; 28 | o.vertex = mul(UNITY_MATRIX_MVP, vertex); 29 | o.texcoord = texcoord; 30 | return o; 31 | } 32 | 33 | float4 frag (v2f i) : COLOR 34 | { 35 | return _LineColor * tex2D (_LineTexture, i.texcoord); 36 | } 37 | ENDCG 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Saves/Calculation Canvas.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Saves/Calculation Canvas.asset -------------------------------------------------------------------------------- /Node_Editor/Resources/Saves/Calculation Canvas.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60480b2d075467a42b5d9e035f6d2a8f 3 | timeCreated: 1456340282 4 | licenseType: Free 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Saves/Recursion Test Canvas.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Saves/Recursion Test Canvas.asset -------------------------------------------------------------------------------- /Node_Editor/Resources/Saves/Recursion Test Canvas.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87d42772d90da95488f28f43882ee301 3 | timeCreated: 1455405859 4 | licenseType: Free 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Saves/State Canvas.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Saves/State Canvas.asset -------------------------------------------------------------------------------- /Node_Editor/Resources/Saves/State Canvas.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15c039f0dad541a42bc1d5eb00afb4ea 3 | timeCreated: 1455405865 4 | licenseType: Free 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/AALine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/AALine.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/AALine.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ef3fefb6407298c42bbcbc73ad562830 3 | timeCreated: 1437572302 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 1 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -3 30 | maxTextureSize: 2048 31 | textureSettings: 32 | filterMode: -1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 0 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 3, z: 0, w: 3} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Icon_Dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/Icon_Dark.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Icon_Dark.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7e0ecbca30f9b347bb68f235cd9cae4 3 | timeCreated: 1450128686 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -2 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: -1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Icon_Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/Icon_Light.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Icon_Light.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8bfe9901a943bc04ebeae2ba7f8f409e 3 | timeCreated: 1450128686 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -2 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: -1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/In_Knob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/In_Knob.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/In_Knob.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: abb6137f2f8441f4caa48872db0b26e0 3 | timeCreated: 1437259204 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 1 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -3 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: 1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Knobs.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/Knobs.xcf -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Knobs.xcf.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff7f91b939ae6d94db540d2ee38349fe 3 | timeCreated: 1450128681 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/NE_Box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/NE_Box.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/NE_Box.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3362057e6db2cea428eb8de4a4800db0 3 | timeCreated: 1453313403 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 1 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: 7 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: 0 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 2 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 1 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 3, y: 3, z: 3, w: 3} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 5 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/NE_Button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/NE_Button.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/NE_Button.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: acc3252d2ced34346a4fb935f95286e3 3 | timeCreated: 1441568627 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 1 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -3 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: 1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 2 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 1 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 4, y: 7, z: 4, w: 4} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 5 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/NE_GUI.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/NE_GUI.xcf -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/NE_GUI.xcf.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 52c97a505457a9c4a9090a94d1701247 3 | timeCreated: 1450128681 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Out_Knob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/Out_Knob.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/Out_Knob.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d39755f0456c1284d8330cdf12c03faf 3 | timeCreated: 1437259204 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 1 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -3 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: 1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/background.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/background.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4ab1df7fc134e8b4c86823e805911657 3 | timeCreated: 1437259204 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 1 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -2 30 | maxTextureSize: 512 31 | textureSettings: 32 | filterMode: -1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/expandRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unagiHuman/Node_Editor/3ea157a947df340f91f431dfb30871bb09792c35/Node_Editor/Resources/Textures/expandRight.png -------------------------------------------------------------------------------- /Node_Editor/Resources/Textures/expandRight.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b09b3b201c310aa48aacdc1446dfc4bb 3 | timeCreated: 1441487997 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | linearTexture: 1 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -2 30 | maxTextureSize: 32 31 | textureSettings: 32 | filterMode: -1 33 | aniso: 1 34 | mipBias: -1 35 | wrapMode: 1 36 | nPOTScale: 0 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | allowsAlphaSplitting: 0 41 | spriteMode: 0 42 | spriteExtrude: 1 43 | spriteMeshType: 1 44 | alignment: 0 45 | spritePivot: {x: 0.5, y: 0.5} 46 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 47 | spritePixelsToUnits: 100 48 | alphaIsTransparency: 1 49 | textureType: 2 50 | buildTargetSettings: [] 51 | spriteSheet: 52 | sprites: [] 53 | outline: [] 54 | spritePackingTag: 55 | userData: 56 | assetBundleName: 57 | assetBundleVariant: 58 | -------------------------------------------------------------------------------- /Node_Editor/RuntimeNodeEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | using NodeEditorFramework; 7 | using NodeEditorFramework.Utilities; 8 | 9 | public class RuntimeNodeEditor : MonoBehaviour 10 | { 11 | public string canvasPath; 12 | public NodeCanvas canvas; 13 | private NodeEditorState state; 14 | 15 | public bool screenSize = false; 16 | private Rect canvasRect; 17 | public Rect specifiedRootRect; 18 | public Rect specifiedCanvasRect; 19 | 20 | public void Start () 21 | { 22 | if (!string.IsNullOrEmpty (canvasPath)) 23 | LoadNodeCanvas (canvasPath); 24 | else 25 | NewNodeCanvas (); 26 | NodeEditor.initiated = false; 27 | FPSCounter.Create (); 28 | } 29 | 30 | public void Update () 31 | { 32 | NodeEditor.Update (); 33 | FPSCounter.Update (); 34 | } 35 | 36 | public void OnGUI () 37 | { 38 | if (canvas != null && state != null) 39 | { 40 | NodeEditor.checkInit (); 41 | if (NodeEditor.InitiationError) 42 | { 43 | GUILayout.Label ("Initiation failed! Check console for more information!"); 44 | return; 45 | } 46 | 47 | try 48 | { 49 | if (!screenSize && specifiedRootRect.max != specifiedRootRect.min) GUI.BeginGroup (specifiedRootRect, NodeEditorGUI.nodeSkin.box); 50 | 51 | canvasRect = screenSize? new Rect (0, 0, Screen.width, Screen.height) : specifiedCanvasRect; 52 | canvasRect.width -= 200; 53 | state.canvasRect = canvasRect; 54 | NodeEditor.DrawCanvas (canvas, state); 55 | SideGUI (); 56 | 57 | if (!screenSize && specifiedRootRect.max != specifiedRootRect.min) GUI.EndGroup (); 58 | } 59 | catch (Exception e) 60 | { // on exceptions in drawing flush the canvas to avoid locking the ui. 61 | NewNodeCanvas (); 62 | NodeEditor.ReInit (true); 63 | Debug.LogError ("Unloaded Canvas due to exception in Draw!"); 64 | Debug.LogException (e); 65 | } 66 | } 67 | } 68 | 69 | public void SideGUI () 70 | { 71 | GUILayout.BeginArea (new Rect (canvasRect.x + state.canvasRect.width, state.canvasRect.y, 200, state.canvasRect.height), NodeEditorGUI.nodeSkin.box); 72 | GUILayout.Label (new GUIContent ("Node Editor (" + canvas.name + ")", "The currently opened canvas in the Node Editor")); 73 | screenSize = GUILayout.Toggle (screenSize, "Adapt to Screen"); 74 | GUILayout.Label ("FPS: " + FPSCounter.currentFPS); 75 | 76 | GUILayout.Label (new GUIContent ("Node Editor (" + canvas.name + ")"), NodeEditorGUI.nodeLabelBold); 77 | 78 | #if UNITY_EDITOR 79 | if (GUILayout.Button (new GUIContent ("Save Canvas", "Saves the Canvas to a Canvas Save File in the Assets Folder"))) 80 | { 81 | string path = UnityEditor.EditorUtility.SaveFilePanelInProject ("Save Node Canvas", "Node Canvas", "asset", "", NodeEditor.editorPath + "Resources/Saves/"); 82 | if (!string.IsNullOrEmpty (path)) 83 | NodeEditorSaveManager.SaveNodeCanvas (path, true, canvas, state); 84 | } 85 | 86 | if (GUILayout.Button (new GUIContent ("Load Canvas", "Loads the Canvas from a Canvas Save File in the Assets Folder"))) 87 | { 88 | string path = UnityEditor.EditorUtility.OpenFilePanel ("Load Node Canvas", NodeEditor.editorPath + "Resources/Saves/", "asset"); 89 | if (!path.Contains (Application.dataPath)) 90 | { 91 | if (!string.IsNullOrEmpty (path)) 92 | Debug.LogWarning ("You should select an asset inside your project folder!"); 93 | } 94 | else 95 | { 96 | path = path.Replace (Application.dataPath, "Assets"); 97 | LoadNodeCanvas (path); 98 | } 99 | } 100 | 101 | #endif 102 | 103 | if (GUILayout.Button (new GUIContent ("New Canvas", "Loads an empty Canvas"))) 104 | NewNodeCanvas (); 105 | 106 | if (GUILayout.Button (new GUIContent ("Recalculate All", "Initiates complete recalculate. Usually does not need to be triggered manually."))) 107 | NodeEditor.RecalculateAll (canvas); 108 | 109 | if (GUILayout.Button ("Force Re-Init")) 110 | NodeEditor.ReInit (true); 111 | 112 | NodeEditorGUI.knobSize = RTEditorGUI.IntSlider (new GUIContent ("Handle Size", "The size of the Node Input/Output handles"), NodeEditorGUI.knobSize, 12, 20); 113 | state.zoom = RTEditorGUI.Slider (new GUIContent ("Zoom", "Use the Mousewheel. Seriously."), state.zoom, 0.6f, 2); 114 | 115 | GUILayout.EndArea (); 116 | } 117 | 118 | public void LoadNodeCanvas (string path) 119 | { 120 | // Load the NodeCanvas 121 | canvas = NodeEditorSaveManager.LoadNodeCanvas (path); 122 | if (canvas == null) 123 | { 124 | NewNodeCanvas (); 125 | return; 126 | } 127 | 128 | // Load the associated MainEditorState 129 | List editorStates = NodeEditorSaveManager.LoadEditorStates (path); 130 | if (editorStates.Count == 0) 131 | state = ScriptableObject.CreateInstance (); 132 | else 133 | { 134 | state = editorStates.Find (x => x.name == "MainEditorState"); 135 | if (state == null) state = editorStates[0]; 136 | } 137 | state.canvas = canvas; 138 | 139 | NodeEditor.RecalculateAll (canvas); 140 | } 141 | 142 | public void NewNodeCanvas () 143 | { 144 | canvas = ScriptableObject.CreateInstance (); 145 | state = ScriptableObject.CreateInstance (); 146 | state.canvas = canvas; 147 | state.name = "MainEditorState"; 148 | } 149 | } 150 | 151 | 152 | public class FPSCounter 153 | { 154 | public static float FPSMeasurePeriod = 0.1f; 155 | private int FPSAccumulator; 156 | private float FPSNextPeriod; 157 | public static int currentFPS; 158 | 159 | private static FPSCounter instance; 160 | 161 | public static void Create () 162 | { 163 | if (instance == null) 164 | { 165 | instance = new FPSCounter (); 166 | instance.FPSNextPeriod = Time.realtimeSinceStartup + FPSMeasurePeriod; 167 | } 168 | } 169 | 170 | // has to be called 171 | public static void Update () 172 | { 173 | Create (); 174 | instance.FPSAccumulator++; 175 | if (Time.realtimeSinceStartup > instance.FPSNextPeriod) 176 | { 177 | currentFPS = (int) (instance.FPSAccumulator/FPSMeasurePeriod); 178 | instance.FPSAccumulator = 0; 179 | instance.FPSNextPeriod += FPSMeasurePeriod; 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Node_Editor/RuntimeNodeEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 058701fb26fe9874f8dffda7e66a0fe9 3 | timeCreated: 1450128681 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Node_Editor/Utilities/EditorLoadingControl.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using UnityEngine; 4 | using UnityEditor; 5 | #if UNITY_5_3 6 | using UnityEngine.SceneManagement; 7 | using UnityEditor.SceneManagement; 8 | #endif 9 | using System; 10 | 11 | namespace NodeEditorFramework.Utilities 12 | { 13 | [InitializeOnLoad] 14 | public static class EditorLoadingControl 15 | { 16 | #if UNITY_5_3 17 | private static Scene loadedScene; 18 | #else 19 | private static string loadedScene; 20 | #endif 21 | 22 | private static bool serializationTest = false; 23 | private static bool playmodeSwitchToEdit = false; 24 | private static bool toggleLateEnteredPlaymode = false; 25 | 26 | public static Action beforeEnteringPlayMode; 27 | public static Action justEnteredPlayMode; 28 | public static Action lateEnteredPlayMode; 29 | public static Action beforeLeavingPlayMode; 30 | public static Action justLeftPlayMode; 31 | public static Action justOpenedNewScene; 32 | 33 | static EditorLoadingControl () 34 | { 35 | EditorApplication.playmodeStateChanged -= PlaymodeStateChanged; 36 | EditorApplication.playmodeStateChanged += PlaymodeStateChanged; 37 | EditorApplication.update -= Update; 38 | EditorApplication.update += Update; 39 | EditorApplication.hierarchyWindowChanged -= OnHierarchyChange; 40 | EditorApplication.hierarchyWindowChanged += OnHierarchyChange; 41 | } 42 | 43 | private static void OnHierarchyChange () 44 | { 45 | #if UNITY_5_3 46 | Scene currentScene = EditorSceneManager.GetActiveScene (); 47 | #else 48 | string currentScene = Application.loadedLevelName; 49 | #endif 50 | if (loadedScene != currentScene) 51 | { 52 | if (justOpenedNewScene != null) 53 | justOpenedNewScene.Invoke (); 54 | loadedScene = currentScene; 55 | } 56 | } 57 | 58 | // Handles just after switch (non-serialized values lost) 59 | private static void Update () 60 | { 61 | if (toggleLateEnteredPlaymode) 62 | { 63 | toggleLateEnteredPlaymode = false; 64 | if (lateEnteredPlayMode != null) 65 | lateEnteredPlayMode.Invoke (); 66 | } 67 | serializationTest = true; 68 | } 69 | 70 | private static void PlaymodeStateChanged () 71 | { 72 | //Debug.Log ("Playmode State Change! isPlaying: " + Application.isPlaying + "; Serialized: " + serializationTest); 73 | if (!Application.isPlaying) 74 | { // Edit Mode 75 | if (playmodeSwitchToEdit) 76 | { // After Playmode 77 | //Debug.Log ("LOAD PLAY MODE Values in Edit Mode!!"); 78 | if (justLeftPlayMode != null) 79 | justLeftPlayMode.Invoke (); 80 | playmodeSwitchToEdit = false; 81 | } 82 | else 83 | { // Before Playmode 84 | //Debug.Log ("SAVE EDIT MODE Values before Play Mode!!"); 85 | if (beforeEnteringPlayMode != null) 86 | beforeEnteringPlayMode.Invoke (); 87 | } 88 | } 89 | else 90 | { // Play Mode 91 | if (serializationTest) 92 | { // Before Leaving Playmode 93 | //Debug.Log ("SAVE PLAY MODE Values before Edit Mode!!"); 94 | if (beforeLeavingPlayMode != null) 95 | beforeLeavingPlayMode.Invoke (); 96 | playmodeSwitchToEdit = true; 97 | } 98 | else 99 | { // After Entering Playmode 100 | //Debug.Log ("LOAD EDIT MODE Values in Play Mode!!"); 101 | if (justEnteredPlayMode != null) 102 | justEnteredPlayMode.Invoke (); 103 | toggleLateEnteredPlaymode = true; 104 | } 105 | 106 | } 107 | } 108 | } 109 | } 110 | #endif -------------------------------------------------------------------------------- /Node_Editor/Utilities/GUIScaleUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System; 5 | using System.Reflection; 6 | 7 | namespace NodeEditorFramework.Utilities 8 | { 9 | 10 | public static class GUIScaleUtility 11 | { 12 | // General 13 | private static bool compabilityMode; 14 | private static bool initiated; 15 | 16 | // Fast.Reflection delegates 17 | private static Func GetTopRectDelegate; 18 | private static Func topmostRectDelegate; 19 | 20 | // Delegate accessors 21 | public static Rect getTopRect { get { return (Rect)GetTopRectDelegate.Invoke (); } } 22 | public static Rect getTopRectScreenSpace { get { return (Rect)topmostRectDelegate.Invoke (); } } 23 | 24 | // Rect stack for manipulating groups 25 | public static List currentRectStack { get; private set; } 26 | private static List> rectStackGroups; 27 | 28 | // Matrices stack 29 | private static List GUIMatrices; 30 | private static List adjustedGUILayout; 31 | 32 | private static FieldInfo currentGUILayoutCache; 33 | private static FieldInfo currentTopLevelGroup; 34 | 35 | #region Init 36 | 37 | public static void CheckInit () 38 | { 39 | if (!initiated) 40 | Init (); 41 | } 42 | 43 | public static void Init () 44 | { 45 | // Fetch rect acessors using Reflection 46 | Assembly UnityEngine = Assembly.GetAssembly (typeof (UnityEngine.GUI)); 47 | Type GUIClipType = UnityEngine.GetType ("UnityEngine.GUIClip", true); 48 | 49 | // string log = "Members without Bindflags: "; 50 | // foreach (MemberInfo member in GUIClipType.GetMembers ()) 51 | // log += member.MemberType + "-" + member.Name + " |-| "; 52 | // 53 | // log += Environment.NewLine + "Both NonPublic and Public Instance Members: "; 54 | // foreach (MemberInfo member in GUIClipType.GetMembers (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) 55 | // log += member.MemberType + "-" + member.Name + " |-| "; 56 | // 57 | // log += Environment.NewLine + "Nonpublic Static Members: "; 58 | // foreach (MemberInfo member in GUIClipType.GetMembers (BindingFlags.Static | BindingFlags.NonPublic)) 59 | // log += member.MemberType + "-" + member.Name + " |-| "; 60 | // 61 | // log += Environment.NewLine + "Public Static Members: "; 62 | // foreach (MemberInfo member in GUIClipType.GetMembers (BindingFlags.Static | BindingFlags.Public)) 63 | // log += member.MemberType + "-" + member.Name + " |-| "; 64 | // 65 | // Debug.Log (log); 66 | 67 | PropertyInfo topmostRect = GUIClipType.GetProperty ("topmostRect", BindingFlags.Static | BindingFlags.Public); 68 | MethodInfo GetTopRect = GUIClipType.GetMethod ("GetTopRect", BindingFlags.Static | BindingFlags.NonPublic); 69 | MethodInfo ClipRect = GUIClipType.GetMethod ("Clip", BindingFlags.Static | BindingFlags.Public, Type.DefaultBinder, new Type[] { typeof(Rect) }, new ParameterModifier[] {}); 70 | 71 | if (GUIClipType == null || topmostRect == null || GetTopRect == null || ClipRect == null) 72 | { 73 | Debug.LogWarning ("GUIScaleUtility cannot run on this system! Compability mode enabled. For you that means you're not able to use the Node Editor inside more than one group:( Please PM me (Seneral @UnityForums) so I can figure out what causes this! Thanks!"); 74 | Debug.LogWarning ((GUIClipType == null? "GUIClipType is Null, " : "") + (topmostRect == null? "topmostRect is Null, " : "") + (GetTopRect == null? "GetTopRect is Null, " : "") + (ClipRect == null? "ClipRect is Null, " : "")); 75 | compabilityMode = true; 76 | initiated = true; 77 | return; 78 | } 79 | 80 | // Create simple acessor delegates 81 | GetTopRectDelegate = (Func)Delegate.CreateDelegate (typeof(Func), GetTopRect); 82 | topmostRectDelegate = (Func)Delegate.CreateDelegate (typeof(Func), topmostRect.GetGetMethod ()); 83 | 84 | // As we can call Begin/Ends inside another, we need to save their states hierarchial in Lists (not Stack, as we need to iterate over them!): 85 | currentRectStack = new List (); 86 | rectStackGroups = new List> (); 87 | GUIMatrices = new List (); 88 | adjustedGUILayout = new List (); 89 | 90 | // Sometimes, strange errors pop up (related to Mac?), which we try to catch and enable a compability Mode no supporting zooming in groups 91 | try 92 | { 93 | topmostRectDelegate.Invoke (); 94 | } 95 | catch (Exception e) 96 | { 97 | Debug.LogWarning ("GUIScaleUtility cannot run on this system! Compability mode enabled. For you that means you're not able to use the Node Editor inside more than one group:( Please PM me (Seneral @UnityForums) so I can figure out what causes this! Thanks!"); 98 | Debug.Log (e.Message); 99 | compabilityMode = true; 100 | } 101 | 102 | initiated = true; 103 | } 104 | 105 | #endregion 106 | 107 | #region Scale Area 108 | 109 | // public static Vector2 secondaryGroupOffset; 110 | // 111 | // public static Vector2 primaryScale; 112 | // public static Vector2 primaryZoomPanAdjust; 113 | // public static Rect primaryInitialRect; 114 | // public static Rect primaryScaledRect; 115 | // 116 | // public static Vector2 secondaryScale; 117 | // public static Vector2 secondaryZoomPanAdjust; 118 | // public static Rect secondaryInitialRect; 119 | // public static Rect secondaryScaledRect; 120 | 121 | public static Vector2 getCurrentScale { get { return new Vector2 (1/GUI.matrix.GetColumn (0).magnitude, 1/GUI.matrix.GetColumn (1).magnitude); } } 122 | 123 | /// 124 | /// Begins a scaled local area. 125 | /// Returns vector to offset GUI controls with to account for zooming to the pivot. 126 | /// Using adjustGUILayout does that automatically for GUILayout rects. Theoretically can be nested! 127 | /// 128 | public static Vector2 BeginScale (ref Rect rect, Vector2 zoomPivot, float zoom, bool adjustGUILayout) 129 | { 130 | Rect screenRect; 131 | if (compabilityMode) 132 | { // In compability mode, we will assume only one top group and do everything manually, not using reflected calls (-> practically blind) 133 | GUI.EndGroup (); 134 | screenRect = rect; 135 | #if UNITY_EDITOR 136 | if (!Application.isPlaying) 137 | screenRect.y += 23; 138 | #endif 139 | } 140 | else 141 | { // If it's supported, we take the completely generic way using reflected calls 142 | GUIScaleUtility.BeginNoClip (); 143 | screenRect = GUIScaleUtility.InnerToScreenRect (rect); 144 | } 145 | 146 | // Vector2 GUIScale = getCurrentScale; 147 | 148 | rect = ScaleRect (screenRect, screenRect.position + zoomPivot, new Vector2 (zoom, zoom)); 149 | 150 | // bool primary = adjustedGUILayout.Count == 0; 151 | // if (!primary) 152 | // { 153 | // rect.position += secondaryGroupOffset; 154 | // 155 | // secondaryScale = new Vector2 (zoom, zoom); 156 | // secondaryInitialRect = screenRect; 157 | // secondaryScaledRect = rect; 158 | // } 159 | // else 160 | // { 161 | // primaryScale = new Vector2 (zoom, zoom); 162 | // primaryInitialRect = screenRect; 163 | // primaryScaledRect = rect; 164 | // } 165 | 166 | // Now continue drawing using the new clipping group 167 | GUI.BeginGroup (rect); 168 | rect.position = Vector2.zero; // Adjust because we entered the new group 169 | 170 | // Because I currently found no way to actually scale to a custom pivot rather than (0, 0), 171 | // we'll make use of a cheat and just offset it accordingly to let it appear as if it would scroll to the center 172 | // Note, due to that, controls not adjusted are still scaled to (0, 0) 173 | Vector2 zoomPosAdjust = rect.center - screenRect.size/2 + zoomPivot; 174 | 175 | // For GUILayout, we can make this adjustment here if desired 176 | adjustedGUILayout.Add (adjustGUILayout); 177 | if (adjustGUILayout) 178 | { 179 | GUILayout.BeginHorizontal (); 180 | GUILayout.Space (rect.center.x - screenRect.size.x + zoomPivot.x); 181 | GUILayout.BeginVertical (); 182 | GUILayout.Space (rect.center.y - screenRect.size.y + zoomPivot.y); 183 | } 184 | 185 | // Take a matrix backup to restore back later on 186 | GUIMatrices.Add (GUI.matrix); 187 | 188 | // Scale GUI.matrix. After that we have the correct clipping group again. 189 | GUIUtility.ScaleAroundPivot (new Vector2 (1/zoom, 1/zoom), zoomPosAdjust); 190 | 191 | // if (!primary) 192 | // { 193 | // secondaryZoomPanAdjust = zoomPosAdjust; 194 | // } 195 | // else 196 | // { 197 | // primaryZoomPanAdjust = zoomPosAdjust; 198 | // } 199 | 200 | return zoomPosAdjust; 201 | } 202 | 203 | /// 204 | /// Ends a scale region previously opened with BeginScale 205 | /// 206 | public static void EndScale () 207 | { 208 | // Set last matrix and clipping group 209 | if (GUIMatrices.Count == 0 || adjustedGUILayout.Count == 0) 210 | throw new UnityException ("GUIScaleUtility: You are ending more scale regions than you are beginning!"); 211 | GUI.matrix = GUIMatrices[GUIMatrices.Count-1]; 212 | GUIMatrices.RemoveAt (GUIMatrices.Count-1); 213 | 214 | // End GUILayout zoomPosAdjustment 215 | if (adjustedGUILayout[adjustedGUILayout.Count-1]) 216 | { 217 | GUILayout.EndVertical (); 218 | GUILayout.EndHorizontal (); 219 | } 220 | adjustedGUILayout.RemoveAt (adjustedGUILayout.Count-1); 221 | 222 | // End the scaled group 223 | GUI.EndGroup (); 224 | 225 | if (compabilityMode) 226 | { // In compability mode, we don't know the previous group rect, but as we cannot use top groups there either way, we restore the screen group 227 | if (!Application.isPlaying) // We're in an editor window 228 | GUI.BeginClip (new Rect (0, 23, Screen.width, Screen.height-23)); 229 | else 230 | GUI.BeginClip (new Rect (0, 0, Screen.width, Screen.height)); 231 | } 232 | else 233 | { // Else, restore the clips (groups) 234 | GUIScaleUtility.RestoreClips (); 235 | } 236 | } 237 | 238 | #endregion 239 | 240 | #region Clips Hierarchy 241 | 242 | /// 243 | /// Begins a field without groups. They should be restored using RestoreClips. Can be nested! 244 | /// 245 | public static void BeginNoClip () 246 | { 247 | // Record and close all clips one by one, from bottom to top, until we hit the 'origin' 248 | List rectStackGroup = new List (); 249 | Rect topMostClip = getTopRect; 250 | while (topMostClip != new Rect (-10000, -10000, 40000, 40000)) 251 | { 252 | rectStackGroup.Add (topMostClip); 253 | GUI.EndClip (); 254 | topMostClip = getTopRect; 255 | } 256 | // Store the clips appropriately 257 | rectStackGroup.Reverse (); 258 | rectStackGroups.Add (rectStackGroup); 259 | currentRectStack.AddRange (rectStackGroup); 260 | } 261 | 262 | /// 263 | /// Begins a field without the last count groups. They should be restored using RestoreClips. Can be nested! 264 | /// 265 | public static void MoveClipsUp (int count) 266 | { 267 | // Record and close all clips one by one, from bottom to top, until reached the count or hit the 'origin' 268 | List rectStackGroup = new List (); 269 | Rect topMostClip = getTopRect; 270 | while (topMostClip != new Rect (-10000, -10000, 40000, 40000) && count > 0) 271 | { 272 | rectStackGroup.Add (topMostClip); 273 | GUI.EndClip (); 274 | topMostClip = getTopRect; 275 | count--; 276 | } 277 | // Store the clips appropriately 278 | rectStackGroup.Reverse (); 279 | rectStackGroups.Add (rectStackGroup); 280 | currentRectStack.AddRange (rectStackGroup); 281 | } 282 | 283 | /// 284 | /// Restores the clips removed in BeginNoClip or MoveClipsUp 285 | /// 286 | public static void RestoreClips () 287 | { 288 | if (rectStackGroups.Count == 0) 289 | { 290 | Debug.LogError ("GUIClipHierarchy: BeginNoClip/MoveClipsUp - RestoreClips count not balanced!"); 291 | return; 292 | } 293 | 294 | // Read and restore clips one by one, from top to bottom 295 | List rectStackGroup = rectStackGroups[rectStackGroups.Count-1]; 296 | for (int clipCnt = 0; clipCnt < rectStackGroup.Count; clipCnt++) 297 | { 298 | GUI.BeginClip (rectStackGroup[clipCnt]); 299 | currentRectStack.RemoveAt (currentRectStack.Count-1); 300 | } 301 | rectStackGroups.RemoveAt (rectStackGroups.Count-1); 302 | } 303 | 304 | #endregion 305 | 306 | #region Layout & Matrix Ignores 307 | 308 | /// 309 | /// Ignores the current GUILayout cache and begins a new one. Cannot be nested! 310 | /// 311 | public static void BeginNewLayout () 312 | { 313 | if (compabilityMode) 314 | return; 315 | // Will mimic a new layout by creating a new group at (0, 0). Will be restored though after ending the new Layout 316 | Rect topMostClip = getTopRect; 317 | if (topMostClip != new Rect (-10000, -10000, 40000, 40000)) 318 | GUILayout.BeginArea (new Rect (0, 0, topMostClip.width, topMostClip.height)); 319 | else 320 | GUILayout.BeginArea (new Rect (0, 0, Screen.width, Screen.height)); 321 | } 322 | 323 | /// 324 | /// Ends the last GUILayout cache 325 | /// 326 | public static void EndNewLayout () 327 | { 328 | if (!compabilityMode) 329 | GUILayout.EndArea (); 330 | } 331 | 332 | /// 333 | /// Begins an area without GUIMatrix transformations. Can be nested! 334 | /// 335 | public static void BeginIgnoreMatrix () 336 | { 337 | // Store and clean current matrix 338 | GUIMatrices.Add (GUI.matrix); 339 | GUI.matrix = Matrix4x4.identity; 340 | } 341 | 342 | /// 343 | /// Restores last matrix ignored with BeginIgnoreMatrix 344 | /// 345 | public static void EndIgnoreMatrix () 346 | { 347 | if (GUIMatrices.Count == 0) 348 | throw new UnityException ("GUIScaleutility: You are ending more ignoreMatrices than you are beginning!"); 349 | // Read and assign previous matrix 350 | GUI.matrix = GUIMatrices[GUIMatrices.Count-1]; 351 | GUIMatrices.RemoveAt (GUIMatrices.Count-1); 352 | } 353 | 354 | #endregion 355 | 356 | #region Helpers 357 | 358 | /// 359 | /// Scales the rect around the pivot with scale 360 | /// 361 | public static Vector2 ScalePosition (Vector2 pos, Vector2 pivot, Vector2 scale) 362 | { 363 | return Vector2.Scale (pos - pivot, scale) + pivot; 364 | } 365 | 366 | /// 367 | /// Scales the rect around the pivot with scale 368 | /// 369 | public static Rect ScaleRect (Rect rect, Vector2 pivot, Vector2 scale) 370 | { 371 | rect.position = Vector2.Scale (rect.position - pivot, scale) + pivot; 372 | rect.size = Vector2.Scale (rect.size, scale); 373 | return rect; 374 | } 375 | 376 | /// 377 | /// Transforms the rect to the new space aquired with BeginNoClip or MoveClipsUp. 378 | /// It's way faster to call GUIToScreenRect before calling any of these functions though! 379 | /// 380 | public static Rect InnerToScreenRect (Rect innerRect) 381 | { 382 | if (rectStackGroups == null || rectStackGroups.Count == 0) 383 | return innerRect; 384 | 385 | // Iterate through the clips and add positions ontop 386 | List rectStackGroup = rectStackGroups[rectStackGroups.Count-1]; 387 | for (int clipCnt = 0; clipCnt < rectStackGroup.Count; clipCnt++) 388 | innerRect.position += rectStackGroup[clipCnt].position; 389 | return innerRect; 390 | } 391 | 392 | /// 393 | /// Transforms the rect to screen space. 394 | /// Use InnerToScreenRect when you want to transform an old rect to the new space aquired with BeginNoClip or MoveClipsUp (slower, try to call this function before any of these two)! 395 | /// ATTENTION: This does not work well when any of the top groups is negative, means extends to the top or left of the screen. You may consider to use InnerToScreenRect then, if possible! 396 | /// 397 | public static Rect GUIToScreenRect (Rect guiRect) 398 | { 399 | guiRect.position += getTopRectScreenSpace.position; 400 | return guiRect; 401 | } 402 | 403 | #endregion 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /Node_Editor/Utilities/OverlayGUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | namespace NodeEditorFramework.Utilities 5 | { 6 | public static class OverlayGUI 7 | { 8 | public static PopupMenu currentPopup; 9 | 10 | /// 11 | /// Returns if any popup currently has control. 12 | /// 13 | public static bool HasPopupControl () 14 | { 15 | return currentPopup != null; 16 | } 17 | 18 | /// 19 | /// Starts the OverlayGUI (Call before any other GUI code!) 20 | /// 21 | public static void StartOverlayGUI () 22 | { 23 | if (currentPopup != null && Event.current.type != EventType.Layout && Event.current.type != EventType.Repaint) 24 | currentPopup.Draw (); 25 | } 26 | 27 | /// 28 | /// Ends the OverlayGUI (Call after any other GUI code!) 29 | /// 30 | public static void EndOverlayGUI () 31 | { 32 | if (currentPopup != null && (Event.current.type == EventType.Layout || Event.current.type == EventType.Repaint)) 33 | currentPopup.Draw (); 34 | } 35 | } 36 | 37 | /// 38 | /// A Generic Popupmenu. Used by GenericMenu, Popup (future), etc. 39 | /// 40 | public class PopupMenu 41 | { 42 | public delegate void MenuFunction (); 43 | public delegate void MenuFunctionData (object userData); 44 | 45 | public List menuItems = new List (); 46 | 47 | // State 48 | private Rect position; 49 | private string selectedPath = ""; 50 | private MenuItem groupToDraw; 51 | private float currentItemHeight; 52 | private bool close; 53 | 54 | // GUI variables 55 | public static GUIStyle backgroundStyle; 56 | public static Texture2D expandRight; 57 | public static float itemHeight; 58 | public static GUIStyle selectedLabel; 59 | 60 | public PopupMenu () 61 | { 62 | SetupGUI (); 63 | } 64 | 65 | public void SetupGUI () 66 | { 67 | backgroundStyle = new GUIStyle (GUI.skin.box); 68 | backgroundStyle.contentOffset = new Vector2 (2, 2); 69 | expandRight = NodeEditorFramework.Utilities.ResourceManager.LoadTexture ("Textures/expandRight.png"); 70 | itemHeight = GUI.skin.label.CalcHeight (new GUIContent ("text"), 100); 71 | 72 | selectedLabel = new GUIStyle (GUI.skin.label); 73 | selectedLabel.normal.background = NodeEditorFramework.Utilities.RTEditorGUI.ColorToTex (1, new Color (0.4f, 0.4f, 0.4f)); 74 | } 75 | 76 | public void Show (Rect pos) 77 | { 78 | position = pos; 79 | selectedPath = ""; 80 | OverlayGUI.currentPopup = this; 81 | } 82 | 83 | #region Creation 84 | 85 | public void AddItem (GUIContent content, bool on, MenuFunctionData func, object userData) 86 | { 87 | string path; 88 | MenuItem parent = AddHierarchy (ref content, out path); 89 | if (parent != null) 90 | parent.subItems.Add (new MenuItem (path, content, func, userData)); 91 | else 92 | menuItems.Add (new MenuItem (path, content, func, userData)); 93 | } 94 | 95 | public void AddItem (GUIContent content, bool on, MenuFunction func) 96 | { 97 | string path; 98 | MenuItem parent = AddHierarchy (ref content, out path); 99 | if (parent != null) 100 | parent.subItems.Add (new MenuItem (path, content, func)); 101 | else 102 | menuItems.Add (new MenuItem (path, content, func)); 103 | } 104 | 105 | public void AddSeparator (string path) 106 | { 107 | GUIContent content = new GUIContent (path); 108 | MenuItem parent = AddHierarchy (ref content, out path); 109 | if (parent != null) 110 | parent.subItems.Add (new MenuItem ()); 111 | else 112 | menuItems.Add (new MenuItem ()); 113 | } 114 | 115 | private MenuItem AddHierarchy (ref GUIContent content, out string path) 116 | { 117 | path = content.text; 118 | if (path.Contains ("/")) 119 | { // is inside a group 120 | string[] subContents = path.Split ('/'); 121 | string folderPath = subContents[0]; 122 | 123 | // top level group 124 | MenuItem parent = menuItems.Find ((MenuItem item) => item.content != null && item.content.text == folderPath); 125 | if (parent == null) 126 | menuItems.Add (parent = new MenuItem (folderPath, new GUIContent (folderPath), true)); 127 | 128 | // additional level groups 129 | for (int groupCnt = 1; groupCnt < subContents.Length-1; groupCnt++) 130 | { 131 | string folder = subContents[groupCnt]; 132 | folderPath += "/" + folder; 133 | MenuItem subGroup = parent.subItems.Find ((MenuItem item) => item.content != null && item.content.text == folder); 134 | if (subGroup == null) 135 | parent.subItems.Add (subGroup = new MenuItem (folderPath, new GUIContent (folder), true)); 136 | parent = subGroup; 137 | } 138 | 139 | // actual item 140 | path = content.text; 141 | content = new GUIContent (subContents[subContents.Length-1], content.tooltip); 142 | return parent; 143 | } 144 | return null; 145 | } 146 | 147 | #endregion 148 | 149 | #region Drawing 150 | 151 | public void Draw () 152 | { 153 | bool inRect = DrawGroup (position, menuItems); 154 | 155 | while (groupToDraw != null && !close) 156 | { 157 | MenuItem group = groupToDraw; 158 | groupToDraw = null; 159 | if (group.group) 160 | { 161 | if (DrawGroup (group.groupPos, group.subItems)) 162 | inRect = true; 163 | } 164 | } 165 | 166 | if (!inRect || close) 167 | OverlayGUI.currentPopup = null; 168 | 169 | NodeEditorFramework.NodeEditor.RepaintClients (); 170 | } 171 | 172 | private bool DrawGroup (Rect pos, List menuItems) 173 | { 174 | Rect rect = calculateRect (pos.position, menuItems); 175 | 176 | Rect clickRect = new Rect (rect); 177 | clickRect.xMax += 20; 178 | clickRect.xMin -= 20; 179 | clickRect.yMax += 20; 180 | clickRect.yMin -= 20; 181 | bool inRect = clickRect.Contains (Event.current.mousePosition); 182 | 183 | currentItemHeight = backgroundStyle.contentOffset.y; 184 | GUI.BeginGroup (extendRect (rect, backgroundStyle.contentOffset), GUIContent.none, backgroundStyle); 185 | for (int itemCnt = 0; itemCnt < menuItems.Count; itemCnt++) 186 | { 187 | DrawItem (menuItems[itemCnt], rect); 188 | if (close) break; 189 | } 190 | GUI.EndGroup (); 191 | 192 | return inRect; 193 | } 194 | 195 | private void DrawItem (MenuItem item, Rect groupRect) 196 | { 197 | if (item.separator) 198 | { 199 | if (Event.current.type == EventType.Repaint) 200 | RTEditorGUI.Seperator (new Rect (backgroundStyle.contentOffset.x+1, currentItemHeight+1, groupRect.width-2, 1)); 201 | currentItemHeight += 3; 202 | } 203 | else 204 | { 205 | Rect labelRect = new Rect (backgroundStyle.contentOffset.x, currentItemHeight, groupRect.width, itemHeight); 206 | 207 | bool selected = selectedPath.Contains (item.path); 208 | if (labelRect.Contains (Event.current.mousePosition)) 209 | { 210 | selectedPath = item.path; 211 | selected = true; 212 | } 213 | 214 | GUI.Label (labelRect, item.content, selected? selectedLabel : GUI.skin.label); 215 | 216 | if (item.group) 217 | { 218 | GUI.DrawTexture (new Rect (labelRect.x+labelRect.width-12, labelRect.y+(labelRect.height-12)/2, 12, 12), expandRight); 219 | if (selected) 220 | { 221 | item.groupPos = new Rect (groupRect.x+groupRect.width+4, groupRect.y+currentItemHeight-2, 0, 0); 222 | groupToDraw = item; 223 | } 224 | } 225 | else if (selected && (Event.current.type == EventType.MouseDown || (Event.current.button != 1 && Event.current.type == EventType.MouseUp))) 226 | { 227 | item.Execute (); 228 | close = true; 229 | Event.current.Use (); 230 | } 231 | 232 | currentItemHeight += itemHeight; 233 | } 234 | } 235 | 236 | private static Rect extendRect (Rect rect, Vector2 extendValue) 237 | { 238 | rect.x -= extendValue.x; 239 | rect.y -= extendValue.y; 240 | rect.width += extendValue.x+extendValue.x; 241 | rect.height += extendValue.y+extendValue.y; 242 | return rect; 243 | } 244 | 245 | private static Rect calculateRect (Vector2 position, List menuItems) 246 | { 247 | Vector2 size; 248 | float width = 40, height = 0; 249 | 250 | for (int itemCnt = 0; itemCnt < menuItems.Count; itemCnt++) 251 | { 252 | MenuItem item = menuItems[itemCnt]; 253 | if (item.separator) 254 | height += 3; 255 | else 256 | { 257 | width = Mathf.Max (width, GUI.skin.label.CalcSize (item.content).x + (item.group? 22 : 10)); 258 | height += itemHeight; 259 | } 260 | } 261 | 262 | size = new Vector2 (width, height); 263 | bool down = (position.y+size.y) <= Screen.height; 264 | return new Rect (position.x, position.y - (down? 0 : size.y), size.x, size.y); 265 | } 266 | 267 | #endregion 268 | 269 | #region Nested MenuItem 270 | 271 | public class MenuItem 272 | { 273 | public string path; 274 | // -!Separator 275 | public GUIContent content; 276 | // -Executable Item 277 | public MenuFunction func; 278 | public MenuFunctionData funcData; 279 | public object userData; 280 | // -Non-executables 281 | public bool separator = false; 282 | // --Group 283 | public bool group = false; 284 | public Rect groupPos; 285 | public List subItems; 286 | 287 | public MenuItem () 288 | { 289 | separator = true; 290 | } 291 | 292 | public MenuItem (string _path, GUIContent _content, bool _group) 293 | { 294 | path = _path; 295 | content = _content; 296 | group = _group; 297 | 298 | if (group) 299 | subItems = new List (); 300 | } 301 | 302 | public MenuItem (string _path, GUIContent _content, MenuFunction _func) 303 | { 304 | path = _path; 305 | content = _content; 306 | func = _func; 307 | } 308 | 309 | public MenuItem (string _path, GUIContent _content, MenuFunctionData _func, object _userData) 310 | { 311 | path = _path; 312 | content = _content; 313 | funcData = _func; 314 | userData = _userData; 315 | } 316 | 317 | public void Execute () 318 | { 319 | if (funcData != null) 320 | funcData (userData); 321 | else if (func != null) 322 | func (); 323 | } 324 | } 325 | 326 | #endregion 327 | } 328 | 329 | /// 330 | /// Generic Menu which mimics UnityEditor.GenericMenu class pretty much exactly. Wrapper for the generic PopupMenu. 331 | /// 332 | public class GenericMenu 333 | { 334 | private static PopupMenu popup; 335 | 336 | public GenericMenu () 337 | { 338 | popup = new PopupMenu (); 339 | } 340 | 341 | public void ShowAsContext () 342 | { 343 | popup.Show (new Rect (Event.current.mousePosition.x, Event.current.mousePosition.y, 0f, 0f)); 344 | } 345 | 346 | public void AddItem (GUIContent content, bool on, PopupMenu.MenuFunctionData func, object userData) 347 | { 348 | popup.AddItem (content, on, func, userData); 349 | } 350 | 351 | public void AddItem (GUIContent content, bool on, PopupMenu.MenuFunction func) 352 | { 353 | popup.AddItem (content, on, func); 354 | } 355 | 356 | public void AddSeparator (string path) 357 | { 358 | popup.AddSeparator (path); 359 | } 360 | } 361 | } -------------------------------------------------------------------------------- /Node_Editor/Utilities/RTEditorGUI.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | 7 | using Object = UnityEngine.Object; 8 | 9 | namespace NodeEditorFramework.Utilities 10 | { 11 | public static class RTEditorGUI 12 | { 13 | /// 14 | /// Float Field with label for ingame purposes. Behaves like UnityEditor.EditorGUILayout.TextField 15 | /// 16 | public static string TextField (GUIContent label, string text) 17 | { 18 | #if UNITY_EDITOR 19 | if (!Application.isPlaying) 20 | return UnityEditor.EditorGUILayout.TextField (label, text); 21 | #endif 22 | GUILayout.BeginHorizontal (); 23 | GUILayout.Label (label, label != GUIContent.none? GUILayout.ExpandWidth (true) : GUILayout.ExpandWidth (false)); 24 | text = GUILayout.TextField (text); 25 | GUILayout.EndHorizontal (); 26 | return text; 27 | } 28 | 29 | #region Slider Extended 30 | 31 | /// 32 | /// A slider that emulates the EditorGUILayout version. 33 | /// HorizontalSlider with an additional float field thereafter. 34 | /// 35 | public static float Slider (float value, float minValue, float maxValue, params GUILayoutOption[] sliderOptions) 36 | { 37 | #if UNITY_EDITOR 38 | if (!Application.isPlaying) 39 | return UnityEditor.EditorGUILayout.Slider (value, minValue, maxValue, sliderOptions); 40 | #endif 41 | return Slider (GUIContent.none, value, minValue, maxValue, sliderOptions); 42 | } 43 | 44 | /// 45 | /// A slider that emulates the EditorGUILayout version. 46 | /// HorizontalSlider with a label prefixed and an additional float field thereafter if desired. 47 | /// 48 | public static float Slider (GUIContent label, float value, float minValue, float maxValue, params GUILayoutOption[] sliderOptions) 49 | { 50 | #if UNITY_EDITOR 51 | if (!Application.isPlaying) 52 | return UnityEditor.EditorGUILayout.Slider (label, value, minValue, maxValue, sliderOptions); 53 | #endif 54 | GUILayout.BeginHorizontal (); 55 | if (label != GUIContent.none) 56 | GUILayout.Label (label, GUILayout.ExpandWidth (true)); 57 | value = GUILayout.HorizontalSlider (value, minValue, maxValue, sliderOptions); 58 | value = Mathf.Min (maxValue, Mathf.Max (minValue, FloatField (value))); 59 | GUILayout.EndHorizontal (); 60 | return value; 61 | } 62 | 63 | /// 64 | /// An integer slider that emulates the EditorGUILayout version. 65 | /// HorizontalSlider with a label prefixed and an additional int field thereafter if desired. 66 | /// 67 | public static int IntSlider (GUIContent label, int value, int minValue, int maxValue, params GUILayoutOption[] sliderOptions) 68 | { 69 | return (int)Slider (label, value, minValue, maxValue, sliderOptions); 70 | } 71 | 72 | /// 73 | /// An integer slider that emulates the EditorGUILayout version. 74 | /// HorizontalSlider with a label prefixed and an additional int field thereafter if desired. 75 | /// 76 | public static int IntSlider (int value, int minValue, int maxValue, params GUILayoutOption[] sliderOptions) 77 | { 78 | return (int)Slider (GUIContent.none, value, minValue, maxValue, sliderOptions); 79 | } 80 | 81 | #endregion 82 | 83 | #region FloatField 84 | 85 | private static int activeFloatField = -1; 86 | private static float activeFloatFieldLastValue = 0; 87 | private static string activeFloatFieldString = ""; 88 | 89 | /// 90 | /// Float Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.FloatField, besides the label slide field 91 | /// 92 | public static float FloatField (GUIContent label, float value, params GUILayoutOption[] fieldOptions) 93 | { 94 | GUILayout.BeginHorizontal (); 95 | if (label != GUIContent.none) 96 | GUILayout.Label (label, GUILayout.ExpandWidth (true)); 97 | value = FloatField (value, fieldOptions); 98 | GUILayout.EndHorizontal (); 99 | return value; 100 | } 101 | 102 | /// 103 | /// Float Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.FloatField 104 | /// 105 | public static float FloatField (float value, params GUILayoutOption[] fieldOptions) 106 | { 107 | // Get rect and control for this float field for identification 108 | if (fieldOptions.Length == 0) 109 | fieldOptions = new GUILayoutOption[] { GUILayout.ExpandWidth (false), GUILayout.MinWidth (50) }; 110 | Rect pos = GUILayoutUtility.GetRect (new GUIContent (value.ToString ()), GUI.skin.label, fieldOptions); 111 | 112 | int floatFieldID = GUIUtility.GetControlID ("FloatField".GetHashCode (), FocusType.Keyboard, pos) + 1; 113 | if (floatFieldID == 0) 114 | return value; 115 | 116 | bool recorded = activeFloatField == floatFieldID; 117 | bool active = floatFieldID == GUIUtility.keyboardControl; 118 | 119 | if (active && recorded && activeFloatFieldLastValue != value) 120 | { // Value has been modified externally 121 | activeFloatFieldLastValue = value; 122 | activeFloatFieldString = value.ToString (); 123 | } 124 | 125 | // Get stored string for the text field if this one is recorded 126 | string str = recorded? activeFloatFieldString : value.ToString (); 127 | 128 | string strValue = GUI.TextField (pos, str); 129 | if (recorded) 130 | activeFloatFieldString = strValue; 131 | 132 | // Try Parse if value got changed. If the string could not be parsed, ignore it and keep last value 133 | bool parsed = true; 134 | if (strValue == "") 135 | value = activeFloatFieldLastValue = 0; 136 | else if (strValue != value.ToString ()) 137 | { 138 | float newValue; 139 | parsed = float.TryParse (strValue, out newValue); 140 | if (parsed) 141 | value = activeFloatFieldLastValue = newValue; 142 | } 143 | 144 | if (active && !recorded) 145 | { // Gained focus this frame 146 | activeFloatField = floatFieldID; 147 | activeFloatFieldString = strValue; 148 | activeFloatFieldLastValue = value; 149 | } 150 | else if (!active && recorded) 151 | { // Lost focus this frame 152 | activeFloatField = -1; 153 | if (!parsed) 154 | value = strValue.ForceParse (); 155 | } 156 | 157 | return value; 158 | } 159 | 160 | /// 161 | /// Forces to parse to float by cleaning string if necessary 162 | /// 163 | public static float ForceParse (this string str) 164 | { 165 | // try parse 166 | float value; 167 | if (float.TryParse (str, out value)) 168 | return value; 169 | 170 | // Clean string if it could not be parsed 171 | bool recordedDecimalPoint = false; 172 | List strVal = new List (str); 173 | for (int cnt = 0; cnt < strVal.Count; cnt++) 174 | { 175 | UnicodeCategory type = CharUnicodeInfo.GetUnicodeCategory (str[cnt]); 176 | if (type != UnicodeCategory.DecimalDigitNumber) 177 | { 178 | strVal.RemoveRange (cnt, strVal.Count-cnt); 179 | break; 180 | } 181 | else if (str[cnt] == '.') 182 | { 183 | if (recordedDecimalPoint) 184 | { 185 | strVal.RemoveRange (cnt, strVal.Count-cnt); 186 | break; 187 | } 188 | recordedDecimalPoint = true; 189 | } 190 | } 191 | 192 | // Parse again 193 | if (strVal.Count == 0) 194 | return 0; 195 | str = new string (strVal.ToArray ()); 196 | if (!float.TryParse (str, out value)) 197 | Debug.LogError ("Could not parse " + str); 198 | return value; 199 | } 200 | 201 | #endregion 202 | 203 | #region ObjectField 204 | 205 | /// 206 | /// Provides an object field both for editor (using default) and for runtime (not yet implemented other that displaying object) 207 | /// 208 | public static T ObjectField (T obj, bool allowSceneObjects) where T : Object 209 | { 210 | return ObjectField (GUIContent.none, obj, allowSceneObjects); 211 | } 212 | 213 | /// 214 | /// Provides an object field both for editor (using default) and for runtime (not yet implemented other that displaying object) 215 | /// 216 | public static T ObjectField (GUIContent label, T obj, bool allowSceneObjects) where T : Object 217 | { 218 | #if UNITY_EDITOR 219 | if (!Application.isPlaying) 220 | return UnityEditor.EditorGUILayout.ObjectField (GUIContent.none, obj, typeof (T), allowSceneObjects) as T; 221 | #endif 222 | throw new System.NotImplementedException (); 223 | // if (Application.isPlaying) 224 | // { 225 | // bool open = false; 226 | // if (typeof(T).Name == "UnityEngine.Texture2D") 227 | // { 228 | // label.image = obj as Texture2D; 229 | // GUIStyle style = new GUIStyle (GUI.skin.box); 230 | // style.imagePosition = ImagePosition.ImageAbove; 231 | // open = GUILayout.Button (label, style); 232 | // } 233 | // else 234 | // { 235 | // GUIStyle style = new GUIStyle (GUI.skin.box); 236 | // open = GUILayout.Button (label, style); 237 | // } 238 | // if (open) 239 | // { 240 | // Debug.Log ("Selecting Object!"); 241 | // } 242 | // } 243 | // return obj; 244 | } 245 | 246 | #endregion 247 | 248 | #region Popups 249 | 250 | // TODO: Implement RT Popup 251 | 252 | public static System.Enum EnumPopup (GUIContent label, System.Enum selected) 253 | { 254 | #if UNITY_EDITOR 255 | selected = UnityEditor.EditorGUILayout.EnumPopup (label, selected); 256 | #else 257 | label.text += ": " + selected.ToString (); 258 | GUILayout.Label (label); 259 | #endif 260 | return selected; 261 | } 262 | 263 | public static System.Enum EnumPopup (string label, System.Enum selected) 264 | { 265 | #if UNITY_EDITOR 266 | selected = UnityEditor.EditorGUILayout.EnumPopup (label, selected); 267 | #else 268 | GUILayout.Label (label + ": " + selected.ToString ()); 269 | #endif 270 | return selected; 271 | } 272 | 273 | public static System.Enum EnumPopup (System.Enum selected) 274 | { 275 | #if UNITY_EDITOR 276 | selected = UnityEditor.EditorGUILayout.EnumPopup (selected); 277 | #else 278 | GUILayout.Label (selected.ToString ()); 279 | #endif 280 | return selected; 281 | } 282 | 283 | public static int Popup (GUIContent label, int selected, string[] displayedOptions) 284 | { 285 | GUILayout.BeginHorizontal (); 286 | #if UNITY_EDITOR 287 | GUILayout.Label (label); 288 | selected = UnityEditor.EditorGUILayout.Popup (selected, displayedOptions); 289 | #else 290 | label.text += ": " + selected.ToString (); 291 | GUILayout.Label (label); 292 | #endif 293 | GUILayout.EndHorizontal (); 294 | return selected; 295 | } 296 | 297 | public static int Popup (string label, int selected, string[] displayedOptions) 298 | { 299 | #if UNITY_EDITOR 300 | selected = UnityEditor.EditorGUILayout.Popup (label, selected, displayedOptions); 301 | #else 302 | GUILayout.Label (label + ": " + selected.ToString ()); 303 | #endif 304 | return selected; 305 | } 306 | 307 | public static int Popup (int selected, string[] displayedOptions) 308 | { 309 | #if UNITY_EDITOR 310 | selected = UnityEditor.EditorGUILayout.Popup (selected, displayedOptions); 311 | #else 312 | GUILayout.Label (selected.ToString ()); 313 | #endif 314 | return selected; 315 | } 316 | 317 | #endregion 318 | 319 | #region Seperator 320 | 321 | /// 322 | /// A GUI Function which simulates the default seperator 323 | /// 324 | public static void Seperator () 325 | { 326 | setupSeperator (); 327 | GUILayout.Box (GUIContent.none, seperator, new GUILayoutOption[] { GUILayout.Height (1) }); 328 | } 329 | 330 | /// 331 | /// A GUI Function which simulates the default seperator 332 | /// 333 | public static void Seperator (Rect rect) 334 | { 335 | setupSeperator (); 336 | GUI.Box (new Rect (rect.x, rect.y, rect.width, 1), GUIContent.none, seperator); 337 | } 338 | 339 | private static GUIStyle seperator; 340 | private static void setupSeperator () 341 | { 342 | if (seperator == null) 343 | { 344 | seperator = new GUIStyle(); 345 | seperator.normal.background = ColorToTex (1, new Color (0.6f, 0.6f, 0.6f)); 346 | seperator.stretchWidth = true; 347 | seperator.margin = new RectOffset(0, 0, 7, 7); 348 | } 349 | } 350 | 351 | #endregion 352 | 353 | #region Low-Level Drawing 354 | 355 | private static Material lineMaterial; 356 | private static Texture2D lineTexture; 357 | 358 | private static void SetupLineMat (Texture tex, Color col) 359 | { 360 | if (lineMaterial == null) 361 | lineMaterial = new Material (Shader.Find ("Hidden/LineShader")); 362 | if (tex == null) 363 | tex = lineTexture != null? lineTexture : lineTexture = NodeEditorFramework.Utilities.ResourceManager.LoadTexture ("Textures/AALine.png"); 364 | lineMaterial.SetTexture ("_LineTexture", tex); 365 | lineMaterial.SetColor ("_LineColor", col); 366 | lineMaterial.SetPass (0); 367 | } 368 | 369 | /// 370 | /// Draws a Bezier curve just as UnityEditor.Handles.DrawBezier, non-clipped, with width of 1 371 | /// 372 | public static void DrawBezier (Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan, Color col) 373 | { 374 | if (Event.current.type != EventType.Repaint) 375 | return; 376 | 377 | if (!Application.isPlaying) 378 | { 379 | #if UNITY_EDITOR 380 | UnityEditor.Handles.DrawBezier (startPos, endPos, startTan, endTan, col, null, 1); 381 | return; 382 | #endif 383 | } 384 | 385 | // Own Bezier Formula; Slower than handles because of the horrendous amount of calls into the native api 386 | // Setup: 387 | GL.Begin (GL.LINES); 388 | GL.Color (col); 389 | // Calculate optimal segment count: 390 | int segmentCount = CalculateBezierSegmentCount (startPos, endPos, startTan, endTan); 391 | // Caluclate and draw segments: 392 | Vector2 curPoint = startPos; 393 | for (int segCnt = 1; segCnt <= segmentCount; segCnt++) 394 | { 395 | Vector2 nextPoint = GetBezierPoint ((float)segCnt/segmentCount, startPos, endPos, startTan, endTan); 396 | GL.Vertex (curPoint); 397 | GL.Vertex (nextPoint); 398 | curPoint = nextPoint; 399 | } 400 | // Finish loop and finalize drawing 401 | GL.Vertex (curPoint); 402 | GL.Vertex (endPos); 403 | GL.End (); 404 | GL.Color (Color.white); 405 | } 406 | 407 | /// 408 | /// Draws a Bezier curve just as UnityEditor.Handles.DrawBezier, non-clipped. If width is 1, tex is ignored; Else if tex is null, a anti-aliased texture tinted with col will be used; else, col is ignored and tex is used. 409 | /// 410 | public static void DrawBezier (Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan, Color col, Texture2D tex, float width) 411 | { 412 | if (Event.current.type != EventType.Repaint) 413 | return; 414 | 415 | if (!Application.isPlaying) 416 | { 417 | #if UNITY_EDITOR 418 | UnityEditor.Handles.DrawBezier (startPos, endPos, startTan, endTan, col, tex, width); 419 | return; 420 | #endif 421 | } 422 | 423 | if (width == 1) 424 | { 425 | DrawBezier (startPos, endPos, startTan, endTan, col); 426 | return; 427 | } 428 | 429 | // Own Bezier Formula 430 | // Slower than handles because of the horrendous amount of calls into the native api 431 | 432 | // Aproximate Bounds and clip 433 | 434 | // Setup 435 | SetupLineMat (tex, col); 436 | GL.Begin (GL.TRIANGLE_STRIP); 437 | GL.Color (Color.white); 438 | 439 | // Calculate optimal segment count 440 | int segmentCount = CalculateBezierSegmentCount (startPos, endPos, startTan, endTan); 441 | // Caluclate and draw segments: 442 | Vector2 curPoint = startPos; 443 | for (int segCnt = 1; segCnt <= segmentCount; segCnt++) 444 | { 445 | Vector2 nextPoint = GetBezierPoint ((float)segCnt/segmentCount, startPos, endPos, startTan, endTan); 446 | DrawLineSegment (curPoint, new Vector2 (nextPoint.y-curPoint.y, curPoint.x-nextPoint.x).normalized * width/2); 447 | curPoint = nextPoint; 448 | } 449 | // Finish loop and finalize drawing 450 | DrawLineSegment (curPoint, new Vector2 (endTan.y, -endTan.x).normalized * width/2); 451 | GL.End (); 452 | GL.Color (Color.white); 453 | } 454 | 455 | /// 456 | /// Calculates the optimal bezier segment count for the given bezier 457 | /// 458 | private static int CalculateBezierSegmentCount (Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan) 459 | { 460 | float straightFactor = Vector2.Angle (startTan-startPos, endPos-startPos) * Vector2.Angle (endTan-endPos, startPos-endPos) * (endTan.magnitude+startTan.magnitude); 461 | straightFactor = 2 + Mathf.Pow (straightFactor / 400, 1.0f/8); 462 | float distanceFactor = 1 + (startPos-endPos).magnitude; 463 | distanceFactor = Mathf.Pow (distanceFactor, 1.0f/4); 464 | return 4 + (int)(straightFactor * distanceFactor); 465 | } 466 | 467 | /// 468 | /// Gets the point of the bezier at t 469 | /// 470 | public static Vector2 GetBezierPoint (float t, Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan) 471 | { 472 | return startPos * Mathf.Pow (1-t, 3) + 473 | startTan * 3 * Mathf.Pow (1-t, 2) * t + 474 | endTan * 3 * (1-t) * Mathf.Pow (t, 2) + 475 | endPos * Mathf.Pow (t, 3); 476 | } 477 | 478 | /// 479 | /// Adds a line sgement to the GL buffer. Useed in a row to create a line 480 | /// 481 | private static void DrawLineSegment (Vector2 point, Vector2 perpendicular) 482 | { 483 | Vector2 straight = new Vector2 (perpendicular.y, -perpendicular.x) * 2; 484 | GL.TexCoord2 (0, 0); 485 | GL.Vertex (point-straight - perpendicular); 486 | GL.TexCoord2 (0, 1); 487 | GL.Vertex (point-straight + perpendicular); 488 | // Showcase line segmentation 489 | // GL.TexCoord2 (0, 0); 490 | // GL.Vertex (point - perpendicular*2); 491 | // GL.TexCoord2 (0, 1); 492 | // GL.Vertex (point + perpendicular*2); 493 | // 494 | // GL.TexCoord2 (0, 0); 495 | // GL.Vertex (point+straight - perpendicular); 496 | // GL.TexCoord2 (0, 1); 497 | // GL.Vertex (point+straight + perpendicular); 498 | } 499 | 500 | /// 501 | /// Draws a non-clipped line. If tex is null, a anti-aliased texture tinted with col will be used; else, col is ignored and tex is used. 502 | /// 503 | public static void DrawLine (Vector2 startPos, Vector2 endPos, Color col, Texture2D tex, float width) 504 | { 505 | if (Event.current.type != EventType.Repaint) 506 | return; 507 | 508 | if (width <= 1) 509 | { 510 | GL.Begin (GL.LINES); 511 | GL.Color (col); 512 | GL.Vertex (startPos); 513 | GL.Vertex (endPos); 514 | GL.End (); 515 | GL.Color (Color.white); 516 | } 517 | else 518 | { 519 | SetupLineMat (tex, col); 520 | 521 | GL.Begin (GL.TRIANGLE_STRIP); 522 | GL.Color (Color.white); 523 | Vector2 perpWidthOffset = new Vector2 ((endPos-startPos).y, -(endPos-startPos).x).normalized * width / 2; 524 | DrawLineSegment (startPos, perpWidthOffset); 525 | DrawLineSegment (endPos, perpWidthOffset); 526 | GL.End (); 527 | } 528 | } 529 | 530 | #endregion 531 | 532 | #region Texture Utilities 533 | 534 | /// 535 | /// Create a 1x1 tex with color col 536 | /// 537 | public static Texture2D ColorToTex (int pxSize, Color col) 538 | { 539 | Texture2D tex = new Texture2D (pxSize, pxSize); 540 | for (int x = 0; x < pxSize; x++) 541 | for (int y = 0; y < pxSize; y++) 542 | tex.SetPixel (x, y, col); 543 | tex.Apply (); 544 | return tex; 545 | } 546 | 547 | /// 548 | /// Tint the texture with the color. It's advised to use ResourceManager.GetTintedTexture to account for doubles. 549 | /// 550 | public static Texture2D Tint (Texture2D tex, Color color) 551 | { 552 | Texture2D tintedTex = UnityEngine.Object.Instantiate (tex); 553 | for (int x = 0; x < tex.width; x++) 554 | for (int y = 0; y < tex.height; y++) 555 | tintedTex.SetPixel (x, y, tex.GetPixel (x, y) * color); 556 | tintedTex.Apply (); 557 | return tintedTex; 558 | } 559 | 560 | /// 561 | /// Rotates the texture Counter-Clockwise, 'NintyDegrSteps' specifying the times 562 | /// 563 | public static Texture2D RotateTextureCCW (Texture2D tex, int quarterSteps) 564 | { 565 | if (tex == null) 566 | return null; 567 | // Copy and setup working arrays 568 | tex = UnityEngine.Object.Instantiate (tex); 569 | int width = tex.width, height = tex.height; 570 | Color[] col = tex.GetPixels (); 571 | Color[] rotatedCol = new Color[width*height]; 572 | for (int itCnt = 0; itCnt < quarterSteps; itCnt++) 573 | { // For each iteration, perform rotation of 90 degrees 574 | for (int x = 0; x < width; x++) 575 | for (int y = 0; y < height; y++) 576 | rotatedCol[x*width + y] = col[(width-y-1) * width + x]; 577 | rotatedCol.CopyTo (col, 0); // Push rotation for next iteration 578 | } 579 | // Apply rotated working arrays 580 | tex.SetPixels (col); 581 | tex.Apply (); 582 | return tex; 583 | } 584 | 585 | #endregion 586 | } 587 | } -------------------------------------------------------------------------------- /Node_Editor/Utilities/ResourceManager.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | 6 | namespace NodeEditorFramework.Utilities 7 | { 8 | /// 9 | /// Provides methods for loading resources both at runtime and in the editor; 10 | /// Though, to load at runtime, they have to be in a resources folder 11 | /// 12 | public static class ResourceManager 13 | { 14 | 15 | private static string _ResourcePath = ""; 16 | public static void SetDefaultResourcePath (string defaultResourcePath) 17 | { 18 | _ResourcePath = defaultResourcePath; 19 | } 20 | 21 | #region Common Resource Loading 22 | 23 | /// 24 | /// Prepares the path; At Runtime, it will return path relative to Resources, in editor, it will return the assets relative to Assets. Takes any path. 25 | /// 26 | public static string PreparePath (string path) 27 | { 28 | path = path.Replace (Application.dataPath, "Assets"); 29 | if (Application.isPlaying) 30 | { // At runtime 31 | if (path.Contains ("Resources")) 32 | path = path.Substring (path.LastIndexOf ("Resources") + 10); 33 | path = path.Substring (0, path.LastIndexOf ('.')); 34 | return path; 35 | } 36 | // In the editor 37 | if (!path.StartsWith ("Assets/")) 38 | path = _ResourcePath + path; 39 | return path; 40 | } 41 | 42 | /// 43 | /// Loads a resource in the resources folder in both the editor and at runtime. 44 | /// Path can be global, relative to the assets folder or, if used at runtime only, any subfolder, but has to be in a Resource folder to be loaded at runtime 45 | /// 46 | public static T[] LoadResources (string path) where T : UnityEngine.Object 47 | { 48 | path = PreparePath (path); 49 | if (Application.isPlaying) // At runtime 50 | return UnityEngine.Resources.LoadAll (path); 51 | #if UNITY_EDITOR 52 | // In the editor 53 | return UnityEditor.AssetDatabase.LoadAllAssetsAtPath (path).Cast ().ToArray (); 54 | #else 55 | return null; 56 | #endif 57 | } 58 | 59 | /// 60 | /// Loads a resource in the resources folder in both the editor and at runtime 61 | /// Path can be global, relative to the assets folder or, if used at runtime only, any subfolder, but has to be in a Resource folder to be loaded at runtime 62 | /// 63 | public static T LoadResource (string path) where T : UnityEngine.Object 64 | { 65 | path = PreparePath (path); 66 | if (Application.isPlaying) // At runtime 67 | return UnityEngine.Resources.Load (path); 68 | #if UNITY_EDITOR 69 | // In the editor 70 | return UnityEditor.AssetDatabase.LoadAssetAtPath (path); 71 | #else 72 | return null; 73 | #endif 74 | } 75 | 76 | #endregion 77 | 78 | #region Texture Management 79 | 80 | private static List loadedTextures = new List (); 81 | 82 | /// 83 | /// Loads a texture in the resources folder in both the editor and at runtime and manages it in a memory for later use. 84 | /// If you don't wan't to optimise memory, just use LoadResource instead 85 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 86 | /// 87 | public static Texture2D LoadTexture (string texPath) 88 | { 89 | if (String.IsNullOrEmpty (texPath)) 90 | return null; 91 | int existingInd = loadedTextures.FindIndex ((MemoryTexture memTex) => memTex.path == texPath); 92 | if (existingInd != -1) 93 | { // If we have this texture in memory already, return it 94 | if (loadedTextures[existingInd].texture == null) 95 | loadedTextures.RemoveAt (existingInd); 96 | else 97 | return loadedTextures[existingInd].texture; 98 | } 99 | // Else, load up the texture and store it in memory 100 | Texture2D tex = LoadResource (texPath); 101 | AddTextureToMemory (texPath, tex); 102 | return tex; 103 | } 104 | 105 | /// 106 | /// Loads up a texture tinted with col, and manages it in a memory for later use. 107 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 108 | /// 109 | public static Texture2D GetTintedTexture (string texPath, Color col) 110 | { 111 | string texMod = "Tint:" + col.ToString (); 112 | Texture2D tintedTexture = GetTexture (texPath, texMod); 113 | if (tintedTexture == null) 114 | { // We have to create a tinted version, perhaps even load the default texture if not yet in memory, and store it 115 | tintedTexture = LoadTexture (texPath); 116 | AddTextureToMemory (texPath, tintedTexture); // Register default texture for re-use 117 | tintedTexture = NodeEditorFramework.Utilities.RTEditorGUI.Tint (tintedTexture, col); 118 | AddTextureToMemory (texPath, tintedTexture, texMod); // Register texture for re-use 119 | } 120 | return tintedTexture; 121 | } 122 | 123 | /// 124 | /// Records an additional texture for the manager memory with optional modifications 125 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 126 | /// 127 | public static void AddTextureToMemory (string texturePath, Texture2D texture, params string[] modifications) 128 | { 129 | if (texture == null) return; 130 | loadedTextures.Add (new MemoryTexture (texturePath, texture, modifications)); 131 | } 132 | 133 | /// 134 | /// Returns whether the manager memory contains the texture 135 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 136 | /// 137 | public static MemoryTexture FindInMemory (Texture2D tex) 138 | { 139 | int existingInd = loadedTextures.FindIndex ((MemoryTexture memTex) => memTex.texture == tex); 140 | return existingInd != -1? loadedTextures[existingInd] : null; 141 | } 142 | 143 | /// 144 | /// Whether the manager memory contains a texture with optional modifications 145 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 146 | /// 147 | public static bool HasInMemory (string texturePath, params string[] modifications) 148 | { 149 | int existingInd = loadedTextures.FindIndex ((MemoryTexture memTex) => memTex.path == texturePath); 150 | return existingInd != -1 && EqualModifications (loadedTextures[existingInd].modifications, modifications); 151 | } 152 | 153 | /// 154 | /// Gets a texture already in manager memory with specified modifications (check with contains before!) 155 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 156 | /// 157 | public static MemoryTexture GetMemoryTexture (string texturePath, params string[] modifications) 158 | { 159 | List textures = loadedTextures.FindAll ((MemoryTexture memTex) => memTex.path == texturePath); 160 | if (textures == null || textures.Count == 0) 161 | return null; 162 | foreach (MemoryTexture tex in textures) 163 | if (EqualModifications (tex.modifications, modifications)) 164 | return tex; 165 | return null; 166 | } 167 | 168 | /// 169 | /// Gets a texture already in manager memory with specified modifications (check with 'HasInMemory' before!) 170 | /// It's adviced to prepare the texPath using the function before to create a uniform 'path format', because textures are compared through their paths 171 | /// 172 | public static Texture2D GetTexture (string texturePath, params string[] modifications) 173 | { 174 | MemoryTexture memTex = GetMemoryTexture (texturePath, modifications); 175 | return memTex == null? null : memTex.texture; 176 | } 177 | 178 | private static bool EqualModifications (string[] modsA, string[] modsB) 179 | { 180 | return modsA.Length == modsB.Length && Array.TrueForAll (modsA, mod => modsB.Count (oMod => mod == oMod) == modsA.Count (oMod => mod == oMod)); 181 | } 182 | 183 | public class MemoryTexture 184 | { 185 | public string path; 186 | public Texture2D texture; 187 | public string[] modifications; 188 | 189 | public MemoryTexture (string texPath, Texture2D tex, params string[] mods) 190 | { 191 | path = texPath; 192 | texture = tex; 193 | modifications = mods; 194 | } 195 | } 196 | 197 | #endregion 198 | } 199 | 200 | } -------------------------------------------------------------------------------- /Node_Editor/Utilities/link.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Node Editor Framework for Unity 2 | This project aims to create an easy-to-use framework for creating graph-based displays in the Unity Editor and runtime. 3 | 4 |

5 | Node Editor Image 6 |

7 | 8 | ### Major Features 9 | - Convenient editor featuring zooming 10 | - Automatic node calculation system 11 | - Customisable drag'n'drop node connection system 12 | - StateMachine Behaviour system (WIP) 13 | - Dynamic extensions (Nodes, ConnectionTypes, NodeKnobs, ...) without touching framework code 14 | - Extensive control over Node/Knob/Connection appearance, even individually 15 | - Save/Load node canvas with auto save 16 | - Fully customisable GUI and layout 17 | - Growing runtime support 18 | - Documentation and active support:) 19 | 20 | ### Documentation and Support 21 | The documentation can be found at 'Docs/Node Editor Documentation.pdf'. If you spot any mistakes, inconsistencies, or improveable sections in the documentation, please contact [Seneral](http://forum.unity3d.com/members/seneral.638015/). You can also contact him for any type of question regarding the framework, or post on the [project thread](http://forum.unity3d.com/threads/simple-node-editor.189230/#post-2134738). 22 | 23 | ### Community and Contributing 24 | I encourage anyone in the Community willing to contribute to step up and do so! There are plenty of things to do, with everything from easy to hard, which can be browsed [here](https://github.com/Baste-RainGames/Node_Editor/issues). In most cases it might help to contact the main developer [Seneral](http://forum.unity3d.com/members/seneral.638015/) when you have any questions regarding the framework itself or any of the plans linked above. 25 | 26 | If you have any questions on how to commit, Unity made a tutorial about using git with SourceTree [here](https://unity3d.com/learn/tutorials/topics/cloud-build/creating-your-first-source-control-repository), and to create pull requests atlassian made one [here](https://www.atlassian.com/git/tutorials/making-a-pull-request/how-it-works). Don't be confused by the fact that they're using Bitucket, the same applies to GitHub. If there is an issue describing the feature or bug you implemented, you should link it from withing the pull request:) 27 | 28 | ### Credits and Licensing 29 | The project was started as a part of the thread ["Simple Node Editor"](http://forum.unity3d.com/threads/simple-node-editor.189230/#post-2134738) in may 2015 by [Seneral](http://forum.unity3d.com/members/seneral.638015/), and the repository was set up by [Baste Nesse Buanes](http://forum.unity3d.com/members/baste.185905/). You can find the full list of contributors [on this page](https://github.com/Baste-RainGames/Node_Editor/graphs/contributors). Additionally, [Vexe](http://forum.unity3d.com/members/vexe.280515/) has greatly helped with reflection related stuff. 30 | 31 | The license used is the MIT License - see Docs/license.md 32 | --------------------------------------------------------------------------------