Portfolio by Chris Huider

Visual Scripting Creator

Year made

  • 2023-Ongoing

Software

  • Unity3D

Language

  • C#

Project Duration

  • Ongoing (24 Months)

Team

  • Solo project

Links

    • No links available

Work in progress footage (Anything may be subject to change)

This video shows the workflow of making a new node blueprint by defining its fields and their names, accessing field values and programming its functionality. After that it shows how to make a new graph, loading it into a scene and navigating through the graph using a debug menu.

Description

After my internship at the KLM XR Center of Excellence I wanted to make an improved 'Macadamia', so I did. It's still a work in progress but dispite that, I'd still like to show it.
The 'Visual Scripting Creator' is a set of tools to create your own visual scripting tool. You can create the nodes for the node system yourself, which allows you to create any node system you want. You can also create scripts for each node and adjust or create your own graph manager, which will enable you to manage your graphs any way you want.

The toolset includes useful commands like undo, redo, copy, cut and paste, alongside post assembly reload persistance. It's also made to be expandable and adjustable to be able to suit anyone's needs with minimal changes or full customization.

What did I learn?

  • Learned how Unity's assembly reloads work.
  • Learned how to use USS (Unity Style Sheet).
  • Learned how to write, read and use json files.
  • Learned the basics of how Unity's serialization works.
  • Improved my knowledge of the positives and negatives of using scriptable objects.
  • Improved my knowledge of Unity's built in systems.
  • Improved my knowledge of user experience in relation to developer tools.
  • Additional Info

    The node's functionality and field values are seperated into two scripts. The node's functionality script is automatically generated when the node blueprint is saved for the first time. The node script's variables are regenerated each time the node blueprint is saved, this is to ensure all field values stay accessable and the node's functionality isn't removed after each save.

    You can find the node functionality script as it's generated below.

    This script is used to give the node its functionality. You can determine what the node does when the scene is initialized, when the node is entered, on update while inside the node and when the node is exited. (More functionality might be added in the future.)

    								
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Object = UnityEngine.Object;
    using Hephaestus.Graph_Creation_Toolbox;
    
    public class ShowcaseNodeScript : NodeScript<ShowcaseNodeVariables>, INodeScript
    {
    	public override void OnInitialize(GraphManager graphManager, NodeData data)
    	{
    		base.OnInitialize(graphManager, data);
    	}
    	
    	public override void OnEnter(GraphManager graphManager, NodeData data)
    	{
    		base.OnEnter(graphManager, data);
    	}
    	
    	public override void OnUpdate(GraphManager graphManager, NodeData data)
    	{
    		base.OnUpdate(graphManager, data);
    	}
    	
    	public override void OnExit(GraphManager graphManager, NodeData data)
    	{
    		base.OnExit(graphManager, data);
    	}
    }
    								
    							

    When making a node blueprint, you can choose between nine different field types (Enum, Float, InportPort, Int, List, OutputPort, Text, Toggle, Object). You can add your own custom fields by making a new FieldConstructor script.

    You can find the Enum field constructor script as an example below.

    When making a custom field type it's important to set a name in GetName(), set the default field value in GetDefaultValue(), how the field in the node looks and functions in ConstructNodeField() and how the field customization field looks and functions in ConstructCustomizationView().
    Nothing else needs to be done, the rest is all handled by the tool.

    								
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using Hephaestus.Tools.Visual_Elements;
    using UnityEngine;
    using UnityEngine.UIElements;
    using VisualElement = UnityEngine.UIElements.VisualElement;
    using EnumField = Hephaestus.Tools.Visual_Elements.EnumField;
    
    namespace Hephaestus.Graph_Creation_Toolbox
    {
    	[System.Serializable]
    	public class EnumFieldConstructor : FieldConstructor
    	{
    		public override string GetName()
    		{
    			return "EnumField";
    		}
    
    		public override object GetDefaultValue()
    		{
    			return Enum.GetValues(ConstantValues.Instance.EnumTypes.First()).GetValue(0) as Enum;
    		}
    		
    		public override NodeField ConstructNodeField(FieldBlueprint blueprint)
    		{
    			var nodeField = new NodeField(blueprint);
    			
    			var field = new EnumField(Enum.GetValues(Type.GetType(blueprint.GetSetting<string>("ValueTypeSignature"))).GetValue(0) as Enum);
    			field.RegisterValueChangedCallback<Enum>(change =>
    			{
    				(!THIS PIECE OF CODE IS COMMENTED OUT IN THE CURRENT WIP VERSION AND THUS REMOVED FROM THE EXAMPLE!)
    			});
    			nodeField.Add(field);
    			nodeField.InputField = field;
    
    			return nodeField;
    		}
    		
    		public override VisualElement ConstructCustomizationView(FieldBlueprint blueprint)
    		{
    			var customizationView = base.ConstructCustomizationView(blueprint);
    			
    			var field = new StringListField(ConstantValues.Instance.EnumTypeNames.ToArray());
    			field.SetValueWithoutNotify(Type.GetType(blueprint.GetSetting<string>("ValueTypeSignature")).Name);
    			field.RegisterValueChangedCallback(change =>
    			{
    				var previousEnumType = ConstantValues.Instance.EnumTypes.Find(match => match.Name == change.previousValue);
    				var newEnumType = ConstantValues.Instance.EnumTypes.Find(match => match.Name == change.newValue);
    				
    				var previousValue = new AdvancedVariable("ValueTypeSignature", $"{previousEnumType}, {previousEnumType.Assembly.FullName}");
    				var newValue = new AdvancedVariable("ValueTypeSignature", $"{newEnumType}, {newEnumType.Assembly.FullName}");
    				using (ChangeEvent<AdvancedVariable> evt = ChangeEvent<AdvancedVariable>.GetPooled(previousValue, newValue))
    				{
    					evt.target = customizationView;
    					customizationView.SendEvent(evt);
    				}
    			});
    			customizationView.Add(field);
    
    			return customizationView;
    		}
    	}
    }
    								
    							

    The advanced editor window script is the parent script for every editor window in this tool set. It gives the editor window some extra functionality and enables data reloading after assembly reloads.

    You can find the full AdvancedEditorWindow.cs script below.

    								
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor;
    using UnityEngine;
    
    namespace Hephaestus.Tools
    {
    	public class AdvancedEditorWindow : EditorWindow
    	{
    		#region Singleton
    
    		public static AdvancedEditorWindow Instance { get; private set; }
    
    		private void InitSingleton()
    		{
    			if (Instance != null && Instance != this)
    			{
    				Destroy(this);
    			}
    			else
    			{
    				Instance = this;
    			}
    		}
    
    		#endregion
    
    		//Private variables
    		private bool _initialized;
    
    		//Overriding protected value to public
    		public void SetUnsavedChanges(bool value)
    		{
    			hasUnsavedChanges = value;
    		}
    
    		/// <summary>
    		/// Create a new editor window of type TWindowType
    		/// </summary>
    		/// <param name="title">Window name.</param>
    		/// <param name="saveChangesMessage">Popup message when editor window is closed with unsaved changes.</param>
    		/// <typeparam name="TWindowType">Window type (Child type of ExtendedEditorWindow).</typeparam>
    		protected static void CreateWindow<TWindowType>(string title,
    			string saveChangesMessage = "This window has unsaved changes. Would you like to save?")
    			where TWindowType : AdvancedEditorWindow
    		{
    			var window = GetWindow<TWindowType>();
    			window.titleContent = new GUIContent(title);
    			window.OnWindowOpen();
    			window.saveChangesMessage = saveChangesMessage;
    		}
    		
    		/// <summary>
    		/// Called on window loaded.
    		/// </summary>
    		protected virtual void OnEnable()
    		{
    			LayoutUI();
    
    			Application.quitting += HandleQuitting;
    			UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += BeforeAssemblyReload;
    			UnityEditor.AssemblyReloadEvents.afterAssemblyReload += AfterAssemblyReload;
    			if (_initialized) DuringAssemblyReload();
    
    			if (!_initialized)
    			{
    				InitSingleton();
    				Initialize();
    				_initialized = true;
    			}
    		}
    
    		/// <summary>
    		/// Called on window initialization.
    		/// </summary>
    		protected virtual void Initialize()
    		{
    		}
    
    		/// <summary>
    		/// Called on window opened.
    		/// </summary>
    		protected virtual void OnWindowOpen()
    		{
    		}
    
    		/// <summary>
    		/// Called on window closed.
    		/// </summary>
    		protected virtual void OnWindowClose()
    		{
    			rootVisualElement.Clear();
    			Instance = null;
    			UnityEditor.AssemblyReloadEvents.beforeAssemblyReload -= BeforeAssemblyReload;
    			UnityEditor.AssemblyReloadEvents.afterAssemblyReload -= AfterAssemblyReload;
    		}
    
    		/// <summary>
    		/// Called before assembly reload.
    		/// </summary>
    		protected virtual void BeforeAssemblyReload()
    		{
    		}
    
    		/// <summary>
    		/// Called during assembly reload.
    		/// </summary>
    		protected virtual void DuringAssemblyReload()
    		{
    		}
    
    		/// <summary>
    		/// Called after assembly reload.
    		/// </summary>
    		protected virtual void AfterAssemblyReload()
    		{
    		}
    
    		/// <summary>
    		/// Called on window disabled.
    		/// </summary>
    		protected virtual void OnDisable()
    		{
    			Application.quitting -= HandleQuitting;
    		}
    
    		/// <summary>
    		/// Called on window destroyed.
    		/// </summary>
    		protected virtual void OnDestroy()
    		{
    			OnWindowClose();
    		}
    
    		/// <summary>
    		/// Save unsaved changes.
    		/// </summary>
    		public override void SaveChanges()
    		{
    			base.SaveChanges();
    		}
    
    		/// <summary>
    		/// Discard unsaved changes.
    		/// </summary>
    		public override void DiscardChanges()
    		{
    			base.DiscardChanges();
    		}
    
    		/// <summary>
    		/// Called upon quitting Unity.
    		/// </summary>
    		protected virtual void HandleQuitting()
    		{
    			if (hasUnsavedChanges) SaveChanges();
    		}
    
    		/// <summary>
    		/// Create user interface.
    		/// </summary>
    		protected virtual void LayoutUI()
    		{
    		}
    	}
    }
    								
    							

    The node blueprint window is the editor window for creating custom nodes. You can visually click together how you want your nodes to look like. It also inherits from AdvancedEditorWindow.cs and implements the assembly reload window reload.
    One of the main issues 'Macadamia' had was that the editor window reset when Unity did it's assembly reload (after for example changing a script). To fix this, I added window reloading after the assembly reload. This reloads the user interface and back-end data in the window making the window stay the same after assembly reloads.

    You can find the full NodeBlueprintWindow.cs script below.

    								
    using System;
    using System.Collections;
    using Hephaestus.Tools;
    using Hephaestus.Tools.Visual_Elements;
    using UnityEditor;
    using UnityEditor.UIElements;
    using UnityEngine;
    using UnityEngine.UIElements;
    using PopupWindow = UnityEditor.PopupWindow;
    
    namespace Hephaestus.Graph_Creation_Toolbox
    {
    	public class NodeBlueprintWindow : AdvancedEditorWindow
    	{
    		//Global variables
    		private string _currentNodeBlueprintName;
    		public string CurrentNodeBlueprintName
    		{
    			get => _currentNodeBlueprintName;
    			set
    			{
    				_currentNodeBlueprintName = value;
    				_loadedNodeBlueprintLabel.text = $"Current Node Blueprint: <b><i>{value}<b><i>";
    			}
    		}
    		
    		public string LoadedNodeBlueprintPath => $"{ExplorerPaths.SAVED_NODES_FOLDER_PATH}/{CurrentNodeBlueprintName}{ExplorerPaths.SAVE_FORMAT}";
    		
    		//Undo and redo stacks
    		public Stack Changes = new Stack();
    		public Stack UndidChanges = new Stack();
    		
    		
    		//Private variables
    		public EditorNode EditorNode;
    		private NodeBlueprint _assemblyReloadData;
    		
    		//Visual Elements
    		private GraphView _graphView;
    		
    		private Toolbar _toolbar;
    		private Label _loadedNodeBlueprintLabel;
    		private Button _loadNodeBlueprintButton;
    		private Button _saveNodeBlueprintButton;
    		private Button _saveNodeBlueprintAsButton;
    		private Button _clearNodeBlueprintButton;
    		
    		private Button _undoButton;
    		private Button _redoButton;
    		
    		private VisualElement _zoneListContainer;
    		public ZoneBlueprintElement InputZone;
    		public ZoneBlueprintElement OutputZone;
    		public ZoneBlueprintElement ExtensionZone;
    		
    		//Events
    		public event Action OnNodeBlueprintLoaded;
    		public event Action OnNodeBlueprintSaved;
    		public event Action OnNodeBlueprintSavedAs;
    		public event Action OnNodeBlueprintCleared;
    		
    		#region Window
    		[MenuItem("Graph Creation Toolbox/Node Blueprinting")]
    		public static void CreateGraphViewWindow()
    		{
    			CreateWindow<NodeBlueprintWindow>(
    				"Node Blueprinting", 
    				"This node blueprint has unsaved changes. Would you like to save?"
    				);
    		}
    		
    		protected override void OnEnable()
    		{
    			ConstantValues.CreateInstance();
    			base.OnEnable();
    		}
    		
    		protected override void OnWindowClose()
    		{
    			base.OnWindowClose();
    			OnNodeBlueprintLoaded = null;
    			OnNodeBlueprintSaved = null;
    			OnNodeBlueprintSavedAs = null;
    			OnNodeBlueprintCleared = null;
    			ConstantValues.DestroyInstance();
    		}
    		
    		protected override void BeforeAssemblyReload()
    		{
    			base.BeforeAssemblyReload();
    			_assemblyReloadData = this.GenerateNodeBlueprint();
    		}
    		
    		protected override void DuringAssemblyReload()
    		{
    			base.DuringAssemblyReload();
    		}
    		
    		protected override void AfterAssemblyReload()
    		{
    			ConstantValues.CreateInstance();
    			base.AfterAssemblyReload();
    			this.LoadNodeBlueprint(_assemblyReloadData);
    			_assemblyReloadData = null;
    		}
    		
    		public override void SaveChanges()
    		{
    			base.SaveChanges();
    			SaveButtonClicked();
    		}
    		
    		public override void DiscardChanges()
    		{
    			base.DiscardChanges();
    		}
    		
    		protected override void LayoutUI()
    		{
    			base.LayoutUI();
    			
    			//Create toolbar
    			LayoutToolbar();
    			
    			//Create zone definition lists
    			_zoneListContainer = new ColumnElement();
    			_zoneListContainer.BringToFront();
    			_zoneListContainer.style.backgroundColor = new StyleColor(new Color(0.2196f, 0.2196f, 0.2196f, 1));
    			_zoneListContainer.style.width = 210;
    			rootVisualElement.Add(_zoneListContainer);
    			
    			InitZoneLists();
    			
    			_zoneListContainer.Add(InputZone);
    			_zoneListContainer.Add(OutputZone);
    			_zoneListContainer.Add(ExtensionZone);
    			
    			//Create graph
    			LayoutGraphView();
    		}
    		#endregion
    		
    		#region Toolbar
    		/// <summary>
    		/// Layout toolbar user interface.
    		/// </summary>
    		private void LayoutToolbar()
    		{
    			//Layout Toolbar
    			_toolbar = new Toolbar() { style = { paddingTop = 2, paddingBottom = 2, minHeight = 26 } };
    			rootVisualElement.Add(_toolbar);
    			
    			//Layout current graph Label
    			_loadedNodeBlueprintLabel = new Label { style = { alignSelf = Align.Center, marginRight = 10 } };
    			_toolbar.Add(_loadedNodeBlueprintLabel);
    			CurrentNodeBlueprintName = "New Node Blueprint";
    			
    			//Layout load Button
    			_loadNodeBlueprintButton = new Button { text = "Load" };
    			_toolbar.Add(_loadNodeBlueprintButton);
    			_loadNodeBlueprintButton.clicked += LoadButtonClicked;
    			
    			//Layout save Button
    			_saveNodeBlueprintButton = new Button { text = "Save" };
    			_toolbar.Add(_saveNodeBlueprintButton);
    			_saveNodeBlueprintButton.clicked += SaveButtonClicked;
    			
    			//Layout save as Button
    			_saveNodeBlueprintAsButton = new Button { text = "Save As" };
    			_toolbar.Add(_saveNodeBlueprintAsButton);
    			_saveNodeBlueprintAsButton.clicked += SaveAsButtonClicked;
    		
    			//Layout clear Button
    			_clearNodeBlueprintButton = new Button { text = "Clear" };
    			_toolbar.Add(_clearNodeBlueprintButton);
    			_clearNodeBlueprintButton.clicked += ClearButtonClicked;
    			
    			//Undo change Button
    			_undoButton = new Button { text = "Undo" };
    			_toolbar.Add(_undoButton);
    			_undoButton.clicked += UndoButtonClicked;
    			
    			//Redo change Button
    			_redoButton = new Button { text = "Redo" };
    			_toolbar.Add(_redoButton);
    			_redoButton.clicked += RedoButtonClicked;
    		}
    		
    		/// <summary>
    		/// Called when load button is clicked.
    		/// </summary>
    		private void LoadButtonClicked()
    		{
    			PopupWindow.Show(_loadNodeBlueprintButton.worldBound, new LoadNodePopupContent(this, () =>
    			{
    				OnNodeBlueprintLoaded?.Invoke();
    				Changes.Clear();
    				UndidChanges.Clear();
    				SetUnsavedChanges(false);
    			}));
    		}
    		
    		/// <summary>
    		/// Called when save button is clicked.
    		/// </summary>
    		private void SaveButtonClicked()
    		{
    			this.SaveNodeBlueprint();
    			OnNodeBlueprintSaved?.Invoke();
    			SetUnsavedChanges(false);
    		}
    		
    		/// <summary>
    		/// Called when save as button is clicked.
    		/// </summary>
    		private void SaveAsButtonClicked()
    		{
    			PopupWindow.Show(_saveNodeBlueprintAsButton.worldBound, new SaveNodeAsPopupContent(this, () =>
    			{
    				OnNodeBlueprintSavedAs?.Invoke();
    				SetUnsavedChanges(false);
    			}));
    		}
    		
    		/// <summary>
    		/// Called when clear button is clicked.
    		/// </summary>
    		private void ClearButtonClicked()
    		{
    			var inputZoneClearedElements = InputZone.GetElements();
    			var outputZoneClearedElements = OutputZone.GetElements();
    			var extensionZoneClearedElements = ExtensionZone.GetElements();
    
    			var change = new BlueprintingWindowClearedChange(InputZone, OutputZone, ExtensionZone,
    				inputZoneClearedElements, outputZoneClearedElements, extensionZoneClearedElements);
    			
    			this.Clear();
    			OnNodeBlueprintCleared?.Invoke();
    			ZoneChange(change);
    		}
    
    		/// <summary>
    		/// Called when undo button is clicked.
    		/// </summary>
    		private void UndoButtonClicked()
    		{
    			var change = Changes.Pop() as IWindowChange;
    			change.UndoChange();
    			UndidChanges.Push(change);
    			SyncNodeBlueprint();
    		}
    
    		/// <summary>
    		/// Called when redo button is clicked.
    		/// </summary>
    		private void RedoButtonClicked()
    		{
    			var change = UndidChanges.Pop() as IWindowChange;
    			change.RedoChange();
    			Changes.Push(change);
    			SyncNodeBlueprint();
    		}
    		#endregion
    		
    		#region GraphView
    		/// <summary>
    		/// Layout graph user interface.
    		/// </summary>
    		private void LayoutGraphView()
    		{
    			_graphView = new GraphView() { name = "Node Example Graph" };
    			rootVisualElement.Add(_graphView);
    			_graphView.StretchToParentSize();
    			_graphView.SendToBack();
    			
    			EditorNode = new EditorNode().Create(CreateInstance<NodeBlueprint>(), _graphView, new Vector2(275, 75)) as EditorNode;
    		}
    		#endregion
    		
    		#region User Interface
    		/// <summary>
    		/// Initialize zone definition lists.
    		/// </summary>
    		private void InitZoneLists()
    		{
    			InputZone = new ZoneBlueprintElement("Input Zone");
    			OutputZone = new ZoneBlueprintElement("Output Zone");
    			ExtensionZone = new ZoneBlueprintElement("Extension Zone");
    			
    			InputZone.style.paddingTop = 5;
    			OutputZone.style.paddingTop = 10;
    			ExtensionZone.style.paddingTop = 10;
    			
    			InputZone.OnValueChangedEvent += change =>
    			{                                                                                                                  
    				EditorNode.GetBlueprint().inputZoneBlueprint = InputZone.ZoneBlueprint;
    				ZoneChange(change);
    			};
    			OutputZone.OnValueChangedEvent += change =>
    			{
    				EditorNode.GetBlueprint().outputZoneBlueprint = OutputZone.ZoneBlueprint;
    				ZoneChange(change);
    			};
    			ExtensionZone.OnValueChangedEvent += change =>
    			{
    				EditorNode.GetBlueprint().extensionZoneBlueprint = ExtensionZone.ZoneBlueprint;
    				ZoneChange(change);
    			};
    		}
    
    		/// <summary>
    		/// Handle change in Node's zone.
    		/// </summary>
    		/// <param name="change"></param>
    		private void ZoneChange(IWindowChange change)
    		{
    			EditorNode.RefreshFields();
    			Changes.Push(change);
    			UndidChanges.Clear();
    			SetUnsavedChanges(true);
    		}
    
    		/// <summary>
    		/// Sync Editor Node with Blueprint defined in UI.
    		/// </summary>
    		public void SyncNodeBlueprint()
    		{
    			EditorNode.GetBlueprint().inputZoneBlueprint = InputZone.ZoneBlueprint;
    			EditorNode.GetBlueprint().outputZoneBlueprint = OutputZone.ZoneBlueprint;
    			EditorNode.GetBlueprint().extensionZoneBlueprint = ExtensionZone.ZoneBlueprint;
    			EditorNode.RefreshFields();
    		}
    		#endregion
    	}
    }
    								
    							

    The node blueprint save utility is called upon to save, load and clear the node blueprint window.

    You can find the full NodeBlueprintSaveUtility.cs script below.

    								
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor;
    using UnityEngine;
    
    namespace Hephaestus.Graph_Creation_Toolbox
    {
    	public static class NodeBlueprintSaveUtility
    	{
    		#region Saving
    		/// <summary>
    		/// Save Node Blueprint
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to save</param>
    		public static void SaveNodeBlueprint(this NodeBlueprintWindow nodeBlueprintWindow)
    		{
    			//Check if previous save exists.
    			bool saveExists = AssetDatabase.LoadAssetAtPath<NodeBlueprint>(nodeBlueprintWindow.LoadedNodeBlueprintPath);
    			
    			//Generate new blueprint to save.
    			NodeBlueprint newNodeBlueprint = nodeBlueprintWindow.GenerateNodeBlueprint();
    			
    			if (saveExists)
    			{
    				//Overwrite previous save.
    				var savedAsset = AssetDatabase.LoadAssetAtPath<NodeBlueprint>(nodeBlueprintWindow.LoadedNodeBlueprintPath);
    				savedAsset.PopulateFromOtherInstance(newNodeBlueprint);
    				EditorUtility.SetDirty(savedAsset);
    				AssetDatabase.SaveAssetIfDirty(savedAsset);
    			}
    			else
    			{
    				//Create new save.
    				AssetDatabase.CreateAsset(newNodeBlueprint, nodeBlueprintWindow.LoadedNodeBlueprintPath);
    			}
    		}
    
    		/// <summary>
    		/// Generate a Node Blueprint from Node Blueprint Window
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to generate Blueprint from</param>
    		/// <returns>Generated Blueprint</returns>
    		public static NodeBlueprint GenerateNodeBlueprint(this NodeBlueprintWindow nodeBlueprintWindow)
    		{
    			//Make sure the node's blueprint matches up with the UI and get it.
    			nodeBlueprintWindow.SyncNodeBlueprint();
    			var currentNodeBlueprint = nodeBlueprintWindow.EditorNode.GetBlueprint();
    			
    			//Make new blueprint instance and populate it.
    			NodeBlueprint newNodeBlueprint = ScriptableObject.CreateInstance<NodeBlueprint>();
    			newNodeBlueprint.PopulateFromOtherInstance(currentNodeBlueprint);
    			newNodeBlueprint.name = nodeBlueprintWindow.CurrentNodeBlueprintName;
    			
    			return newNodeBlueprint;
    		}
    		
    		/// <summary>
    		/// Save Blueprint as newName
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to save</param>
    		/// <param name="newName">Name to save as</param>
    		public static void SaveNodeBlueprintAs(this NodeBlueprintWindow nodeBlueprintWindow, string newName)
    		{
    			//Check if save with 'newName' exists.
    			if (AssetDatabase.LoadAssetAtPath<NodeBlueprint>($"{ExplorerPaths.SAVED_NODES_FOLDER_PATH}/{newName}{ExplorerPaths.SAVE_FORMAT}"))
    			{
    				//Open UI confirmation window.
    				if (!EditorUtility.DisplayDialog(
    						"Overwrite File?",
    						$"Another node with the name '{newName}' already exists. Saving now will result in overwriting that file.", 
    						"Save anyway", 
    						"Cancel"))
    				{
    					//Canceled save.
    					return;
    				}
    			}
    			
    			//Proceed to save.
    			nodeBlueprintWindow.CurrentNodeBlueprintName = newName;
    			SaveNodeBlueprint(nodeBlueprintWindow);
    		}
    
    		#endregion
    
    		#region Loading
    		/// <summary>
    		/// Load User Interface and Editor Node based on Blueprint saved at nodeDataPath
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to load User Interface and Editor Node</param>
    		/// <param name="nodeDataPath">Path where Blueprint to load is saved</param>
    		public static void LoadNodeBlueprint(this NodeBlueprintWindow nodeBlueprintWindow, string nodeDataPath)
    		{
    			NodeBlueprint loadedNodeBlueprint = AssetDatabase.LoadAssetAtPath<NodeBlueprint>(nodeDataPath);
    			LoadNodeBlueprint(nodeBlueprintWindow, loadedNodeBlueprint);
    		}
    		
    		/// <summary>
    		/// Load User Interface and Editor Node based on Blueprint.
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to load User Interface and Editor Node</param>
    		/// <param name="nodeBlueprint">Blueprint to load</param>
    		public static void LoadNodeBlueprint(this NodeBlueprintWindow nodeBlueprintWindow, NodeBlueprint nodeBlueprint)
    		{
    			nodeBlueprintWindow.ClearWithoutNotify();
    			
    			nodeBlueprintWindow.InputZone.Load(nodeBlueprint.inputZoneBlueprint);
    			nodeBlueprintWindow.OutputZone.Load(nodeBlueprint.outputZoneBlueprint);
    			nodeBlueprintWindow.ExtensionZone.Load(nodeBlueprint.extensionZoneBlueprint);
    			
    			nodeBlueprintWindow.CurrentNodeBlueprintName = nodeBlueprint.name;
    			
    		   nodeBlueprintWindow.SyncNodeBlueprint();
    		}
    
    		#endregion
    
    		#region Clearing
    		/// <summary>
    		/// Clear window and call change events.
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to clear</param>
    		public static void Clear(this NodeBlueprintWindow nodeBlueprintWindow)
    		{
    			nodeBlueprintWindow.InputZone.Clear();
    			nodeBlueprintWindow.OutputZone.Clear();
    			nodeBlueprintWindow.ExtensionZone.Clear();
    			nodeBlueprintWindow.SyncNodeBlueprint();
    		}
    		
    		/// <summary>
    		/// Clear window without calling change events.
    		/// </summary>
    		/// <param name="nodeBlueprintWindow">Window to clear</param>
    		public static void ClearWithoutNotify(this NodeBlueprintWindow nodeBlueprintWindow)
    		{
    			nodeBlueprintWindow.InputZone.ClearWithoutNotify();
    			nodeBlueprintWindow.OutputZone.ClearWithoutNotify();
    			nodeBlueprintWindow.ExtensionZone.ClearWithoutNotify();
    			nodeBlueprintWindow.SyncNodeBlueprint();
    		}
    
    		#endregion
    	}
    }