Kleiner Anfang für Dialog system

This commit is contained in:
Dominik
2023-05-28 11:44:55 +02:00
parent 370380751b
commit 792c90a214
702 changed files with 109467 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,82 @@
using UnityEngine;
using UnityEditor;
namespace NodeEditorFramework.Standard
{
[CustomEditor(typeof(NodeCanvas), true)]
public class CanvasInspector : Editor
{
public static GUIStyle titleStyle;
public static GUIStyle subTitleStyle;
public static GUIStyle boldLabelStyle;
public NodeCanvas canvas;
public void OnEnable()
{
canvas = (NodeCanvas)target;
canvas.Validate();
}
public override void OnInspectorGUI()
{
if (canvas == null)
canvas = (NodeCanvas)target;
if (canvas == null)
return;
if (titleStyle == null)
{
titleStyle = new GUIStyle(GUI.skin.label);
titleStyle.fontStyle = FontStyle.Bold;
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.fontSize = 16;
}
if (subTitleStyle == null)
{
subTitleStyle = new GUIStyle(GUI.skin.label);
subTitleStyle.fontStyle = FontStyle.Bold;
subTitleStyle.alignment = TextAnchor.MiddleCenter;
subTitleStyle.fontSize = 12;
}
if (boldLabelStyle == null)
{
boldLabelStyle = new GUIStyle(GUI.skin.label);
boldLabelStyle.fontStyle = FontStyle.Bold;
}
EditorGUI.BeginChangeCheck();
GUILayout.Space(10);
GUILayout.Label(new GUIContent(canvas.saveName, canvas.savePath), titleStyle);
GUILayout.Label(canvas.livesInScene? "Scene Save" : "Asset Save", subTitleStyle);
GUILayout.Label("Type: " + canvas.canvasName, subTitleStyle);
GUILayout.Space(10);
EditorGUI.BeginDisabledGroup(NodeEditor.curNodeCanvas != null && NodeEditor.curNodeCanvas.savePath == canvas.savePath);
if (GUILayout.Button("Open"))
{
string NodeCanvasPath = AssetDatabase.GetAssetPath(canvas);
NodeEditorWindow.OpenNodeEditor().canvasCache.LoadNodeCanvas(NodeCanvasPath);
}
EditorGUI.EndDisabledGroup();
GUILayout.Space(10);
GUILayout.Label("Nodes", boldLabelStyle);
foreach (Node node in canvas.nodes)
{
string label = node.Title;
EditorGUILayout.ObjectField(label, node, node.GetType(), true);
}
GUILayout.Space(10);
canvas.DrawCanvasPropertyEditor();
if (EditorGUI.EndChangeCheck())
NodeEditor.RepaintClients();
}
}
}

View File

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

View File

@@ -0,0 +1,400 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d66198f05a38b4c63bc0337fedf5bb9e, type: 3}
m_Name: LastSession
m_EditorClassIdentifier:
editorStates:
- {fileID: 114036025237380346}
saveName: New RPGTalk
savePath:
livesInScene: 0
nodes:
- {fileID: 114697767431104404}
- {fileID: 114647340530522144}
- {fileID: 114891256535487408}
- {fileID: 114563873118879350}
- {fileID: 114115618942922240}
groups: []
Name: RPGTalk
characters:
- {fileID: 11400000, guid: 70743892abc1144a8be0bd4ca5efa3e9, type: 2}
- {fileID: 11400000, guid: f01cef5fc18f942b594f6920a7490b3a, type: 2}
- {fileID: 11400000, guid: 209c7f70d52014f42a096bed78e21a52, type: 2}
charactersNames:
- None
- FunnyGuy
- Girl
- '*PlayerName*'
expressions: []
--- !u!114 &114036025237380346
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f6ab6487237ff124ea4c2aa5de9ce3fb, type: 3}
m_Name: MainEditorState
m_EditorClassIdentifier:
canvas: {fileID: 0}
parentEditor: {fileID: 0}
selectedNode: {fileID: 0}
panOffset: {x: -128.78, y: -6.17}
zoom: 1.17
--- !u!114 &114115618942922240
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8bc0b4ceaf84e421bbb8af9fbe820df0, type: 3}
m_Name: Not start but not attached to anything
m_EditorClassIdentifier:
position: {x: 293.41, y: -7.84}
dynamicConnectionPorts: []
backgroundColor: {r: 1, g: 1, b: 1, a: 1}
CharacterPotrait: {fileID: 21300000, guid: 29abebc8e9c7048819f3d2245a2a92be, type: 3}
DialogLine: This is what will be said
CutsceneTitle: FollowUp_Cutscene_0_SaveNode_1
startOfCutscene: 1
attachedTo: {fileID: 0}
characterID: 2
expressions:
- None
expressionID: 0
choices: []
questionID: Type your question ID here
attachedToChoice: {fileID: 0}
lineInTxt: 2
saves: []
attachedToSave: {fileID: 0}
toWhereOUT: {fileID: 114975964795517366}
fromWhereIN: {fileID: 114768079256692236}
--- !u!114 &114217372969242296
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3d338988a4691f43b8c0764bd85cf70, type: 3}
m_Name: From Where
m_EditorClassIdentifier:
body: {fileID: 114697767431104404}
direction: 1
maxConnectionCount: 1
styleID: RPGTalkForward
_connections:
- {fileID: 114911661688501458}
side: 4
sidePosition: 0
sideOffset: 0
--- !u!114 &114278135927515336
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3d338988a4691f43b8c0764bd85cf70, type: 3}
m_Name: From Where
m_EditorClassIdentifier:
body: {fileID: 114647340530522144}
direction: 1
maxConnectionCount: 0
styleID: RPGTalkSaveForward
_connections:
- {fileID: 114590476646751212}
side: 4
sidePosition: 0
sideOffset: 0
--- !u!114 &114330538819088658
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: To Where
m_EditorClassIdentifier:
body: {fileID: 114697767431104404}
direction: 2
maxConnectionCount: 1
styleID: RPGTalkForward
_connections: []
side: 2
sidePosition: 0
sideOffset: 0
--- !u!114 &114524780879273120
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3d338988a4691f43b8c0764bd85cf70, type: 3}
m_Name: From Where
m_EditorClassIdentifier:
body: {fileID: 114891256535487408}
direction: 1
maxConnectionCount: 1
styleID: RPGTalkForward
_connections: []
side: 4
sidePosition: 0
sideOffset: 0
--- !u!114 &114563873118879350
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ff623184d6a3b46c1bcbd076e9a8374b, type: 3}
m_Name: Save Node
m_EditorClassIdentifier:
position: {x: 31.085, y: 18.455002}
dynamicConnectionPorts: []
backgroundColor: {r: 1, g: 1, b: 1, a: 1}
savedData: IDOfQuestion
modifier: 0
attachedTo: {fileID: 0}
toWhereOUT: {fileID: 114886359839530458}
fromWhereIN: {fileID: 114851598468503804}
--- !u!114 &114590476646751212
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: To Where
m_EditorClassIdentifier:
body: {fileID: 114891256535487408}
direction: 2
maxConnectionCount: 1
styleID: RPGTalkForward
_connections:
- {fileID: 114278135927515336}
- {fileID: 114851598468503804}
side: 2
sidePosition: 0
sideOffset: 0
--- !u!114 &114647340530522144
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ff623184d6a3b46c1bcbd076e9a8374b, type: 3}
m_Name: Save Node
m_EditorClassIdentifier:
position: {x: 57.995, y: -270.53497}
dynamicConnectionPorts: []
backgroundColor: {r: 1, g: 1, b: 1, a: 1}
savedData: IDOfQuestion
modifier: 0
attachedTo: {fileID: 0}
toWhereOUT: {fileID: 114911661688501458}
fromWhereIN: {fileID: 114278135927515336}
--- !u!114 &114697767431104404
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8bc0b4ceaf84e421bbb8af9fbe820df0, type: 3}
m_Name: Not start but not attached to anything
m_EditorClassIdentifier:
position: {x: 308.09, y: -297.18}
dynamicConnectionPorts: []
backgroundColor: {r: 1, g: 1, b: 1, a: 1}
CharacterPotrait: {fileID: 21300000, guid: 29abebc8e9c7048819f3d2245a2a92be, type: 3}
DialogLine: This is what will be said
CutsceneTitle: FollowUp_Cutscene_0_SaveNode_0
startOfCutscene: 1
attachedTo: {fileID: 0}
characterID: 2
expressions:
- None
expressionID: 0
choices: []
questionID: Type your question ID here
attachedToChoice: {fileID: 0}
lineInTxt: 5
saves: []
attachedToSave: {fileID: 0}
toWhereOUT: {fileID: 114330538819088658}
fromWhereIN: {fileID: 114217372969242296}
--- !u!114 &114768079256692236
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3d338988a4691f43b8c0764bd85cf70, type: 3}
m_Name: From Where
m_EditorClassIdentifier:
body: {fileID: 114115618942922240}
direction: 1
maxConnectionCount: 1
styleID: RPGTalkForward
_connections:
- {fileID: 114886359839530458}
side: 4
sidePosition: 0
sideOffset: 0
--- !u!114 &114851598468503804
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3d338988a4691f43b8c0764bd85cf70, type: 3}
m_Name: From Where
m_EditorClassIdentifier:
body: {fileID: 114563873118879350}
direction: 1
maxConnectionCount: 0
styleID: RPGTalkSaveForward
_connections:
- {fileID: 114590476646751212}
side: 4
sidePosition: 0
sideOffset: 0
--- !u!114 &114886359839530458
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: To Where
m_EditorClassIdentifier:
body: {fileID: 114563873118879350}
direction: 2
maxConnectionCount: 0
styleID: RPGTalkSaveForward
_connections:
- {fileID: 114768079256692236}
side: 2
sidePosition: 0
sideOffset: 0
--- !u!114 &114891256535487408
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8bc0b4ceaf84e421bbb8af9fbe820df0, type: 3}
m_Name: Not start but not attached to anything
m_EditorClassIdentifier:
position: {x: -361.5, y: -179.5}
dynamicConnectionPorts: []
backgroundColor: {r: 1, g: 1, b: 1, a: 1}
CharacterPotrait: {fileID: 21300004, guid: 29abebc8e9c7048819f3d2245a2a92be, type: 3}
DialogLine: This is what will be said
CutsceneTitle: Cutscene_0
startOfCutscene: 1
attachedTo: {fileID: 0}
characterID: 1
expressions:
- None
expressionID: 0
choices: []
questionID: TypeyourquestionIDhere
attachedToChoice: {fileID: 0}
lineInTxt: 8
saves:
- {fileID: 0}
- {fileID: 0}
attachedToSave: {fileID: 0}
toWhereOUT: {fileID: 114590476646751212}
fromWhereIN: {fileID: 114524780879273120}
--- !u!114 &114911661688501458
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: To Where
m_EditorClassIdentifier:
body: {fileID: 114647340530522144}
direction: 2
maxConnectionCount: 0
styleID: RPGTalkSaveForward
_connections:
- {fileID: 114217372969242296}
side: 2
sidePosition: 0
sideOffset: 0
--- !u!114 &114975964795517366
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: To Where
m_EditorClassIdentifier:
body: {fileID: 114115618942922240}
direction: 2
maxConnectionCount: 1
styleID: RPGTalkForward
_connections: []
side: 2
sidePosition: 0
sideOffset: 0

View File

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

View File

@@ -0,0 +1,187 @@
using UnityEngine;
using UnityEditor;
using System.IO;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework.Standard
{
public class NodeEditorWindow : EditorWindow
{
// Information about current instance
private static NodeEditorWindow _editor;
public static NodeEditorWindow editor { get { AssureEditor(); return _editor; } }
public static void AssureEditor() { if (_editor == null) OpenNodeEditor(); }
// Canvas cache
public NodeEditorUserCache canvasCache;
public NodeEditorInterface editorInterface;
// GUI
private Rect canvasWindowRect { get { return new Rect(0, editorInterface.toolbarHeight, position.width, position.height - editorInterface.toolbarHeight); } }
#region General
//RPGTalk changed to make it to LockReloadAssemblies preventing the bug that makes choices and saves becoming -1. This a provisory workaround. Needs to be revised
//Also changed the names to make it easier for the noob user
/// <summary>
/// Opens the Node Editor window and loads the last session
/// </summary>
[MenuItem("RPGTalk/Node Editor - BETA")]
public static NodeEditorWindow OpenNodeEditor ()
{
_editor = GetWindow<NodeEditorWindow>();
_editor.minSize = new Vector2(400, 200);
NodeEditor.ReInit (false);
Texture iconTexture = ResourceManager.LoadTexture (EditorGUIUtility.isProSkin? "Textures/Icon_Dark.png" : "Textures/Icon_Light.png");
_editor.titleContent = new GUIContent ("Node Editor - BETA", iconTexture);
return _editor;
}
/*
/// <summary>
/// Assures that the canvas is opened when double-clicking a canvas asset
/// </summary>
[UnityEditor.Callbacks.OnOpenAsset(1)]
private static bool AutoOpenCanvas(int instanceID, int line)
{
if (Selection.activeObject != null && Selection.activeObject is NodeCanvas)
{
string NodeCanvasPath = AssetDatabase.GetAssetPath(instanceID);
OpenNodeEditor().canvasCache.LoadNodeCanvas(NodeCanvasPath);
return true;
}
return false;
}
*/
private void OnEnable()
{
_editor = this;
NormalReInit();
// Subscribe to events
NodeEditor.ClientRepaints -= Repaint;
NodeEditor.ClientRepaints += Repaint;
EditorLoadingControl.justLeftPlayMode -= NormalReInit;
EditorLoadingControl.justLeftPlayMode += NormalReInit;
EditorLoadingControl.justOpenedNewScene -= NormalReInit;
EditorLoadingControl.justOpenedNewScene += NormalReInit;
SceneView.onSceneGUIDelegate -= OnSceneGUI;
SceneView.onSceneGUIDelegate += OnSceneGUI;
EditorApplication.LockReloadAssemblies();
}
void OnDisable()
{
EditorApplication.UnlockReloadAssemblies();
}
private void OnDestroy()
{
// Unsubscribe from events
NodeEditor.ClientRepaints -= Repaint;
EditorLoadingControl.justLeftPlayMode -= NormalReInit;
EditorLoadingControl.justOpenedNewScene -= NormalReInit;
SceneView.onSceneGUIDelegate -= OnSceneGUI;
// Clear Cache
canvasCache.ClearCacheEvents();
}
private void OnLostFocus ()
{ // Save any changes made while focussing this window
// Will also save before possible assembly reload, scene switch, etc. because these require focussing of a different window
canvasCache.SaveCache();
}
private void OnFocus ()
{ // Make sure the canvas hasn't been corrupted externally
NormalReInit();
}
private void NormalReInit()
{
NodeEditor.ReInit(false);
AssureSetup();
if (canvasCache.nodeCanvas)
canvasCache.nodeCanvas.Validate();
}
private void AssureSetup()
{
if (canvasCache == null)
{ // Create cache
canvasCache = new NodeEditorUserCache(Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this))));
}
canvasCache.AssureCanvas();
if (editorInterface == null)
{ // Setup editor interface
editorInterface = new NodeEditorInterface();
editorInterface.canvasCache = canvasCache;
editorInterface.ShowNotificationAction = ShowNotification;
}
}
#endregion
#region GUI
private void OnGUI()
{
// Initiation
NodeEditor.checkInit(true);
if (NodeEditor.InitiationError)
{
GUILayout.Label("Node Editor Initiation failed! Check console for more information!");
return;
}
AssureEditor ();
AssureSetup();
// ROOT: Start Overlay GUI for popups
OverlayGUI.StartOverlayGUI("NodeEditorWindow");
// Begin Node Editor GUI and set canvas rect
NodeEditorGUI.StartNodeGUI(true);
canvasCache.editorState.canvasRect = canvasWindowRect;
try
{ // Perform drawing with error-handling
NodeEditor.DrawCanvas(canvasCache.nodeCanvas, canvasCache.editorState);
}
catch (UnityException e)
{ // On exceptions in drawing flush the canvas to avoid locking the UI
canvasCache.NewNodeCanvas();
NodeEditor.ReInit(true);
Debug.LogError("Unloaded Canvas due to an exception during the drawing phase!");
Debug.LogException(e);
}
// Draw Interface
editorInterface.DrawToolbarGUI(new Rect(0, 0, Screen.width, 0));
editorInterface.DrawModalPanel();
// End Node Editor GUI
NodeEditorGUI.EndNodeGUI();
// END ROOT: End Overlay GUI and draw popups
OverlayGUI.EndOverlayGUI();
}
private void OnSceneGUI(SceneView sceneview)
{
AssureSetup();
if (canvasCache.editorState != null && canvasCache.editorState.selectedNode != null)
canvasCache.editorState.selectedNode.OnSceneGUI();
SceneView.lastActiveSceneView.Repaint();
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,72 @@
using UnityEngine;
using UnityEditor;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework.Standard
{
[CustomEditor(typeof(Node), true)]
public class NodeInspector : Editor
{
public static GUIStyle titleStyle;
public static GUIStyle boldLabelStyle;
public Node node;
public void OnEnable()
{
node = (Node)target;
}
public override void OnInspectorGUI()
{
if (node == null)
node = (Node)target;
if (node == null)
return;
if (titleStyle == null)
{
titleStyle = new GUIStyle(GUI.skin.label);
titleStyle.fontStyle = FontStyle.Bold;
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.fontSize = 16;
}
if (boldLabelStyle == null)
{
boldLabelStyle = new GUIStyle(GUI.skin.label);
boldLabelStyle.fontStyle = FontStyle.Bold;
}
OverlayGUI.StartOverlayGUI("NodeInspector");
EditorGUI.BeginChangeCheck();
GUILayout.Space(10);
GUILayout.Label(node.Title, titleStyle);
GUILayout.Space(10);
GUILayout.Label("Rect: " + node.rect.ToString());
node.backgroundColor = EditorGUILayout.ColorField("Color", node.backgroundColor);
GUILayout.Space(10);
GUILayout.Label("Connection Ports", boldLabelStyle);
foreach (ConnectionPort port in node.connectionPorts)
{
string labelPrefix = port.direction == Direction.In ? "Input " : (port.direction == Direction.Out ? "Output " : "");
string label = labelPrefix + port.styleID + " '" + port.name + "'";
EditorGUILayout.ObjectField(label, port, port.GetType(), true);
}
GUILayout.Space(10);
GUILayout.Label("Property Editor", boldLabelStyle);
node.DrawNodePropertyEditor();
if (EditorGUI.EndChangeCheck())
NodeEditor.RepaintClients();
OverlayGUI.EndOverlayGUI();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4f4b4fa804bb6a641b473d0ca1e833b7
timeCreated: 1482685504
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using NodeEditorFramework;
namespace NodeEditorFramework.Standard
{
[CustomEditor (typeof(RTCanvasCalculator))]
public class RTCanvasCalculatorEditor : Editor
{
public RTCanvasCalculator RTCalc;
public List<Node> inputNodes;
public void OnEnable ()
{
RTCalc = (RTCanvasCalculator)target;
}
public override void OnInspectorGUI ()
{
RTCalc.canvas = EditorGUILayout.ObjectField ("Canvas", RTCalc.canvas, typeof(NodeCanvas), false) as NodeCanvas;
if (RTCalc.canvas == null)
return;
if (GUILayout.Button ("Calculate and debug Output"))
RTCalc.CalculateCanvas ();
DisplayInputValues ();
}
private void DisplayInputValues ()
{
if (inputNodes == null)
inputNodes = RTCalc.getInputNodes ();
foreach (Node inputNode in inputNodes)
{
string outValueLog = "(IN) " + inputNode.name + ": ";
foreach (ValueConnectionKnob knob in inputNode.outputKnobs.OfType<ValueConnectionKnob> ())
outValueLog += knob.styleID + " " + knob.name + " = " + (knob.IsValueNull? "NULL" : knob.GetValue ().ToString ()) + "; ";
GUILayout.Label (outValueLog);
}
}
}
}

View File

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

View File

@@ -0,0 +1,45 @@
using UnityEngine;
using UnityEditor;
namespace NodeEditorFramework.Standard
{
[CustomEditor (typeof(RTNodeEditor))]
public class RTNodeEditorInspector : Editor
{
public RTNodeEditor RTNE;
private string[] sceneCanvasNames;
public void OnEnable ()
{
RTNE = (RTNodeEditor)target;
sceneCanvasNames = NodeEditorSaveManager.GetSceneSaves();
}
public override void OnInspectorGUI ()
{
GUILayout.BeginHorizontal();
// Reference canvas
RTNE.canvas = EditorGUILayout.ObjectField ("Canvas", RTNE.canvas, typeof(NodeCanvas), true,
RTNE.canvas == null ? GUILayout.ExpandWidth(false) : GUILayout.ExpandWidth(true)) as NodeCanvas;
if (RTNE.canvas != null)
RTNE.loadSceneName = null;
// Select canvas to load from scene
int prevCanvasIndex = ArrayUtility.IndexOf(sceneCanvasNames, RTNE.loadSceneName);
int newCanvasIndex = EditorGUILayout.Popup(prevCanvasIndex, sceneCanvasNames,
string.IsNullOrEmpty(RTNE.loadSceneName) ? GUILayout.Width(30) : GUILayout.ExpandWidth (true));
if (prevCanvasIndex != newCanvasIndex && newCanvasIndex >= 0)
{
RTNE.loadSceneName = sceneCanvasNames[newCanvasIndex];
RTNE.canvas = null;
}
else if (newCanvasIndex < 0)
RTNE.loadSceneName = null;
GUILayout.EndHorizontal();
RTNE.screenSize = !EditorGUILayout.BeginToggleGroup (new GUIContent ("Specify Rect", "Specify Rects explicitly instead of adapting to the screen size"), !RTNE.screenSize);
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);
RTNE.specifiedCanvasRect = EditorGUILayout.RectField (new GUIContent ("Canvas Rect", "The rect of the canvas."), RTNE.specifiedCanvasRect);
EditorGUILayout.EndToggleGroup ();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
namespace NodeEditorFramework.Standard
{
[NodeCanvasType("Calculation")]
public class CalculationCanvasType : NodeCanvas
{
public override string canvasName { get { return "Calculation Canvas"; } }
protected override void OnCreate ()
{
Traversal = new CanvasCalculator (this);
}
public void OnEnable ()
{
// Register to other callbacks, f.E.:
//NodeEditorCallbacks.OnDeleteNode += OnDeleteNode;
}
protected override void ValidateSelf ()
{
if (Traversal == null)
Traversal = new CanvasCalculator (this);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 26a89c36c30b2e940804d7efcccaf0bd
timeCreated: 1481402892
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,102 @@
using UnityEngine;
using System.Collections.Generic;
namespace NodeEditorFramework.Standard
{
public class CanvasCalculator : NodeCanvasTraversal
{
// A list of Nodes from which calculation originates -> Call StartCalculation
public List<Node> workList;
public CanvasCalculator (NodeCanvas canvas) : base(canvas) {}
/// <summary>
/// Recalculate from every node regarded as an input node
/// </summary>
public override void TraverseAll ()
{
workList = new List<Node> ();
for (int i = 0; i < nodeCanvas.nodes.Count; i++)
{
Node node = nodeCanvas.nodes[i];
if (node.isInput ())
{ // Add all Inputs
node.ClearCalculation ();
workList.Add (node);
}
}
StartCalculation ();
}
/// <summary>
/// Recalculate from the specified node
/// </summary>
public override void OnChange (Node node)
{
node.ClearCalculation ();
workList = new List<Node> { node };
StartCalculation ();
}
/// <summary>
/// Iteratively calculates all nodes from the worklist, including child nodes, until no further calculation is possible
/// </summary>
private void StartCalculation ()
{
if (workList == null || workList.Count == 0)
return;
bool limitReached = false;
while (!limitReached)
{ // Runs until the whole workList is calculated thoroughly or no further calculation is possible
limitReached = true;
for (int workCnt = 0; workCnt < workList.Count; workCnt++)
{ // Iteratively check workList
if (ContinueCalculation (workList[workCnt]))
limitReached = false;
}
}
if (workList.Count > 0)
{
Debug.LogError("Did not complete calculation! " + workList.Count + " nodes block calculation from advancing!");
foreach (Node node in workList)
Debug.LogError("" + node.name + " blocks calculation!");
}
}
/// <summary>
/// Recursively calculates this node and it's children
/// All nodes that could not be calculated in the current state are added to the workList for later calculation
/// Returns whether calculation could advance at all
/// </summary>
private bool ContinueCalculation (Node node)
{
if (node.calculated && !node.AllowRecursion)
{ // Already calulated
workList.Remove (node);
return true;
}
if (node.ancestorsCalculated () && node.Calculate ())
{ // Calculation was successful
node.calculated = true;
workList.Remove (node);
if (node.ContinueCalculation)
{ // Continue with children
for (int i = 0; i < node.outputPorts.Count; i++)
{
ConnectionPort outPort = node.outputPorts[i];
for (int t = 0; t < outPort.connections.Count; t++)
ContinueCalculation(outPort.connections[t].body);
}
}
return true;
}
else if (!workList.Contains (node))
{ // Calculation failed, record to calculate later on
workList.Add (node);
}
return false;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 82570dd096e3133488c3ad08f4bd59e9
timeCreated: 1481043527
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
using UnityEngine;
using System;
namespace NodeEditorFramework.Standard
{
public class FloatConnectionType : ValueConnectionType
{
public override string Identifier { get { return "Float"; } }
public override Color Color { get { return Color.cyan; } }
public override Type Type { get { return typeof(float); } }
}
}

View File

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

View File

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

View File

@@ -0,0 +1,453 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
using System.Reflection;
using UnityEngine;
namespace NodeEditorFramework.IO
{
public class XMLImportExport : StructuredImportExportFormat
{
public override string FormatIdentifier { get { return "XML"; } }
public override string FormatExtension { get { return "xml"; } }
public override void ExportData(CanvasData data, params object[] args)
{
if (args == null || args.Length != 1 || args[0].GetType() != typeof(string))
throw new ArgumentException("Location Arguments");
string path = (string)args[0];
XmlDocument saveDoc = new XmlDocument();
XmlDeclaration decl = saveDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
saveDoc.InsertBefore(decl, saveDoc.DocumentElement);
// CANVAS
XmlElement canvas = saveDoc.CreateElement("NodeCanvas");
canvas.SetAttribute("type", data.type.FullName);
saveDoc.AppendChild(canvas);
// EDITOR STATES
XmlElement editorStates = saveDoc.CreateElement("EditorStates");
canvas.AppendChild(editorStates);
foreach (EditorStateData stateData in data.editorStates)
{
XmlElement editorState = saveDoc.CreateElement("EditorState");
editorState.SetAttribute("selected", stateData.selectedNode != null ? stateData.selectedNode.nodeID.ToString() : "");
editorState.SetAttribute("pan", stateData.panOffset.x + "," + stateData.panOffset.y);
editorState.SetAttribute("zoom", stateData.zoom.ToString());
editorStates.AppendChild(editorState);
}
// GROUPS
XmlElement groups = saveDoc.CreateElement("Groups");
canvas.AppendChild(groups);
foreach (GroupData groupData in data.groups)
{
XmlElement group = saveDoc.CreateElement("Group");
group.SetAttribute("name", groupData.name);
group.SetAttribute("rect", groupData.rect.x + "," + groupData.rect.y + "," + groupData.rect.width + "," + groupData.rect.height);
group.SetAttribute("color", groupData.color.r + "," + groupData.color.g + "," + groupData.color.b + "," + groupData.color.a);
groups.AppendChild(group);
}
// NODES
XmlElement nodes = saveDoc.CreateElement("Nodes");
canvas.AppendChild(nodes);
foreach (NodeData nodeData in data.nodes.Values)
{
XmlElement node = saveDoc.CreateElement("Node");
node.SetAttribute("name", nodeData.name);
node.SetAttribute("ID", nodeData.nodeID.ToString());
node.SetAttribute("type", nodeData.typeID);
node.SetAttribute("pos", nodeData.nodePos.x + "," + nodeData.nodePos.y);
nodes.AppendChild(node);
// NODE PORTS
foreach (PortData portData in nodeData.connectionPorts)
{
XmlElement port = saveDoc.CreateElement("Port");
port.SetAttribute("ID", portData.portID.ToString());
port.SetAttribute("name", portData.name);
port.SetAttribute("dynamic", portData.dynamic.ToString());
if (portData.dynamic)
{ // Serialize dynamic port
port.SetAttribute("type", portData.dynaType.FullName);
foreach (string fieldName in portData.port.AdditionalDynamicKnobData())
SerializeFieldToXML(port, portData.port, fieldName); // Serialize all dynamic knob variables
}
node.AppendChild(port);
}
// NODE VARIABLES
foreach (VariableData varData in nodeData.variables)
{ // Serialize all node variables
if (varData.refObject != null)
{ // Serialize reference-type variables as 'Variable' element
XmlElement variable = saveDoc.CreateElement("Variable");
variable.SetAttribute("name", varData.name);
variable.SetAttribute("refID", varData.refObject.refID.ToString());
node.AppendChild(variable);
}
else // Serialize value-type fields in-line
SerializeFieldToXML(node, nodeData.node, varData.name);
}
}
// CONNECTIONS
XmlElement connections = saveDoc.CreateElement("Connections");
canvas.AppendChild(connections);
foreach (ConnectionData connectionData in data.connections)
{
XmlElement connection = saveDoc.CreateElement("Connection");
connection.SetAttribute("port1ID", connectionData.port1.portID.ToString());
connection.SetAttribute("port2ID", connectionData.port2.portID.ToString());
connections.AppendChild(connection);
}
// OBJECTS
XmlElement objects = saveDoc.CreateElement("Objects");
canvas.AppendChild(objects);
foreach (ObjectData objectData in data.objects.Values)
{
XmlElement obj = saveDoc.CreateElement("Object");
obj.SetAttribute("refID", objectData.refID.ToString());
obj.SetAttribute("type", objectData.data.GetType().FullName);
objects.AppendChild(obj);
SerializeObjectToXML(obj, objectData.data);
}
// WRITE
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.Indentation = 1;
writer.IndentChar = '\t';
saveDoc.Save(writer);
}
}
public override CanvasData ImportData(params object[] args)
{
if (args == null || args.Length != 1 || args[0].GetType() != typeof(string))
throw new ArgumentException("Location Arguments");
string path = (string)args[0];
using (FileStream fs = new FileStream(path, FileMode.Open))
{
XmlDocument data = new XmlDocument();
data.Load(fs);
// CANVAS
string canvasName = Path.GetFileNameWithoutExtension(path);
XmlElement xmlCanvas = (XmlElement)data.SelectSingleNode("//NodeCanvas");
Type canvasType = NodeCanvasManager.GetCanvasTypeData(xmlCanvas.GetAttribute("type")).CanvasType;
if (canvasType == null)
throw new XmlException("Could not find NodeCanvas of type '" + xmlCanvas.GetAttribute("type") + "'!");
CanvasData canvasData = new CanvasData(canvasType, canvasName);
Dictionary<int, PortData> ports = new Dictionary<int, PortData>();
// OBJECTS
IEnumerable<XmlElement> xmlObjects = xmlCanvas.SelectNodes("Objects/Object").OfType<XmlElement>();
foreach (XmlElement xmlObject in xmlObjects)
{
int refID = GetIntegerAttribute(xmlObject, "refID");
string typeName = xmlObject.GetAttribute("type");
Type type = Type.GetType(typeName, true);
object obj = DeserializeObjectFromXML(xmlObject, type);
ObjectData objData = new ObjectData(refID, obj);
canvasData.objects.Add(refID, objData);
}
// NODES
IEnumerable<XmlElement> xmlNodes = xmlCanvas.SelectNodes("Nodes/Node").OfType<XmlElement>();
foreach (XmlElement xmlNode in xmlNodes)
{
string name = xmlNode.GetAttribute("name");
int nodeID = GetIntegerAttribute(xmlNode, "ID");
string typeID = xmlNode.GetAttribute("type");
Vector2 nodePos = GetVectorAttribute(xmlNode, "pos");
// Record
NodeData node = new NodeData(name, typeID, nodeID, nodePos);
canvasData.nodes.Add(nodeID, node);
// NODE PORTS
IEnumerable<XmlElement> xmlConnectionPorts = xmlNode.SelectNodes("Port").OfType<XmlElement>();
foreach (XmlElement xmlPort in xmlConnectionPorts)
{
int portID = GetIntegerAttribute(xmlPort, "ID");
string portName = xmlPort.GetAttribute("name");
if (string.IsNullOrEmpty(portName)) // Fallback to old save
portName = xmlPort.GetAttribute("varName");
bool dynamic = GetBooleanAttribute(xmlPort, "dynamic", false);
PortData portData;
if (!dynamic) // Record static port
portData = new PortData(node, portName, portID);
else
{ // Deserialize dynamic port
string typeName = xmlPort.GetAttribute("type");
Type portType = Type.GetType(typeName, true);
if (portType != typeof(ConnectionPort) && !portType.IsSubclassOf(typeof(ConnectionPort)))
continue; // Invalid type stored
ConnectionPort port = (ConnectionPort)ScriptableObject.CreateInstance(portType);
port.name = portName;
foreach (XmlElement portVar in xmlPort.ChildNodes.OfType<XmlElement>())
DeserializeFieldFromXML(portVar, port);
portData = new PortData(node, port, portID);
}
node.connectionPorts.Add(portData);
ports.Add(portID, portData);
}
// NODE VARIABLES
foreach (XmlElement variable in xmlNode.ChildNodes.OfType<XmlElement>())
{ // Deserialize all value-type variables
if (variable.Name != "Variable" && variable.Name != "Port")
{
string varName = variable.GetAttribute("name");
object varValue = DeserializeFieldFromXML(variable, node.type, null);
VariableData varData = new VariableData(varName);
varData.value = varValue;
node.variables.Add(varData);
}
}
IEnumerable<XmlElement> xmlVariables = xmlNode.SelectNodes("Variable").OfType<XmlElement>();
foreach (XmlElement xmlVariable in xmlVariables)
{ // Deserialize all reference-type variables (and also value type variables in old save files)
string varName = xmlVariable.GetAttribute("name");
VariableData varData = new VariableData(varName);
if (xmlVariable.HasAttribute("refID"))
{ // Read reference-type variables from objects
int refID = GetIntegerAttribute(xmlVariable, "refID");
ObjectData objData;
if (canvasData.objects.TryGetValue(refID, out objData))
varData.refObject = objData;
}
else
{ // Read value-type variable (old save file only) TODO: Remove
string typeName = xmlVariable.GetAttribute("type");
Type type = Type.GetType(typeName, true);
varData.value = DeserializeObjectFromXML(xmlVariable, type);
}
node.variables.Add(varData);
}
}
// CONNECTIONS
IEnumerable<XmlElement> xmlConnections = xmlCanvas.SelectNodes("Connections/Connection").OfType<XmlElement>();
foreach (XmlElement xmlConnection in xmlConnections)
{
int port1ID = GetIntegerAttribute(xmlConnection, "port1ID");
int port2ID = GetIntegerAttribute(xmlConnection, "port2ID");
PortData port1, port2;
if (ports.TryGetValue(port1ID, out port1) && ports.TryGetValue(port2ID, out port2))
canvasData.RecordConnection(port1, port2);
}
// GROUPS
IEnumerable<XmlElement> xmlGroups = xmlCanvas.SelectNodes("Groups/Group").OfType<XmlElement>();
foreach (XmlElement xmlGroup in xmlGroups)
{
string name = xmlGroup.GetAttribute("name");
Rect rect = GetRectAttribute(xmlGroup, "rect");
Color color = GetColorAttribute(xmlGroup, "color");
canvasData.groups.Add(new GroupData(name, rect, color));
}
// EDITOR STATES
IEnumerable<XmlElement> xmlEditorStates = xmlCanvas.SelectNodes("EditorStates/EditorState").OfType<XmlElement>();
List<EditorStateData> editorStates = new List<EditorStateData>();
foreach (XmlElement xmlEditorState in xmlEditorStates)
{
Vector2 pan = GetVectorAttribute(xmlEditorState, "pan");
float zoom;
if (!float.TryParse(xmlEditorState.GetAttribute("zoom"), out zoom))
zoom = 1;
// Selected Node
NodeData selectedNode = null;
int selectedNodeID;
if (int.TryParse(xmlEditorState.GetAttribute("selected"), out selectedNodeID))
selectedNode = canvasData.FindNode(selectedNodeID);
// Create state
editorStates.Add(new EditorStateData(selectedNode, pan, zoom));
}
canvasData.editorStates = editorStates.ToArray();
return canvasData;
}
}
#region Utility
private XmlElement SerializeFieldToXML(XmlElement parent, object obj, string fieldName)
{
Type type = obj.GetType();
FieldInfo field = type.GetField(fieldName);
if (field == null)
{
Debug.LogWarning("Failed to find field " + fieldName + " on type " + type.Name);
return null;
}
object fieldValue = field.GetValue(obj);
XmlElement serializedValue = SerializeObjectToXML(parent, fieldValue);
if (serializedValue != null)
{
serializedValue.SetAttribute("name", fieldName);
return serializedValue;
}
return null;
}
private object DeserializeFieldFromXML(XmlElement xmlElement, object obj)
{
Type type = obj.GetType();
return DeserializeFieldFromXML(xmlElement, type, obj);
}
private object DeserializeFieldFromXML(XmlElement xmlElement, Type type, object obj = null)
{
string fieldName = xmlElement.GetAttribute("name");
FieldInfo field = type.GetField(fieldName);
if (field == null)
{
Debug.LogWarning("Failed to find field " + fieldName + " on type " + type.Name);
return null;
}
object fieldValue = DeserializeObjectFromXML(xmlElement, field.FieldType, false);
if (obj != null)
field.SetValue(obj, fieldValue);
return fieldValue;
}
private XmlElement SerializeObjectToXML(XmlElement parent, object obj)
{
// TODO: Need to handle asset references
// Because of runtime compability, always try to embed objects
// If that fails, try to find references to assets (e.g. for textures)
try
{ // Try to embed object
XmlSerializer serializer = new XmlSerializer(obj.GetType());
XPathNavigator navigator = parent.CreateNavigator();
using (XmlWriter writer = navigator.AppendChild())
serializer.Serialize(writer, obj);
return (XmlElement)parent.LastChild;
}
catch (Exception)
{
Debug.Log("Could not serialize " + obj.ToString());
return null;
}
}
private object DeserializeObjectFromXML(XmlElement xmlElement, Type type, bool isParent = true)
{
if (isParent && !xmlElement.HasChildNodes)
return null;
XmlSerializer serializer = new XmlSerializer(type);
XPathNavigator navigator = (isParent ? xmlElement.FirstChild : xmlElement).CreateNavigator();
using (XmlReader reader = navigator.ReadSubtree())
return serializer.Deserialize(reader);
}
public delegate bool TryParseHandler<T>(string value, out T result);
private T GetAttribute<T>(XmlElement element, string attribute, TryParseHandler<T> handler, T defaultValue)
{
T result;
if (handler(element.GetAttribute(attribute), out result))
return result;
return defaultValue;
}
private T GetAttribute<T>(XmlElement element, string attribute, TryParseHandler<T> handler)
{
T result;
if (!handler(element.GetAttribute(attribute), out result))
throw new XmlException("Invalid " + typeof(T).Name + " " + attribute + " for element " + element.Name + "!");
return result;
}
private int GetIntegerAttribute(XmlElement element, string attribute, bool throwIfInvalid = true)
{
int result = 0;
if (!int.TryParse(element.GetAttribute(attribute), out result) && throwIfInvalid)
throw new XmlException("Invalid Int " + attribute + " for element " + element.Name + "!");
return result;
}
private float GetFloatAttribute(XmlElement element, string attribute, bool throwIfInvalid = true)
{
float result = 0;
if (!float.TryParse(element.GetAttribute(attribute), out result) && throwIfInvalid)
throw new XmlException("Invalid Float " + attribute + " for element " + element.Name + "!");
return result;
}
private bool GetBooleanAttribute(XmlElement element, string attribute, bool throwIfInvalid = true)
{
bool result = false;
if (!bool.TryParse(element.GetAttribute(attribute), out result) && throwIfInvalid)
throw new XmlException("Invalid Bool " + attribute + " for element " + element.Name + "!");
return result;
}
private Vector2 GetVectorAttribute(XmlElement element, string attribute, bool throwIfInvalid = false)
{
string[] vecString = element.GetAttribute(attribute).Split(',');
Vector2 vector = new Vector2(0, 0);
float vecX, vecY;
if (vecString.Length == 2 && float.TryParse(vecString[0], out vecX) && float.TryParse(vecString[1], out vecY))
vector = new Vector2(vecX, vecY);
else if (throwIfInvalid)
throw new XmlException("Invalid Vector2 " + attribute + " for element " + element.Name + "!");
return vector;
}
private Color GetColorAttribute(XmlElement element, string attribute, bool throwIfInvalid = false)
{
string[] vecString = element.GetAttribute(attribute).Split(',');
Color color = Color.white;
float colR, colG, colB, colA;
if (vecString.Length == 4 && float.TryParse(vecString[0], out colR) && float.TryParse(vecString[1], out colG) && float.TryParse(vecString[2], out colB) && float.TryParse(vecString[3], out colA))
color = new Color(colR, colG, colB, colA);
else if (throwIfInvalid)
throw new XmlException("Invalid Color " + attribute + " for element " + element.Name + "!");
return color;
}
private Rect GetRectAttribute(XmlElement element, string attribute, bool throwIfInvalid = false)
{
string[] vecString = element.GetAttribute(attribute).Split(',');
Rect rect = new Rect(0, 0, 100, 100);
float x, y, w, h;
if (vecString.Length == 4 && float.TryParse(vecString[0], out x) && float.TryParse(vecString[1], out y) && float.TryParse(vecString[2], out w) && float.TryParse(vecString[3], out h))
rect = new Rect(x, y, w, h);
else if (throwIfInvalid)
throw new XmlException("Invalid Rect " + attribute + " for element " + element.Name + "!");
return rect;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4b4fed10ebd11f149aca4caaba5a4c9b
timeCreated: 1497983217
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,73 @@
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace NodeEditorFramework.Standard
{
/// <summary>
/// Example of accessing and using the canvas at runtime
/// </summary>
public class RTCanvasCalculator : MonoBehaviour
{
public NodeCanvas canvas;
/// <summary>
/// Assures the canvas is loaded
/// </summary>
public void AssureCanvas ()
{
if (canvas == null)
throw new UnityException ("No canvas specified to calculate on " + name + "!");
}
/// <summary>
/// Calculates the currently loaded canvas and debugs the various outputs
/// </summary>
public void CalculateCanvas ()
{
AssureCanvas ();
NodeEditor.checkInit (false);
canvas.Validate ();
canvas.TraverseAll ();
DebugOutputResults ();
}
/// <summary>
/// Debugs the values of all possible output nodes
/// Could be done more precisely but it atleast shows how to get them
/// </summary>
private void DebugOutputResults ()
{
AssureCanvas ();
Debug.Log ("Calculating '" + canvas.saveName + "':");
List<Node> outputNodes = getOutputNodes ();
foreach (Node outputNode in outputNodes)
{ // Display value of all output nodes
string outValueLog = "(OUT) " + outputNode.name + ": ";
// Use knob values - either output knobs, or input knobs if there are now output knobs
List<ConnectionKnob> sourceValueKnobs = outputNode.outputKnobs.Count == 0? outputNode.inputKnobs : outputNode.outputKnobs;
foreach (ValueConnectionKnob knob in sourceValueKnobs.OfType<ValueConnectionKnob> ())
outValueLog += knob.styleID + " " + knob.name + " = " + (knob.IsValueNull? "NULL" : knob.GetValue ().ToString ()) + "; ";
Debug.Log (outValueLog);
}
}
/// <summary>
/// Gets all nodes that either have no inputs or no input connections assigned
/// </summary>
public List<Node> getInputNodes ()
{
AssureCanvas ();
return canvas.nodes.Where ((Node node) => node.isInput ()).ToList ();
}
/// <summary>
/// Gets all nodes that either have no output or no output connections leading to a followup node
/// </summary>
public List<Node> getOutputNodes ()
{
AssureCanvas ();
return canvas.nodes.Where ((Node node) => node.isOutput ()).ToList ();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 11b12ccbf5533274f9abf5008f057a67
timeCreated: 1472742778
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using UnityEngine;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework.Standard
{
/// <summary>
/// Example of displaying the Node Editor at runtime including GUI
/// </summary>
public class RTNodeEditor : MonoBehaviour
{
// Startup-canvas, cache and interface
public NodeCanvas canvas;
public string loadSceneName;
private NodeEditorUserCache canvasCache;
private NodeEditorInterface editorInterface;
// GUI rects
public bool screenSize = false;
public Rect specifiedRootRect = new Rect(0, 0, 1000, 500);
public Rect specifiedCanvasRect = new Rect(50, 50, 900, 400);
public Rect rootRect { get { return new Rect(screenSize ? screenRect : specifiedRootRect); } }
public Rect canvasRect { get { return screenSize ? screenRect : specifiedCanvasRect; } }
private Rect screenRect { get { return new Rect(0, 0, Screen.width, Screen.height); } }
private void Start ()
{
NormalReInit();
}
private void Update ()
{
NodeEditor.Update ();
}
private void NormalReInit()
{
NodeEditor.ReInit(false);
AssureSetup();
if (canvasCache.nodeCanvas)
canvasCache.nodeCanvas.Validate();
}
private void AssureSetup()
{
if (canvasCache == null)
{ // Create cache and load startup-canvas
canvasCache = new NodeEditorUserCache();
if (canvas != null)
canvasCache.SetCanvas(NodeEditorSaveManager.CreateWorkingCopy(canvas));
else if (!string.IsNullOrEmpty (loadSceneName))
canvasCache.LoadSceneNodeCanvas(loadSceneName);
}
canvasCache.AssureCanvas();
if (editorInterface == null)
{ // Setup editor interface
editorInterface = new NodeEditorInterface();
editorInterface.canvasCache = canvasCache;
}
}
private void OnGUI ()
{
// Initiation
NodeEditor.checkInit(true);
if (NodeEditor.InitiationError)
{
GUILayout.Label("Node Editor Initiation failed! Check console for more information!");
return;
}
AssureSetup();
// ROOT: Start Overlay GUI for popups
OverlayGUI.StartOverlayGUI("RTNodeEditor");
// Set various nested groups
GUI.BeginGroup(rootRect, GUI.skin.box);
// Begin Node Editor GUI and set canvas rect
NodeEditorGUI.StartNodeGUI(false);
canvasCache.editorState.canvasRect = new Rect (canvasRect.x, canvasRect.y + editorInterface.toolbarHeight, canvasRect.width, canvasRect.height - editorInterface.toolbarHeight);
try
{ // Perform drawing with error-handling
NodeEditor.DrawCanvas (canvasCache.nodeCanvas, canvasCache.editorState);
}
catch (UnityException e)
{ // On exceptions in drawing flush the canvas to avoid locking the UI
canvasCache.NewNodeCanvas ();
NodeEditor.ReInit (true);
Debug.LogError ("Unloaded Canvas due to exception in Draw!");
Debug.LogException (e);
}
// Draw Interface
editorInterface.DrawToolbarGUI(canvasRect);
editorInterface.DrawModalPanel();
// End Node Editor GUI
NodeEditorGUI.EndNodeGUI();
// End various nested groups
GUI.EndGroup();
// END ROOT: End Overlay GUI and draw popups
OverlayGUI.EndOverlayGUI();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 058701fb26fe9874f8dffda7e66a0fe9
timeCreated: 1450128681
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

@@ -0,0 +1,397 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
[System.Serializable]
public class ConnectionKnob : ConnectionPort
{
// Properties
public override ConnectionShape shape { get { return ConnectionShape.Bezier; } }
// Connections
new public List<ConnectionKnob> connections { get { return _connections.OfType<ConnectionKnob> ().ToList (); } }
// Knob Style
protected override Type styleBaseClass { get { return typeof(ConnectionKnobStyle); } }
new protected ConnectionKnobStyle ConnectionStyle { get { CheckConnectionStyle (); return (ConnectionKnobStyle)_connectionStyle; } }
// Knob GUI
protected Texture2D knobTexture;
private float knobAspect { get { return knobTexture != null? knobTexture.width/knobTexture.height : 1; } }
private GUIStyle labelStyle { get { return side == NodeSide.Right? NodeEditorGUI.nodeLabelRight : NodeEditorGUI.nodeLabelLeft; } }
// Knob Position
private NodeSide defaultSide { get { return direction == Direction.Out? NodeSide.Right : NodeSide.Left; } }
public NodeSide side;
public float sidePosition = 0; // Position on the side, top->bottom, left->right
public float sideOffset = 0; // Offset from the side
public void Init (Node body, string name, Direction dir)
{
base.Init (body, name);
direction = dir;
maxConnectionCount = dir == Direction.In? ConnectionCount.Single : ConnectionCount.Multi;
side = dir == Direction.Out? NodeSide.Right : NodeSide.Left;
sidePosition = 0;
}
public void Init (Node body, string name, Direction dir, NodeSide nodeSide, float nodeSidePosition = 0)
{
base.Init (body, name);
direction = dir;
maxConnectionCount = dir == Direction.In? ConnectionCount.Single : ConnectionCount.Multi;
side = nodeSide;
sidePosition = nodeSidePosition;
}
new public ConnectionKnob connection (int index)
{
if (connections.Count <= index)
throw new IndexOutOfRangeException ("connections[" + index + "] of '" + name + "'");
return connections[index];
}
public override IEnumerable<string> AdditionalDynamicKnobData()
{
return base.AdditionalDynamicKnobData ().Concat (new List<string>() { "side", "sidePosition", "sideOffset" });
}
#region Knob Texture
/// <summary>
/// Checks the texture and requests to load it again if necessary
/// </summary>
internal void CheckKnobTexture ()
{
if (side == 0)
side = defaultSide;
if (knobTexture == null)
UpdateKnobTexture ();
}
/// <summary>
/// Requests to reload the knobTexture and adapts it to the position and orientation
/// </summary>
protected void UpdateKnobTexture ()
{
ReloadTexture ();
if (knobTexture == null)
throw new UnityException ("Knob texture of " + name + " could not be loaded!");
if (side != defaultSide)
{ // Rotate Knob texture according to the side it's used on
int rotationSteps = getRotationStepsAntiCW (defaultSide, side);
string[] mods = new string[] { "Rotation:" + rotationSteps };
Texture2D modKnobTex = null;
// Try to get standard texture in memory
ResourceManager.MemoryTexture memoryTex = ResourceManager.FindInMemory (knobTexture);
if (memoryTex != null)
{ // Texture does exist in memory, so built a mod including rotation and try to find it again
mods = ResourceManager.AppendMod (memoryTex.modifications, "Rotation:" + rotationSteps);
ResourceManager.TryGetTexture (memoryTex.path, ref modKnobTex, mods);
}
if (modKnobTex == null)
{ // Rotated version does not exist yet, so create and record it
modKnobTex = RTEditorGUI.RotateTextureCCW (knobTexture, rotationSteps);
ResourceManager.AddTextureToMemory (memoryTex.path, modKnobTex, mods);
}
knobTexture = modKnobTex;
}
}
/// <summary>
/// Requests to reload the source knob texture
/// </summary>
protected virtual void ReloadTexture ()
{
// knobTexture = RTEditorGUI.ColorToTex (1, Color.red);
// knobTexture = ResourceManager.GetTintedTexture (direction == Direction.Out? "Textures/Out_Knob.png" : "Textures/In_Knob.png", color);
knobTexture = ConnectionStyle == null? Texture2D.whiteTexture : (direction == Direction.Out? ConnectionStyle.OutKnobTex : ConnectionStyle.InKnobTex);
}
#endregion
#region Knob Position
/// <summary>
/// Gets the Knob rect in GUI space, NOT ZOOMED
/// </summary>
public Rect GetGUIKnob ()
{
Rect rect = GetCanvasSpaceKnob ();
rect.position += NodeEditor.curEditorState.zoomPanAdjust + NodeEditor.curEditorState.panOffset;
return rect;
}
/// <summary>
/// Get the Knob rect in screen space, ZOOMED, for Input detection purposes
/// </summary>
public Rect GetCanvasSpaceKnob ()
{
Vector2 knobSize = new Vector2 (NodeEditorGUI.knobSize * knobAspect,
NodeEditorGUI.knobSize / knobAspect);
Vector2 knobCenter = GetKnobCenter (knobSize);
return new Rect (knobCenter.x - knobSize.x/2, knobCenter.y - knobSize.y/2, knobSize.x, knobSize.y);
}
private Vector2 GetKnobCenter (Vector2 knobSize)
{
if (side == NodeSide.Left)
return body.rect.position + new Vector2 (-sideOffset-knobSize.x/2, sidePosition);
else if (side == NodeSide.Right)
return body.rect.position + new Vector2 ( sideOffset+knobSize.x/2 + body.rect.width, sidePosition);
else if (side == NodeSide.Bottom)
return body.rect.position + new Vector2 (sidePosition, sideOffset+knobSize.y/2 + body.rect.height);
else if (side == NodeSide.Top)
return body.rect.position + new Vector2 (sidePosition, -sideOffset-knobSize.y/2);
else
throw new Exception ("Unspecified nodeSide of NodeKnop " + name + ": " + side.ToString ());
}
/// <summary>
/// Gets the direction of the knob (vertical inverted) for connection drawing purposes
/// </summary>
public Vector2 GetDirection ()
{
return side == NodeSide.Right? Vector2.right :
(side == NodeSide.Bottom? Vector2.up :
(side == NodeSide.Top? Vector2.down :
/* Left */ Vector2.left));
}
/// <summary>
/// Gets the rotation steps anti-clockwise from NodeSide A to B
/// </summary>
private static int getRotationStepsAntiCW (NodeSide sideA, NodeSide sideB)
{
return sideB - sideA + (sideA>sideB? 4 : 0);
}
#endregion
#region Knob GUI
/// <summary>
/// Draw the knob at the defined position
/// </summary>
public virtual void DrawKnob ()
{
CheckKnobTexture ();
GUI.DrawTexture (GetGUIKnob (), knobTexture);
}
/// <summary>
/// Draws the connection curves from this knob to all it's connections
/// </summary>
public override void DrawConnections ()
{
if (Event.current.type != EventType.Repaint)
return;
Vector2 startPos = GetGUIKnob ().center;
Vector2 startDir = GetDirection();
for (int i = 0; i < connections.Count; i++)
{
ConnectionKnob conKnob = connections[i];
Vector2 endPos = conKnob.GetGUIKnob().center;
Vector2 endDir = conKnob.GetDirection();
NodeEditorGUI.DrawConnection(startPos, startDir, endPos, endDir, color);
}
}
/// <summary>
/// Draws a connection line from the current knob to the specified position
/// </summary>
public override void DrawConnection (Vector2 endPos)
{
Vector2 startPos = GetGUIKnob ().center;
Vector2 startDir = GetDirection ();
NodeEditorGUI.DrawConnection (startPos, startDir, endPos, -startDir, color);
}
/// <summary>
/// Displays a label and places the knob next to it, apropriately
/// </summary>
public void DisplayLayout ()
{
DisplayLayout (new GUIContent (name), labelStyle);
}
/// <summary>
/// Draws a label with the knob's name and the given style. Places the knob next to it at it's nodeSide
/// </summary>
public void DisplayLayout (GUIStyle style)
{
DisplayLayout (new GUIContent (name), style);
}
/// <summary>
/// Draws a label with the given GUIContent. Places the knob next to it at it's nodeSide
/// </summary>
public void DisplayLayout (GUIContent content)
{
DisplayLayout (content, labelStyle);
}
/// <summary>
/// Draws a label with the given GUIContent and the given style. Places the knob next to it at it's nodeSide
/// </summary>
public void DisplayLayout (GUIContent content, GUIStyle style)
{
GUILayout.Label (content, style);
SetPosition ();
}
/// <summary>
/// Sets the knob's position at the specified nodeSide next to the last GUILayout control
/// </summary>
public void SetPosition ()
{
if (Event.current.type != EventType.Repaint || body.ignoreGUIKnobPlacement)
return;
Vector2 pos = GUILayoutUtility.GetLastRect ().center + body.contentOffset;
SetPosition (side == NodeSide.Bottom || side == NodeSide.Top? pos.x : pos.y);
}
/// <summary>
/// Sets the knob's position at the specified nodeSide, from Top->Bottom and Left->Right
/// </summary>
public void SetPosition(float position, NodeSide nodeSide)
{
if (body.ignoreGUIKnobPlacement)
return;
if (side != nodeSide)
{
side = nodeSide;
UpdateKnobTexture();
}
SetPosition(position);
}
/// <summary>
/// Sets the knob's position at it's nodeSide, from Top->Bottom and Left->Right
/// </summary>
public void SetPosition(float position)
{
if (body.ignoreGUIKnobPlacement)
return;
sidePosition = position;
}
#endregion
}
[AttributeUsage(AttributeTargets.Field)]
public class ConnectionKnobAttribute : ConnectionPortAttribute
{
public NodeSide NodeSide;
public float NodeSidePos;
public override Type ConnectionType { get { return typeof(ConnectionKnob); } }
public ConnectionKnobAttribute(string name, Direction direction) : base (name, direction)
{
Setup (direction == Direction.Out? NodeSide.Right : NodeSide.Left, 0);
}
public ConnectionKnobAttribute(string name, Direction direction, ConnectionCount maxCount) : base (name, direction)
{
Setup (maxCount, direction == Direction.Out? NodeSide.Right : NodeSide.Left, 0);
}
public ConnectionKnobAttribute(string name, Direction direction, string styleID) : base (name, direction, styleID)
{
Setup (direction == Direction.Out? NodeSide.Right : NodeSide.Left, 0);
}
public ConnectionKnobAttribute(string name, Direction direction, string styleID, ConnectionCount maxCount) : base (name, direction, styleID)
{
Setup (maxCount, direction == Direction.Out? NodeSide.Right : NodeSide.Left, 0);
}
public ConnectionKnobAttribute(string name, Direction direction, NodeSide nodeSide, float nodeSidePos = 0) : base (name, direction)
{
Setup (nodeSide, nodeSidePos);
}
public ConnectionKnobAttribute(string name, Direction direction, ConnectionCount maxCount, NodeSide nodeSide, float nodeSidePos = 0) : base (name, direction)
{
Setup (maxCount, nodeSide, nodeSidePos);
}
public ConnectionKnobAttribute(string name, Direction direction, string styleID, NodeSide nodeSide, float nodeSidePos = 0) : base (name, direction, styleID)
{
Setup (nodeSide, nodeSidePos);
}
public ConnectionKnobAttribute(string name, Direction direction, string styleID, ConnectionCount maxCount, NodeSide nodeSide, float nodeSidePos = 0) : base (name, direction, styleID)
{
Setup (maxCount, nodeSide, nodeSidePos);
}
private void Setup (NodeSide nodeSide, float nodeSidePos)
{
MaxConnectionCount = Direction == Direction.In? ConnectionCount.Single : ConnectionCount.Multi;
NodeSide = nodeSide;
NodeSidePos = nodeSidePos;
}
private void Setup (ConnectionCount maxCount, NodeSide nodeSide, float nodeSidePos)
{
MaxConnectionCount = maxCount;
NodeSide = nodeSide;
NodeSidePos = nodeSidePos;
}
public override ConnectionPort CreateNew (Node body)
{
ConnectionKnob knob = ScriptableObject.CreateInstance<ConnectionKnob> ();
knob.Init (body, Name, Direction, NodeSide, NodeSidePos);
knob.styleID = StyleID;
knob.maxConnectionCount = MaxConnectionCount;
return knob;
}
public override void UpdateProperties (ConnectionPort port)
{
ConnectionKnob knob = (ConnectionKnob)port;
knob.name = Name;
knob.direction = Direction;
knob.styleID = StyleID;
knob.maxConnectionCount = MaxConnectionCount;
knob.side = NodeSide;
if (NodeSidePos != 0)
knob.sidePosition = NodeSidePos;
knob.sideOffset = 0;
}
}
[ReflectionUtility.ReflectionSearchIgnoreAttribute ()]
public class ConnectionKnobStyle : ConnectionPortStyle
{
public virtual string InKnobTexPath { get { return "Textures/In_Knob.png"; } }
public virtual string OutKnobTexPath { get { return "Textures/Out_Knob.png"; } }
private Texture2D _inKnobTex;
private Texture2D _outKnobTex;
public Texture2D InKnobTex { get { if (_inKnobTex == null) LoadKnobTextures(); return _inKnobTex; } }
public Texture2D OutKnobTex { get { if (_outKnobTex == null) LoadKnobTextures(); return _outKnobTex; } }
public ConnectionKnobStyle () : base () {}
public ConnectionKnobStyle (string name) : base (name) { }
protected void LoadKnobTextures()
{
_inKnobTex = ResourceManager.GetTintedTexture (InKnobTexPath, Color);
_outKnobTex = ResourceManager.GetTintedTexture (OutKnobTexPath, Color);
if (InKnobTex == null || OutKnobTex == null)
Debug.LogError("Invalid style '" + Identifier + "': Could not load knob textures from '" + InKnobTexPath + "' and '" + OutKnobTexPath + "'!");
}
public override bool isValid ()
{
return InKnobTex != null && OutKnobTex != null;
}
}
}

View File

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

View File

@@ -0,0 +1,341 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
public enum NodeSide { Left = 4, Top = 3, Right = 2, Bottom = 1 }
public enum Direction { None, In, Out }
public enum ConnectionShape { Line, Bezier }
public enum ConnectionCount { Single, Multi, Max }
[System.Serializable]
public class ConnectionPort : ScriptableObject
{
// Properties
public Node body;
public Direction direction = Direction.None;
public ConnectionCount maxConnectionCount = ConnectionCount.Multi;
public virtual ConnectionShape shape { get { return ConnectionShape.Line; } }
// Connection Style
public string styleID;
protected ConnectionPortStyle _connectionStyle;
protected ConnectionPortStyle ConnectionStyle { get { CheckConnectionStyle (); return _connectionStyle; } }
protected virtual Type styleBaseClass { get { return typeof(ConnectionPortStyle); } }
[NonSerialized]
public Color color = Color.white;
// Connections
[SerializeField]
protected List<ConnectionPort> _connections = new List<ConnectionPort> ();
public List<ConnectionPort> connections { get { return _connections; } }
public void Init (Node nodeBody, string knobName)
{
body = nodeBody;
name = knobName;
}
public void Validate(Node nodeBody)
{
if (body == null)
{
Debug.LogError("Port " + name + " has no node body assigned! Fixed!");
body = nodeBody;
}
if (_connections == null)
_connections = new List<ConnectionPort>();
int originalCount = _connections.Count;
_connections = _connections.Where(o => o != null).ToList();
if (originalCount != _connections.Count)
Debug.LogWarning("Removed " + (originalCount - _connections.Count) + " broken (null) connections from node " + body.name + "! Automatically fixed!");
}
public virtual IEnumerable<string> AdditionalDynamicKnobData()
{
return new List<string>() { "styleID", "direction", "maxConnectionCount" };
}
#region Connection GUI
/// <summary>
/// Checks and fetches the connection style declaration specified by the styleID
/// </summary>
protected void CheckConnectionStyle ()
{
if (_connectionStyle == null || !_connectionStyle.isValid ())
{
_connectionStyle = ConnectionPortStyles.GetPortStyle (styleID, styleBaseClass);
if (_connectionStyle == null || !_connectionStyle.isValid())
color = NodeEditorGUI.RandomColorHSV(styleID.GetHashCode(), 0, 1, 0.6f, 0.8f, 0.8f, 1.4f);
else
color = _connectionStyle.Color;
}
}
/// <summary>
/// Draws the connection lines from this port to all it's connections
/// </summary>
public virtual void DrawConnections ()
{
if (Event.current.type != EventType.Repaint)
return;
Vector2 startPos = body.rect.center;
for (int i = 0; i < connections.Count; i++)
{
Vector2 endPos = connections[i].body.rect.center;
NodeEditorGUI.DrawConnection (startPos, endPos, ConnectionDrawMethod.StraightLine, color);
}
}
/// <summary>
/// Draws a connection line from the current knob to the specified position
/// </summary>
public virtual void DrawConnection (Vector2 endPos)
{
if (Event.current.type != EventType.Repaint)
return;
NodeEditorGUI.DrawConnection (body.rect.center, endPos, ConnectionDrawMethod.StraightLine, color);
}
#endregion
#region Connection Utility
/// <summary>
/// Returns whether this port is connected to any other port
/// </summary>
public bool connected ()
{
return connections.Count > 0;
}
/// <summary>
/// Returns the connection with the specified index or null if not existant
/// </summary>
public ConnectionPort connection (int index)
{
if (connections.Count <= index)
throw new IndexOutOfRangeException ("connections[" + index + "] of '" + name + "'");
return connections[index];
}
/// <summary>
/// Tries to apply a connection between this port and the specified port
/// </summary>
public bool TryApplyConnection (ConnectionPort port, bool silent = false)
{
if (CanApplyConnection (port))
{ // This port and the specified port can be connected
ApplyConnection (port, silent);
return true;
}
return false;
}
/// <summary>
/// Determines whether a connection can be applied between this port and the specified port
/// </summary>
public virtual bool CanApplyConnection (ConnectionPort port)
{
if (port == null || body == port.body || connections.Contains (port))
return false;
if (direction == Direction.None && port.direction == Direction.None)
return true; // None-Directive connections can always connect
if (direction == Direction.In && port.direction != Direction.Out)
return false; // Cannot connect inputs with anything other than outputs
if (direction == Direction.Out && port.direction != Direction.In)
return false; // Cannot connect outputs with anything other than inputs
if (direction == Direction.Out) // Let inputs handle checks for recursion
return port.CanApplyConnection (this);
if (port.body.isChildOf (body))
{ // Recursive
if (!port.body.allowsLoopRecursion (body))
{
// TODO: Generic Notification
Debug.LogWarning ("Cannot apply connection: Recursion detected!");
return false;
}
}
return true;
}
/// <summary>
/// Applies a connection between between this port and the specified port.
/// 'CanApplyConnection' has to be checked before to avoid interferences!
/// </summary>
public void ApplyConnection (ConnectionPort port, bool silent = false)
{
if (port == null)
return;
if (maxConnectionCount == ConnectionCount.Single && connections.Count > 0)
{ // Respect maximum connection count on this port
RemoveConnection(connections[0], silent);
connections.Clear ();
}
connections.Add(port);
if (port.maxConnectionCount == ConnectionCount.Single && port.connections.Count > 0)
{ // Respect maximum connection count on the other port
port.RemoveConnection(port.connections[0], silent);
port.connections.Clear ();
}
port.connections.Add (this);
if (!silent)
{ // Callbacks
port.body.OnAddConnection (port, this);
body.OnAddConnection (this, port);
NodeEditorCallbacks.IssueOnAddConnection (this, port);
NodeEditor.curNodeCanvas.OnNodeChange(direction == Direction.In? port.body : body);
}
}
/// <summary>
/// Clears all connections of this port to other ports
/// </summary>
public void ClearConnections (bool silent = false)
{
while (connections.Count > 0)
RemoveConnection (connections[0], silent);
}
/// <summary>
/// Removes the connection of this port to the specified port if existant
/// </summary>
public void RemoveConnection (ConnectionPort port, bool silent = false)
{
if (port == null)
{
connections.RemoveAll (p => p != null);
return;
}
if (!silent) NodeEditorCallbacks.IssueOnRemoveConnection (this, port);
port.connections.Remove (this);
connections.Remove (port);
port.body.OnRemoveConnection(port, this);
body.OnRemoveConnection(this, port);
if (!silent) NodeEditor.curNodeCanvas.OnNodeChange (body);
}
#endregion
}
[AttributeUsage(AttributeTargets.Field)]
public class ConnectionPortAttribute : Attribute
{
public string Name;
public string StyleID;
public Direction Direction;
public ConnectionCount MaxConnectionCount;
public virtual Type ConnectionType { get { return typeof(ConnectionPort); } }
public ConnectionPortAttribute(string name)
{
Setup (name, Direction.None, "Default", ConnectionCount.Multi);
}
public ConnectionPortAttribute(string name, Direction direction)
{
Setup (name, direction, "Default", ConnectionCount.Multi);
}
public ConnectionPortAttribute(string name, Direction direction, ConnectionCount maxCount)
{
Setup (name, direction, "Default", maxCount);
}
public ConnectionPortAttribute(string name, string styleID)
{
Setup (name, Direction.None, styleID, ConnectionCount.Multi);
}
public ConnectionPortAttribute(string name, Direction direction, string styleID)
{
Setup (name, direction, styleID, ConnectionCount.Multi);
}
public ConnectionPortAttribute(string name, Direction direction, string styleID, ConnectionCount maxCount)
{
Setup (name, direction, styleID, maxCount);
}
private void Setup (string name, Direction direction, string styleID, ConnectionCount maxCount)
{
Name = name;
Direction = direction;
StyleID = styleID;
MaxConnectionCount = maxCount;
}
public bool MatchFieldType (Type fieldType)
{
return fieldType.IsAssignableFrom (ConnectionType);
}
public virtual bool IsCompatibleWith (ConnectionPort port)
{
if (port.GetType() != ConnectionType)
return false;
if (port.styleID != StyleID)
return false;
if (!(Direction == Direction.None && port.direction == Direction.None)
&& !(Direction == Direction.In && port.direction == Direction.Out)
&& !(Direction == Direction.Out && port.direction == Direction.In))
return false;
return true;
}
public virtual ConnectionPort CreateNew (Node body)
{
ConnectionPort port = ScriptableObject.CreateInstance<ConnectionPort> ();
port.Init (body, Name);
port.direction = Direction;
port.styleID = StyleID;
port.maxConnectionCount = MaxConnectionCount;
return port;
}
public virtual void UpdateProperties (ConnectionPort port)
{
port.name = Name;
port.direction = Direction;
port.styleID = StyleID;
port.maxConnectionCount = MaxConnectionCount;
}
}
[ReflectionUtility.ReflectionSearchIgnoreAttribute ()]
public class ConnectionPortStyle
{
protected string identifier;
protected Color color;
public virtual string Identifier { get { return identifier; } }
public virtual Color Color { get { return color; } }
public ConnectionPortStyle () {}
public ConnectionPortStyle (string name)
{
identifier = name;
GenerateColor();
}
public void GenerateColor ()
{ // Generate consistent color for a type - using string because it delivers greater variety of colors than type hashcode
color = NodeEditorGUI.RandomColorHSV(Identifier.GetHashCode(), 0, 1, 0.6f, 0.8f, 0.8f, 1.4f);
}
public virtual bool isValid ()
{
return true;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 35456fbe6f3a0f3459ecfa7e219c273c
timeCreated: 1498244777
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,142 @@
using UnityEngine;
using System;
using System.Linq;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
public static class ConnectionPortManager
{
private static Dictionary<string, ConnectionPortDeclaration[]> nodePortDeclarations;
/// <summary>
/// Fetches every node connection declaration for each node type for later use
/// </summary>
public static void FetchNodeConnectionDeclarations()
{
nodePortDeclarations = new Dictionary<string, ConnectionPortDeclaration[]> ();
foreach (NodeTypeData nodeData in NodeTypes.getNodeDefinitions ())
{
Type nodeType = nodeData.type;
List<ConnectionPortDeclaration> declarations = new List<ConnectionPortDeclaration> ();
// Get all declared port fields
FieldInfo[] declaredPorts = ReflectionUtility.getFieldsOfType (nodeType, typeof(ConnectionPort));
foreach (FieldInfo portField in declaredPorts)
{ // Get info about that port declaration using the attribute
object[] declAttrs = portField.GetCustomAttributes (typeof(ConnectionPortAttribute), true);
if (declAttrs.Length < 1)
continue;
ConnectionPortAttribute declarationAttr = (ConnectionPortAttribute)declAttrs[0];
if (declarationAttr.MatchFieldType (portField.FieldType))
declarations.Add (new ConnectionPortDeclaration (portField, declarationAttr));
else
Debug.LogError ("Mismatched " + declarationAttr.GetType ().Name + " for " + portField.FieldType.Name + " '" + declarationAttr.Name + "' on " + nodeData.type.Name + "!");
}
nodePortDeclarations.Add (nodeData.typeID, declarations.ToArray ());
}
}
/// <summary>
/// Updates all node connection ports in the given node and creates or adjusts them according to the declaration
/// </summary>
public static void UpdateConnectionPorts (Node node)
{
foreach (ConnectionPortDeclaration portDecl in GetPortDeclarationEnumerator (node, true))
{
ConnectionPort port = (ConnectionPort)portDecl.portField.GetValue (node);
if (port == null)
{ // Create new port from declaration
port = portDecl.portInfo.CreateNew (node);
portDecl.portField.SetValue (node, port);
}
else
{ // Check port values against port declaration
portDecl.portInfo.UpdateProperties (port);
}
}
}
/// <summary>
/// Updates the connectionPorts and connectionKnobs lists of the given node with all declared nodes
/// </summary>
public static void UpdatePortLists (Node node)
{
// Triggering is enough to update the list
IEnumerator enumerator = GetPortDeclarationEnumerator(node, true).GetEnumerator();
while (enumerator.MoveNext()) { }
}
/// <summary>
/// Returns the ConnectionPortDeclarations for the given node type
/// </summary>
public static ConnectionPortDeclaration[] GetPortDeclarations (string nodeTypeID)
{
ConnectionPortDeclaration[] portDecls;
if (nodePortDeclarations.TryGetValue (nodeTypeID, out portDecls))
return portDecls;
else
throw new ArgumentException ("Could not find node port declarations for node type '" + nodeTypeID + "'!");
}
/// <summary>
/// Returns an enumerator of type ConnectionPortDeclaration over all port declarations of the given node
/// </summary>
public static IEnumerable GetPortDeclarationEnumerator (Node node, bool triggerUpdate = false)
{
List<ConnectionPort> declaredConnectionPorts = new List<ConnectionPort> ();
ConnectionPortDeclaration[] portDecls;
if (nodePortDeclarations.TryGetValue (node.GetID, out portDecls))
{
for (int i = 0; i < portDecls.Length; i++)
{ // Iterate over each connection port and yield it's declaration
ConnectionPortDeclaration portDecl = portDecls[i];
yield return portDecl;
ConnectionPort port = (ConnectionPort)portDecl.portField.GetValue (node);
if (port != null)
declaredConnectionPorts.Add(port);
}
}
if (triggerUpdate)
{ // Update lists as values might have changes when calling this function
node.staticConnectionPorts = declaredConnectionPorts;
UpdateRepresentativePortLists(node);
}
}
/// <summary>
/// Update the differenciated representative port lists in the given node to accommodate to all static and dynamic connection ports
/// </summary>
public static void UpdateRepresentativePortLists(Node node)
{
// Clean source static and dynamic port lists
node.dynamicConnectionPorts = node.dynamicConnectionPorts.Where(o => o != null).ToList();
node.staticConnectionPorts = node.staticConnectionPorts.Where(o => o != null).ToList();
// Combine static and dynamic ports into one list
node.connectionPorts = new List<ConnectionPort>();
node.connectionPorts.AddRange(node.staticConnectionPorts);
node.connectionPorts.AddRange(node.dynamicConnectionPorts);
// Differenciate ports into types and direction
node.inputPorts = node.connectionPorts.Where((ConnectionPort port) => port.direction == Direction.In).ToList();
node.outputPorts = node.connectionPorts.Where((ConnectionPort port) => port.direction == Direction.Out).ToList();
node.connectionKnobs = node.connectionPorts.OfType<ConnectionKnob>().ToList();
node.inputKnobs = node.connectionKnobs.Where((ConnectionKnob knob) => knob.direction == Direction.In).ToList();
node.outputKnobs = node.connectionKnobs.Where((ConnectionKnob knob) => knob.direction == Direction.Out).ToList();
}
}
public class ConnectionPortDeclaration
{
public FieldInfo portField;
public ConnectionPortAttribute portInfo;
public ConnectionPortDeclaration (FieldInfo field, ConnectionPortAttribute attr)
{
portField = field;
portInfo = attr;
}
}
}

View File

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

View File

@@ -0,0 +1,603 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
namespace NodeEditorFramework
{
public abstract partial class Node : ScriptableObject
{
public Vector2 position;
private Vector2 autoSize;
public Vector2 size { get { return AutoLayout? autoSize : DefaultSize; } }
public Rect rect { get { return new Rect (position, size); } }
public Rect fullAABBRect { get { return new Rect(position.x - 20, position.y - 20, size.x + 40, size.y + 40); } }
// Dynamic connection ports
public List<ConnectionPort> dynamicConnectionPorts = new List<ConnectionPort>();
// Static connection ports stored in the actual declaration variables
[NonSerialized] public List<ConnectionPort> staticConnectionPorts = new List<ConnectionPort>();
// Representative lists of static port declarations aswell as dynamic ports
[NonSerialized] public List<ConnectionPort> connectionPorts = new List<ConnectionPort> ();
[NonSerialized] public List<ConnectionPort> inputPorts = new List<ConnectionPort> ();
[NonSerialized] public List<ConnectionPort> outputPorts = new List<ConnectionPort> ();
[NonSerialized] public List<ConnectionKnob> connectionKnobs = new List<ConnectionKnob> ();
[NonSerialized] public List<ConnectionKnob> inputKnobs = new List<ConnectionKnob> ();
[NonSerialized] public List<ConnectionKnob> outputKnobs = new List<ConnectionKnob> ();
// Calculation graph
[HideInInspector] [NonSerialized]
internal bool calculated = true;
// Internal
internal Vector2 contentOffset = Vector2.zero;
internal Vector2 nodeGUIHeight;
internal bool ignoreGUIKnobPlacement;
internal bool isClipped;
// Style
public Color backgroundColor = Color.white;
#region Properties and Settings
/// <summary>
/// Gets the ID of the Node
/// </summary>
public abstract string GetID { get; }
/// <summary>
/// Specifies the node title.
/// </summary>
public virtual string Title { get {
#if UNITY_EDITOR
return UnityEditor.ObjectNames.NicifyVariableName (GetID);
#else
return name;
#endif
} }
/// <summary>
/// Specifies the default size of the node when automatic resizing is turned off.
/// </summary>
public virtual Vector2 DefaultSize { get { return new Vector2(200, 100); } }
/// <summary>
/// Specifies whether the size of this node should be automatically calculated.
/// If this is overridden to true, MinSize should be set, too.
/// </summary>
public virtual bool AutoLayout { get { return false; } }
/// <summary>
/// Specifies the minimum size the node can have if no content is present.
/// </summary>
public virtual Vector2 MinSize { get { return new Vector2(100, 50); } }
/// <summary>
/// Specifies if this node handles recursive node loops on the canvas.
/// A loop requires atleast a single node to handle recursion to be permitted.
/// </summary>
public virtual bool AllowRecursion { get { return false; } }
/// <summary>
/// Specifies if calculation should continue with the nodes connected to the outputs after the Calculation function returns success
/// </summary>
public virtual bool ContinueCalculation { get { return true; } }
#endregion
#region Node Implementation
/// <summary>
/// Initializes the node with Inputs/Outputs and other data if necessary.
/// </summary>
protected virtual void OnCreate() {}
/// <summary>
/// Draws the Node GUI including all controls and potentially Input/Output labels.
/// By default, it displays all Input/Output labels.
/// </summary>
public virtual void NodeGUI ()
{
GUILayout.BeginHorizontal ();
GUILayout.BeginVertical ();
for (int i = 0; i < inputKnobs.Count; i++)
inputKnobs[i].DisplayLayout ();
GUILayout.EndVertical ();
GUILayout.BeginVertical ();
for (int i = 0; i < outputKnobs.Count; i++)
outputKnobs[i].DisplayLayout();
GUILayout.EndVertical ();
GUILayout.EndHorizontal ();
}
/// <summary>
/// Used to display a custom node property editor in the GUI.
/// By default shows the standard NodeGUI.
/// </summary>
public virtual void DrawNodePropertyEditor ()
{
try
{ // Draw Node GUI without disturbing knob placement
ignoreGUIKnobPlacement = true;
NodeEditorGUI.StartNodeGUI(false);
GUILayout.BeginVertical(GUI.skin.box);
NodeGUI();
GUILayout.EndVertical();
NodeEditorGUI.EndNodeGUI();
}
finally
{ // Be sure to always reset the state to not mess up other GUI code
ignoreGUIKnobPlacement = false;
}
}
/// <summary>
/// Calculates the outputs of this Node depending on the inputs.
/// Returns success
/// </summary>
public virtual bool Calculate () { return true; }
#endregion
#region Callbacks
/// <summary>
/// Callback when the node is deleted
/// </summary>
protected internal virtual void OnDelete () {}
/// <summary>
/// Callback when the given port on this node was assigned a new connection
/// </summary>
protected internal virtual void OnAddConnection (ConnectionPort port, ConnectionPort connection) {}
/// <summary>
/// Callback when the given port on this node was removed
/// </summary>
protected internal virtual void OnRemoveConnection(ConnectionPort port, ConnectionPort connection) { }
/// <summary>
/// Should return all additional ScriptableObjects this Node references
/// </summary>
public virtual ScriptableObject[] GetScriptableObjects () { return new ScriptableObject[0]; }
/// <summary>
/// Replaces all references to any ScriptableObjects this Node holds with the cloned versions in the serialization process.
/// </summary>
protected internal virtual void CopyScriptableObjects (System.Func<ScriptableObject, ScriptableObject> replaceSO) {}
#endregion
#region General
/// <summary>
/// Creates a node of the specified ID at pos on the current canvas, optionally auto-connecting the specified output to a matching input
/// </summary>
public static Node Create (string nodeID, Vector2 pos, ConnectionPort connectingPort = null, bool silent = false, bool init = true)
{
return Create (nodeID, pos, NodeEditor.curNodeCanvas, connectingPort, silent, init);
}
/// <summary>
/// Creates a node of the specified ID at pos on the specified canvas, optionally auto-connecting the specified output to a matching input
/// silent disables any events, init specifies whether OnCreate should be called
/// </summary>
public static Node Create (string nodeID, Vector2 pos, NodeCanvas hostCanvas, ConnectionPort connectingPort = null, bool silent = false, bool init = true)
{
if (string.IsNullOrEmpty (nodeID) || hostCanvas == null)
throw new ArgumentException ();
if (!NodeCanvasManager.CheckCanvasCompability (nodeID, hostCanvas.GetType ()))
throw new UnityException ("Cannot create Node with ID '" + nodeID + "' as it is not compatible with the current canavs type (" + hostCanvas.GetType ().ToString () + ")!");
if (!hostCanvas.CanAddNode (nodeID))
throw new UnityException ("Cannot create Node with ID '" + nodeID + "' on the current canvas of type (" + hostCanvas.GetType ().ToString () + ")!");
// Create node from data
NodeTypeData data = NodeTypes.getNodeData (nodeID);
Node node = (Node)CreateInstance (data.type);
if(node == null)
return null;
// Init node state
node.name = node.Title;
node.autoSize = node.DefaultSize;
node.position = pos;
ConnectionPortManager.UpdateConnectionPorts (node);
if (init)
node.OnCreate();
if (connectingPort != null)
{ // Handle auto-connection and link the output to the first compatible input
for (int i = 0; i < node.connectionPorts.Count; i++)
{
if (node.connectionPorts[i].TryApplyConnection (connectingPort, silent))
break;
}
}
// Add node to host canvas
hostCanvas.nodes.Add (node);
if (!silent)
{ // Callbacks
hostCanvas.OnNodeChange(connectingPort != null ? connectingPort.body : node);
NodeEditorCallbacks.IssueOnAddNode(node);
hostCanvas.Validate();
NodeEditor.RepaintClients();
}
return node;
}
/// <summary>
/// Deletes this Node from curNodeCanvas and the save file
/// </summary>
public void Delete (bool silent = false)
{
if (!NodeEditor.curNodeCanvas.nodes.Contains (this))
throw new UnityException ("The Node " + name + " does not exist on the Canvas " + NodeEditor.curNodeCanvas.name + "!");
if (!silent)
NodeEditorCallbacks.IssueOnDeleteNode (this);
NodeEditor.curNodeCanvas.nodes.Remove (this);
for (int i = 0; i < connectionPorts.Count; i++)
{
connectionPorts[i].ClearConnections (silent);
DestroyImmediate (connectionPorts[i], true);
}
DestroyImmediate (this, true);
if (!silent)
NodeEditor.curNodeCanvas.Validate ();
}
#endregion
#region Drawing
#if UNITY_EDITOR
/// <summary>
/// If overridden, the Node can draw to the scene view GUI in the Editor.
/// </summary>
public virtual void OnSceneGUI()
{
}
#endif
/// <summary>
/// Draws the node frame and calls NodeGUI. Can be overridden to customize drawing.
/// </summary>
protected internal virtual void DrawNode ()
{
// Create a rect that is adjusted to the editor zoom and pixel perfect
Rect nodeRect = rect;
Vector2 pos = NodeEditor.curEditorState.zoomPanAdjust + NodeEditor.curEditorState.panOffset;
nodeRect.position = new Vector2((int)(nodeRect.x+pos.x), (int)(nodeRect.y+pos.y));
contentOffset = new Vector2 (0, 20);
GUI.color = backgroundColor;
// Create a headerRect out of the previous rect and draw it, marking the selected node as such by making the header bold
Rect headerRect = new Rect (nodeRect.x, nodeRect.y, nodeRect.width, contentOffset.y);
GUI.color = backgroundColor;
GUI.Box (headerRect, GUIContent.none, GUI.skin.box);
GUI.color = Color.white;
GUI.Label (headerRect, Title, NodeEditor.curEditorState.selectedNode == this? NodeEditorGUI.nodeLabelBoldCentered : NodeEditorGUI.nodeLabelCentered);
// Begin the body frame around the NodeGUI
Rect bodyRect = new Rect (nodeRect.x, nodeRect.y + contentOffset.y, nodeRect.width, nodeRect.height - contentOffset.y);
GUI.color = backgroundColor;
GUI.BeginGroup (bodyRect, GUI.skin.box);
GUI.color = Color.white;
bodyRect.position = Vector2.zero;
GUILayout.BeginArea (bodyRect);
// Call NodeGUI
GUI.changed = false;
NodeGUI ();
if(Event.current.type == EventType.Repaint)
nodeGUIHeight = GUILayoutUtility.GetLastRect().max + contentOffset;
// End NodeGUI frame
GUILayout.EndArea ();
GUI.EndGroup ();
// Automatically node if desired
AutoLayoutNode ();
}
/// <summary>
/// Resizes the node to either the MinSize or to fit size of the GUILayout in NodeGUI
/// </summary>
protected internal virtual void AutoLayoutNode()
{
if (!AutoLayout || Event.current.type != EventType.Repaint)
return;
Rect nodeRect = rect;
Vector2 size = new Vector2();
size.y = Math.Max(nodeGUIHeight.y, MinSize.y) + 4;
// Account for potential knobs that might occupy horizontal space
float knobSize = 0;
List<ConnectionKnob> verticalKnobs = connectionKnobs.Where (x => x.side == NodeSide.Bottom || x.side == NodeSide.Top).ToList ();
if (verticalKnobs.Count > 0)
knobSize = verticalKnobs.Max ((ConnectionKnob knob) => knob.GetGUIKnob ().xMax - nodeRect.xMin);
size.x = Math.Max (knobSize, MinSize.x);
autoSize = size;
NodeEditor.RepaintClients ();
}
/// <summary>
/// Draws the connectionKnobs of this node
/// </summary>
protected internal virtual void DrawKnobs ()
{
for (int i = 0; i < connectionKnobs.Count; i++)
connectionKnobs[i].DrawKnob ();
}
/// <summary>
/// Draws the connections from this node's connectionPorts
/// </summary>
protected internal virtual void DrawConnections ()
{
if (Event.current.type != EventType.Repaint)
return;
for (int i = 0; i < outputPorts.Count; i++)
outputPorts[i].DrawConnections ();
}
#endregion
#region Node Utility
/// <summary>
/// Tests the node whether the specified position is inside any of the node's elements and returns a potentially focused connection knob.
/// </summary>
public bool ClickTest(Vector2 position, out ConnectionKnob focusedKnob)
{
focusedKnob = null;
if (rect.Contains(position))
return true;
for (int i = 0; i < connectionKnobs.Count; i++)
{ // Check if any nodeKnob is focused instead
if (connectionKnobs[i].GetCanvasSpaceKnob().Contains(position))
{
focusedKnob = connectionKnobs[i];
return true;
}
}
return false;
}
/// <summary>
/// Returns whether the node acts as an input (no inputs or no inputs assigned)
/// </summary>
public bool isInput()
{
for (int i = 0; i < inputPorts.Count; i++)
if (inputPorts[i].connected())
return false;
return true;
}
/// <summary>
/// Returns whether the node acts as an output (no outputs or no outputs assigned)
/// </summary>
public bool isOutput()
{
for (int i = 0; i < outputPorts.Count; i++)
if (outputPorts[i].connected())
return false;
return true;
}
/// <summary>
/// Returns whether every direct ancestor has been calculated
/// </summary>
public bool ancestorsCalculated ()
{
for (int i = 0; i < inputPorts.Count; i++)
{
ConnectionPort port = inputPorts[i];
for (int t = 0; t < port.connections.Count; t++)
{
if (!port.connections[t].body.calculated)
return false;
}
}
return true;
}
/// <summary>
/// Recursively checks whether this node is a child of the other node
/// </summary>
public bool isChildOf (Node otherNode)
{
if (otherNode == null || otherNode == this)
return false;
if (BeginRecursiveSearchLoop ()) return false;
for (int i = 0; i < inputPorts.Count; i++)
{
ConnectionPort port = inputPorts[i];
for (int t = 0; t < port.connections.Count; t++)
{
ConnectionPort conPort = port.connections[t];
if (conPort.body != startRecursiveSearchNode && (conPort.body == otherNode || conPort.body.isChildOf(otherNode)))
{
StopRecursiveSearchLoop();
return true;
}
}
}
EndRecursiveSearchLoop ();
return false;
}
/// <summary>
/// Recursively checks whether this node is in a loop
/// </summary>
internal bool isInLoop ()
{
if (BeginRecursiveSearchLoop ()) return this == startRecursiveSearchNode;
for (int i = 0; i < inputPorts.Count; i++)
{
ConnectionPort port = inputPorts[i];
for (int t = 0; t < port.connections.Count; t++)
{
if (port.connections[t].body.isInLoop())
{
StopRecursiveSearchLoop();
return true;
}
}
}
EndRecursiveSearchLoop ();
return false;
}
/// <summary>
/// Recursively checks whether any node in the loop to be made allows recursion.
/// 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).
/// That means isChildOf has to be confirmed before calling this!
/// </summary>
internal bool allowsLoopRecursion (Node otherNode)
{
if (AllowRecursion)
return true;
if (otherNode == null)
return false;
if (BeginRecursiveSearchLoop ()) return false;
for (int i = 0; i < inputPorts.Count; i++)
{
ConnectionPort port = inputPorts[i];
for (int t = 0; t < port.connections.Count; t++)
{
if (port.connections[t].body.allowsLoopRecursion(otherNode))
{
StopRecursiveSearchLoop();
return true;
}
}
}
EndRecursiveSearchLoop ();
return false;
}
/// <summary>
/// A recursive function to clear all calculations depending on this node.
/// Usually does not need to be called manually
/// </summary>
public void ClearCalculation ()
{
calculated = false;
if (BeginRecursiveSearchLoop ()) return;
for (int i = 0; i < outputPorts.Count; i++)
{
ConnectionPort port = outputPorts[i];
if (port is ValueConnectionKnob)
(port as ValueConnectionKnob).ResetValue ();
for (int t = 0; t < port.connections.Count; t++)
{
ConnectionPort conPort = port.connections[t];
if (conPort is ValueConnectionKnob)
(conPort as ValueConnectionKnob).ResetValue ();
conPort.body.ClearCalculation ();
}
}
EndRecursiveSearchLoop ();
}
#region Recursive Search Helpers
[NonSerialized] private List<Node> recursiveSearchSurpassed;
[NonSerialized] private Node startRecursiveSearchNode; // Temporary start node for recursive searches
/// <summary>
/// Begins the recursive search loop and returns whether this node has already been searched
/// </summary>
internal bool BeginRecursiveSearchLoop ()
{
if (startRecursiveSearchNode == null || recursiveSearchSurpassed == null)
{ // Start search
recursiveSearchSurpassed = new List<Node> ();
startRecursiveSearchNode = this;
}
if (recursiveSearchSurpassed.Contains (this))
return true;
recursiveSearchSurpassed.Add (this);
return false;
}
/// <summary>
/// Ends the recursive search loop if this was the start node
/// </summary>
internal void EndRecursiveSearchLoop ()
{
if (startRecursiveSearchNode == this)
{ // End search
recursiveSearchSurpassed = null;
startRecursiveSearchNode = null;
}
}
/// <summary>
/// Stops the recursive search loop immediately. Call when you found what you needed.
/// </summary>
internal void StopRecursiveSearchLoop ()
{
recursiveSearchSurpassed = null;
startRecursiveSearchNode = null;
}
#endregion
#endregion
#region Knob Utility
public ConnectionPort CreateConnectionPort(ConnectionPortAttribute specificationAttribute)
{
ConnectionPort port = specificationAttribute.CreateNew(this);
if (port == null)
return null;
dynamicConnectionPorts.Add(port);
ConnectionPortManager.UpdateRepresentativePortLists(this);
return port;
}
public ConnectionKnob CreateConnectionKnob(ConnectionKnobAttribute specificationAttribute)
{
return (ConnectionKnob)CreateConnectionPort(specificationAttribute);
}
public ValueConnectionKnob CreateValueConnectionKnob(ValueConnectionKnobAttribute specificationAttribute)
{
return (ValueConnectionKnob)CreateConnectionPort(specificationAttribute);
}
public void DeleteConnectionPort(ConnectionPort dynamicPort)
{
dynamicPort.ClearConnections ();
dynamicConnectionPorts.Remove(dynamicPort);
DestroyImmediate(dynamicPort);
ConnectionPortManager.UpdateRepresentativePortLists(this);
}
public void DeleteConnectionPort(int dynamicPortIndex)
{
if (dynamicPortIndex >= 0 && dynamicPortIndex < dynamicConnectionPorts.Count)
DeleteConnectionPort(dynamicConnectionPorts[dynamicPortIndex]);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,195 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NodeEditorFramework
{
/// <summary>
/// Base class for all canvas types
/// </summary>
public abstract class NodeCanvas : ScriptableObject
{
public virtual string canvasName { get { return "DEFAULT"; } }
public virtual bool allowSceneSaveOnly { get { return false; } }
public NodeCanvasTraversal Traversal;
public NodeEditorState[] editorStates = new NodeEditorState[0];
public string saveName;
public string savePath;
public bool livesInScene = false;
public List<Node> nodes = new List<Node> ();
public List<NodeGroup> groups = new List<NodeGroup> ();
#region Constructors
/// <summary>
/// Creates a canvas of the specified generic type
/// </summary>
public static T CreateCanvas<T> () where T : NodeCanvas
{
if (typeof(T) == typeof(NodeCanvas))
throw new Exception ("Cannot create canvas of type 'NodeCanvas' as that is only the base class. Please specify a valid subclass!");
T canvas = ScriptableObject.CreateInstance<T>();
canvas.name = canvas.saveName = "New " + canvas.canvasName;
NodeEditor.BeginEditingCanvas (canvas);
canvas.OnCreate ();
NodeEditor.EndEditingCanvas ();
return canvas;
}
/// <summary>
/// Creates a canvas of the specified canvasType as long as it is a subclass of NodeCanvas
/// </summary>
public static NodeCanvas CreateCanvas (Type canvasType)
{
NodeCanvas canvas;
if (canvasType != null && canvasType.IsSubclassOf (typeof(NodeCanvas)))
canvas = ScriptableObject.CreateInstance (canvasType) as NodeCanvas;
else
canvas = ScriptableObject.CreateInstance<NodeEditorFramework.Standard.CalculationCanvasType>();
canvas.name = canvas.saveName = "New " + canvas.canvasName;
NodeEditor.BeginEditingCanvas (canvas);
canvas.OnCreate ();
NodeEditor.EndEditingCanvas ();
return canvas;
}
#endregion
#region Extension Methods
// GENERAL
protected virtual void OnCreate () {}
protected virtual void ValidateSelf () { }
public virtual void OnBeforeSavingCanvas () { }
public virtual bool CanAddNode (string nodeID) { return true; }
// GUI
public virtual void DrawCanvasPropertyEditor () { }
// ADDITIONAL SERIALIZATION
/// <summary>
/// Should return all additional ScriptableObjects this Node references
/// </summary>
public virtual ScriptableObject[] GetScriptableObjects () { return new ScriptableObject[0]; }
/// <summary>
/// Replaces all references to any ScriptableObjects this Node holds with the cloned versions in the serialization process.
/// </summary>
protected internal virtual void CopyScriptableObjects (System.Func<ScriptableObject, ScriptableObject> replaceSO) {}
#endregion
#region Methods
/// <summary>
/// Trigger traversal of the whole canvas
/// </summary>
public void TraverseAll ()
{
if (Traversal != null)
Traversal.TraverseAll ();
}
/// <summary>
/// Specifies a node change, usually triggering traversal from that node
/// </summary>
public void OnNodeChange (Node node)
{
if (Traversal != null && node != null)
Traversal.OnChange (node);
}
/// <summary>
/// Validates this canvas, checking for any broken nodes or references and cleans them.
/// </summary>
public void Validate ()
{
NodeEditor.checkInit(false);
// Check Groups
CheckNodeCanvasList(ref groups, "groups");
// Check Nodes and their connection ports
CheckNodeCanvasList(ref nodes, "nodes");
foreach (Node node in nodes)
{
ConnectionPortManager.UpdateConnectionPorts(node);
foreach (ConnectionPort port in node.connectionPorts)
port.Validate(node);
}
// Check EditorStates
if (editorStates == null)
editorStates = new NodeEditorState[0];
editorStates = editorStates.Where ((NodeEditorState state) => state != null).ToArray ();
foreach (NodeEditorState state in editorStates)
{
if (!nodes.Contains (state.selectedNode))
state.selectedNode = null;
}
// Validate CanvasType-specific stuff
ValidateSelf ();
}
/// <summary>
/// Checks the specified list and assures it is initialized, contains no null nodes and it it does, removes them and outputs an error.
/// </summary>
private void CheckNodeCanvasList<T> (ref List<T> list, string listName)
{
if (list == null)
{
Debug.LogWarning("NodeCanvas '" + name + "' " + listName + " were erased and set to null! Automatically fixed!");
list = new List<T>();
}
int originalCount = list.Count;
list = list.Where((T o) => o != null).ToList();
if (originalCount != list.Count)
Debug.LogWarning("NodeCanvas '" + name + "' contained " + (originalCount - list.Count) + " broken (null) " + listName + "! Automatically fixed!");
}
/// <summary>
/// Updates the source of this canvas to the specified path, updating saveName and savePath aswell as livesInScene when prefixed with "SCENE/"
/// </summary>
public void UpdateSource (string path)
{
if (path == savePath)
return;
string newName;
if (path.StartsWith ("SCENE/"))
{
newName = path.Substring (6);
}
else
{
int nameStart = path.LastIndexOf ('/')+1;
newName = path.Substring (nameStart, path.Length-nameStart-6);
}
if (!newName.ToLower ().Contains ("lastsession"))
{
savePath = path;
saveName = newName;
livesInScene = path.StartsWith ("SCENE/");
}
return;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,22 @@
using System;
namespace NodeEditorFramework
{
[Serializable]
public abstract class NodeCanvasTraversal
{
public NodeCanvas nodeCanvas;
public NodeCanvasTraversal (NodeCanvas canvas)
{
nodeCanvas = canvas;
}
public virtual void OnLoadCanvas () { }
public virtual void OnSaveCanvas () { }
public abstract void TraverseAll ();
public virtual void OnChange (Node node) {}
}
}

View File

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

View File

@@ -0,0 +1,91 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace NodeEditorFramework
{
public partial class NodeEditorState : ScriptableObject
{ // holds the state of a NodeCanvas inside a NodeEditor
public NodeCanvas canvas;
public NodeEditorState parentEditor;
// Canvas options
[NonSerialized] public bool drawing = true; // whether to draw the canvas
// Selection State
public Node selectedNode; // selected Node
[NonSerialized] public Node focusedNode; // Node under mouse
[NonSerialized] public ConnectionKnob focusedConnectionKnob; // ConnectionKnob under mouse
[NonSerialized] public NodeGroup activeGroup; // NodeGroup that is currently interacted with
// Navigation State
public Vector2 panOffset = new Vector2 (); // pan offset
public float zoom = 1; // zoom; Ranges in 0.2er-steps from 0.6-2.0; applied 1/zoom;
// Current Action
[NonSerialized] public ConnectionKnob connectKnob; // connection this output
[NonSerialized] public bool dragNode; // node dragging
[NonSerialized] public bool panWindow; // window panning
[NonSerialized] public bool navigate; // navigation ('N')
[NonSerialized] public bool resizeGroup; // whether the active group is being resized; if not, it is dragged
// Temporary variables
public Vector2 zoomPos { get { return canvasRect.size/2; } } // zoom center in canvas space
public Rect canvasViewport { get { return new Rect(-panOffset - zoomPos * zoom, canvasRect.size * zoom); } } // canvas viewport in canvas space (same as nodes)
[NonSerialized] public Rect canvasRect; // canvas rect in GUI space
[NonSerialized] public Vector2 zoomPanAdjust; // calculated value to offset elements with when zooming
[NonSerialized] public List<Rect> ignoreInput = new List<Rect> (); // Rects inside the canvas to ignore input in (nested canvases, fE)
#region DragHelper
[NonSerialized] public string dragUserID; // dragging source
[NonSerialized] public Vector2 dragMouseStart; // drag start position (mouse)
[NonSerialized] public Vector2 dragObjectStart; // start position of the dragged object
[NonSerialized] public Vector2 dragOffset; // offset for both node dragging and window panning
public Vector2 dragObjectPos { get { return dragObjectStart + dragOffset; } } // position of the dragged object
/// <summary>
/// Starts a drag operation with the given userID and initial mouse and object position
/// Returns false when a different user already claims this drag operation
/// </summary>
public bool StartDrag (string userID, Vector2 mousePos, Vector2 objectPos)
{
if (!String.IsNullOrEmpty (dragUserID) && dragUserID != userID)
return false;
dragUserID = userID;
dragMouseStart = mousePos;
dragObjectStart = objectPos;
dragOffset = Vector2.zero;
return true;
}
/// <summary>
/// Updates the current drag with the passed new mouse position and returns the drag offset change since the last update
/// </summary>
public Vector2 UpdateDrag (string userID, Vector2 newDragPos)
{
if (dragUserID != userID)
throw new UnityException ("User ID " + userID + " tries to interrupt drag from " + dragUserID);
Vector2 prevOffset = dragOffset;
dragOffset = (newDragPos - dragMouseStart) * zoom;
return dragOffset - prevOffset;
}
/// <summary>
/// Ends the drag of the specified userID
/// </summary>
public Vector2 EndDrag (string userID)
{
if (dragUserID != userID)
throw new UnityException ("User ID " + userID + " tries to end drag from " + dragUserID);
Vector2 dragPos = dragObjectPos;
dragUserID = "";
dragOffset = dragMouseStart = dragObjectStart = Vector2.zero;
return dragPos;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,483 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
/// <summary>
/// A NodeGroup on the canvas that handles node and subgroup pinning and syncing along with functionality to manipulate and customize the group
/// </summary>
[Serializable]
public class NodeGroup
{
/// <summary>
/// Represents selected borders as a flag in order to support corners
/// </summary>
[Flags]
enum BorderSelection {
None = 0,
Left = 1,
Right = 2,
Top = 4,
Bottom = 8
};
public Rect rect;
public string title;
public Color color { get { return _color; } set { _color = value; GenerateStyles(); } }
[SerializeField]
private Color _color = Color.blue;
private bool edit;
internal bool isClipped;
// Resizing and dragging state for active node group
private static BorderSelection resizeDir;
[NonSerialized] private List<Node> pinnedNodes = new List<Node> ();
[NonSerialized] private List<NodeGroup> pinnedGroups = new List<NodeGroup> ();
// Settings
private static bool headerFree = true;
private const int borderWidth = 15;
private const int minGroupSize = 150;
private const int headerHeight = 30;
// Accessors
private Rect headerRect { get { return new Rect (rect.x, rect.y - (headerFree? headerHeight : 0), rect.width, headerHeight); } }
private Rect bodyRect { get { return new Rect (rect.x, rect.y + (headerFree? 0 : headerHeight), rect.width, rect.height - (headerFree? 0 : headerHeight)); } }
internal Rect fullAABBRect { get { return new Rect(rect.x, rect.y - (headerFree ? headerHeight : 0), rect.width, rect.height + (headerFree ? headerHeight : 0)); } }
/// <summary>
/// Silently creates a NodeGroup
/// </summary>
internal NodeGroup() {}
/// <summary>
/// Creates a new NodeGroup with the specified title at pos and adds it to the current canvas
/// </summary>
public NodeGroup(string groupTitle, Vector2 pos)
{
title = groupTitle;
rect = new Rect(pos.x, pos.y, 400, 400);
GenerateStyles();
NodeEditor.curNodeCanvas.groups.Add(this);
UpdateGroupOrder();
}
/// <summary>
/// Deletes this NodeGroup
/// </summary>
public void Delete ()
{
NodeEditor.curNodeCanvas.groups.Remove (this);
}
#region Group Functionality
/// <summary>
/// Update pinned nodes and subGroups of this NodeGroup
/// </summary>
private void UpdatePins ()
{
pinnedGroups = new List<NodeGroup> ();
foreach (NodeGroup group in NodeEditor.curNodeCanvas.groups)
{ // Get all pinned groups -> all groups atleast half in the group
if (group != this && rect.Contains(group.headerRect.center))
pinnedGroups.Add(group);
}
pinnedNodes = new List<Node>();
foreach (Node node in NodeEditor.curNodeCanvas.nodes)
{ // Get all pinned nodes -> all nodes atleast half in the group
if (rect.Contains(node.rect.center))
pinnedNodes.Add(node);
}
}
/// <summary>
/// Updates the group order by their sizes for better input handling
/// </summary>
private static void UpdateGroupOrder ()
{
foreach (NodeGroup group in NodeEditor.curNodeCanvas.groups)
group.UpdatePins();
//NodeEditor.curNodeCanvas.groups.Sort((x, y) => -x.pinnedGroups.Count.CompareTo(y.pinnedGroups.Count));
NodeEditor.curNodeCanvas.groups = NodeEditor.curNodeCanvas.groups
.OrderByDescending((x) => x.pinnedGroups.Count) // Order by pin hierarchy level
.ThenByDescending ((x) => x.rect.size.x * x.rect.size.y) // Incase of equal level, prefer smaller groups
.ToList();
}
#endregion
#region GUI
[NonSerialized]
private GUIStyle backgroundStyle;
[NonSerialized]
private GUIStyle altBackgroundStyle;
[NonSerialized]
private GUIStyle opBackgroundStyle;
// [NonSerialized]
// private GUIStyle dragHandleStyle;
[NonSerialized]
private GUIStyle headerTitleStyle;
[NonSerialized]
private GUIStyle headerTitleEditStyle;
/// <summary>
/// Generates all the styles for this node group based of the color
/// </summary>
private void GenerateStyles ()
{
// Transparent background
Texture2D background = RTEditorGUI.ColorToTex (8, color * new Color (1, 1, 1, 0.4f));
// lighter, less transparent background
Texture2D altBackground = RTEditorGUI.ColorToTex (8, color * new Color (1, 1, 1, 0.6f));
// nearly opaque background
Texture2D opBackground = RTEditorGUI.ColorToTex (8, color * new Color (1, 1, 1, 0.9f));
backgroundStyle = new GUIStyle ();
backgroundStyle.normal.background = background;
backgroundStyle.padding = new RectOffset (10, 10, 5, 5);
altBackgroundStyle = new GUIStyle ();
altBackgroundStyle.normal.background = altBackground;
altBackgroundStyle.padding = new RectOffset (10, 10, 5, 5);
opBackgroundStyle = new GUIStyle();
opBackgroundStyle.normal.background = opBackground;
opBackgroundStyle.padding = new RectOffset (10, 10, 5, 5);
// dragHandleStyle = new GUIStyle ();
// dragHandleStyle.normal.background = background;
// //dragHandleStyle.hover.background = altBackground;
// dragHandleStyle.padding = new RectOffset (10, 10, 5, 5);
headerTitleStyle = new GUIStyle ();
headerTitleStyle.fontSize = 20;
headerTitleStyle.normal.textColor = Color.white;
headerTitleEditStyle = new GUIStyle (headerTitleStyle);
headerTitleEditStyle.normal.background = background;
headerTitleEditStyle.focused.background = background;
headerTitleEditStyle.focused.textColor = Color.white;
}
/// <summary>
/// Draws the NodeGroup
/// </summary>
public void DrawGroup ()
{
if (backgroundStyle == null)
GenerateStyles ();
NodeEditorState state = NodeEditor.curEditorState;
// Create a rect that is adjusted to the editor zoom
Rect groupRect = rect;
groupRect.position += state.zoomPanAdjust + state.panOffset;
// Resize handles
//Rect leftSideRect = new Rect(groupRect.x, groupRect.y, borderWidth, groupRect.height);
//Rect rightSideRect = new Rect(groupRect.x + groupRect.width - borderWidth, groupRect.y, borderWidth, groupRect.height);
//Rect topSideRect = new Rect(groupRect.x, groupRect.y, groupRect.width, borderWidth);
//Rect bottomSideRect = new Rect(groupRect.x, groupRect.y + groupRect.height - borderWidth, groupRect.width, borderWidth);
//GUI.Box(leftSideRect, GUIContent.none, dragHandleStyle);
//GUI.Box(rightSideRect, GUIContent.none, dragHandleStyle);
//GUI.Box(topSideRect, GUIContent.none, dragHandleStyle);
//GUI.Box(bottomSideRect, GUIContent.none, dragHandleStyle);
if (state.activeGroup == this && state.resizeGroup)
{ // Highlight the currently resized border
Rect handleRect = getBorderRect (rect, NodeGroup.resizeDir);
handleRect.position += state.zoomPanAdjust + state.panOffset;
GUI.Box (handleRect, GUIContent.none, opBackgroundStyle);
}
// Body
Rect groupBodyRect = bodyRect;
groupBodyRect.position += state.zoomPanAdjust + state.panOffset;
GUI.Box (groupBodyRect, GUIContent.none, backgroundStyle);
// Header
Rect groupHeaderRect = headerRect;
groupHeaderRect.position += state.zoomPanAdjust + state.panOffset;
GUILayout.BeginArea (groupHeaderRect, headerFree? GUIStyle.none : altBackgroundStyle);
GUILayout.BeginHorizontal ();
GUILayout.Space (8);
// Header Title
if (edit)
title = GUILayout.TextField (title, headerTitleEditStyle, GUILayout.MinWidth (40));
else
GUILayout.Label (title, headerTitleStyle, GUILayout.MinWidth(40));
GUILayout.Space(10);
// Header Color Edit
#if UNITY_EDITOR
if (edit && !Application.isPlaying)
{
GUILayout.Space (10);
color = UnityEditor.EditorGUILayout.ColorField (color);
}
#endif
GUILayout.FlexibleSpace ();
// Edit Button
if (GUILayout.Button ("E", new GUILayoutOption [] { GUILayout.ExpandWidth (false), GUILayout.ExpandHeight (false) }))
edit = !edit;
GUILayout.EndHorizontal ();
GUILayout.EndArea ();
}
#endregion
#region Helpers and Hit Tests
/// <summary>
/// Gets a NodeGroup which has it's header under the mouse. If multiple groups are adressed, the smallest is returned.
/// </summary>
private static NodeGroup HeaderAtPosition(NodeEditorState state, Vector2 canvasPos)
{
if (NodeEditorInputSystem.shouldIgnoreInput(state))
return null;
for (int i = state.canvas.groups.Count-1; i >= 0; i--)
{
NodeGroup group = state.canvas.groups[i];
if (group.headerRect.Contains(canvasPos))
return group;
}
return null;
}
/// <summary>
/// Gets a NodeGroup under the mouse. If multiple groups are adressed, the group lowest in the pin hierarchy is returned.
/// </summary>
private static NodeGroup GroupAtPosition(NodeEditorState state, Vector2 canvasPos)
{
if (NodeEditorInputSystem.shouldIgnoreInput(state))
return null;
for (int i = state.canvas.groups.Count - 1; i >= 0; i--)
{
NodeGroup group = state.canvas.groups[i];
if (group.headerRect.Contains(canvasPos) || group.rect.Contains(canvasPos))
return group;
}
return null;
}
/// <summary>
/// Gets a NodeGroup under the mouse that requires input (header or border). If multiple groups are adressed, the group lowest in the pin hierarchy is returned.
/// </summary>
private static NodeGroup GroupAtPositionInput(NodeEditorState state, Vector2 canvasPos)
{
if (NodeEditorInputSystem.shouldIgnoreInput(state))
return null;
for (int i = state.canvas.groups.Count - 1; i >= 0; i--)
{
NodeGroup group = state.canvas.groups[i];
BorderSelection border;
if (group.headerRect.Contains(canvasPos) || CheckBorderSelection (state, group.rect, canvasPos, out border))
return group;
}
return null;
}
/// <summary>
/// Returns true if the mouse position is on the border of the focused node and outputs the border as a flag in selection
/// </summary>
private static bool CheckBorderSelection(NodeEditorState state, Rect rect, Vector2 canvasPos, out BorderSelection selection)
{
selection = 0;
if (!rect.Contains (canvasPos))
return false;
Vector2 min = new Vector2(rect.xMin + borderWidth, rect.yMax - borderWidth);
Vector2 max = new Vector2(rect.xMax - borderWidth, rect.yMin + borderWidth);
// Check bordes and mark flags accordingly
if (canvasPos.x < min.x)
selection = BorderSelection.Left;
else if (canvasPos.x > max.x)
selection = BorderSelection.Right;
if (canvasPos.y < max.y)
selection |= BorderSelection.Top;
else if (canvasPos.y > min.y)
selection |= BorderSelection.Bottom;
return selection != BorderSelection.None;
}
/// <summary>
/// Gets the rect that represents the passed border flag in the passed rect
/// </summary>
private static Rect getBorderRect (Rect rect, BorderSelection border)
{
Rect borderRect = rect;
if ((border&BorderSelection.Left) != 0)
borderRect.xMax = borderRect.xMin + borderWidth;
else if ((border&BorderSelection.Right) != 0)
borderRect.xMin = borderRect.xMax - borderWidth;
if ((border&BorderSelection.Top) != 0)
borderRect.yMax = borderRect.yMin + borderWidth;
else if ((border&BorderSelection.Bottom) != 0)
borderRect.yMin = borderRect.yMax - borderWidth;
return borderRect;
}
#endregion
#region Input
/// <summary>
/// Handles creation of the group in the editor through a context menu item
/// </summary>
[ContextEntryAttribute (ContextType.Canvas, "Create Group")]
private static void CreateGroup (NodeEditorInputInfo info)
{
info.SetAsCurrentEnvironment();
new NodeGroup ("Group", NodeEditor.ScreenToCanvasSpace (info.inputPos));
}
/// <summary>
/// Handles the group context click (on the header only)
/// </summary>
[EventHandlerAttribute (EventType.MouseDown, -1)] // Before the other context clicks because they won't account for groups
private static void HandleGroupContextClick (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
if (inputInfo.inputEvent.button == 1 && state.focusedNode == null)
{ // Right-click NOT on a node
NodeGroup focusedGroup = HeaderAtPosition (state, NodeEditor.ScreenToCanvasSpace (inputInfo.inputPos));
if (focusedGroup != null)
{ // Context click for the group. This is static, not dynamic, because it would be useless
GenericMenu context = new GenericMenu ();
context.AddItem (new GUIContent ("Delete"), false, () => { NodeEditor.curNodeCanvas = state.canvas; focusedGroup.Delete (); });
context.ShowAsContext ();
inputInfo.inputEvent.Use ();
}
}
}
/// <summary>
/// Starts a dragging operation for either dragging or resizing (on the header or borders only)
/// </summary>
[EventHandlerAttribute (EventType.MouseDown, 104)] // Priority over hundred to make it call after the GUI, and before Node dragging (110) and window panning (105)
private static void HandleGroupDraggingStart (NodeEditorInputInfo inputInfo)
{
if (GUIUtility.hotControl > 0)
return; // GUI has control
NodeEditorState state = inputInfo.editorState;
if (inputInfo.inputEvent.button == 0 && state.focusedNode == null && state.dragNode == false)
{ // Do not interfere with other dragging stuff
NodeGroup focusedGroup = GroupAtPositionInput (state, NodeEditor.ScreenToCanvasSpace (inputInfo.inputPos));
if (focusedGroup != null)
{ // Start dragging the focused group
UpdateGroupOrder();
Vector2 canvasInputPos = NodeEditor.ScreenToCanvasSpace(inputInfo.inputPos);
if (CheckBorderSelection (state, focusedGroup.rect, canvasInputPos, out NodeGroup.resizeDir))
{ // Resizing
state.activeGroup = focusedGroup;
// Get start drag position
Vector2 startSizePos = Vector2.zero;
if ((NodeGroup.resizeDir&BorderSelection.Left) != 0)
startSizePos.x = focusedGroup.rect.xMin;
else if ((NodeGroup.resizeDir&BorderSelection.Right) != 0)
startSizePos.x = focusedGroup.rect.xMax;
if ((NodeGroup.resizeDir&BorderSelection.Top) != 0)
startSizePos.y = focusedGroup.rect.yMin;
else if ((NodeGroup.resizeDir&BorderSelection.Bottom) != 0)
startSizePos.y = focusedGroup.rect.yMax;
// Start the resize drag
state.StartDrag ("group", inputInfo.inputPos, startSizePos);
state.resizeGroup = true;
inputInfo.inputEvent.Use ();
}
else if (focusedGroup.headerRect.Contains (canvasInputPos))
{ // Dragging
state.activeGroup = focusedGroup;
state.StartDrag ("group", inputInfo.inputPos, state.activeGroup.rect.position);
state.activeGroup.UpdatePins ();
inputInfo.inputEvent.Use ();
}
}
}
}
/// <summary>
/// Updates the dragging operation for either dragging or resizing
/// </summary>
[EventHandlerAttribute (EventType.MouseDrag)]
private static void HandleGroupDragging (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
NodeGroup active = state.activeGroup;
if (active != null)
{ // Handle dragging and resizing of active group
if (state.dragUserID != "group")
{
state.activeGroup = null;
state.resizeGroup = false;
return;
}
// Update drag operation
Vector2 dragChange = state.UpdateDrag ("group", inputInfo.inputPos);
Vector2 newSizePos = state.dragObjectPos;
if (state.resizeGroup)
{ // Resizing -> Apply drag to selected borders while keeping a minimum size
// Note: Binary operator and !=0 checks of the flag is enabled, but not necessarily the only flag (in which case you would use ==)
Rect r = active.rect;
if ((NodeGroup.resizeDir&BorderSelection.Left) != 0)
active.rect.xMin = r.xMax - Math.Max (minGroupSize, r.xMax - newSizePos.x);
else if ((NodeGroup.resizeDir&BorderSelection.Right) != 0)
active.rect.xMax = r.xMin + Math.Max (minGroupSize, newSizePos.x - r.xMin);
if ((NodeGroup.resizeDir&BorderSelection.Top) != 0)
active.rect.yMin = r.yMax - Math.Max (minGroupSize, r.yMax - newSizePos.y);
else if ((NodeGroup.resizeDir&BorderSelection.Bottom) != 0)
active.rect.yMax = r.yMin + Math.Max (minGroupSize, newSizePos.y - r.yMin);
}
else
{ // Dragging -> Apply drag to body and pinned nodes
active.rect.position = newSizePos;
foreach (Node pinnedNode in active.pinnedNodes)
pinnedNode.position += dragChange;
foreach (NodeGroup pinnedGroup in active.pinnedGroups)
pinnedGroup.rect.position += dragChange;
}
inputInfo.inputEvent.Use ();
NodeEditor.RepaintClients ();
}
}
/// <summary>
/// Ends the dragging operation for either dragging or resizing
/// </summary>
[EventHandlerAttribute (EventType.MouseDown)]
[EventHandlerAttribute (EventType.MouseUp)]
private static void HandleDraggingEnd (NodeEditorInputInfo inputInfo)
{
if (inputInfo.editorState.dragUserID == "group")
{
// if (inputInfo.editorState.activeGroup != null )
// inputInfo.editorState.activeGroup.UpdatePins ();
inputInfo.editorState.EndDrag ("group");
NodeEditor.RepaintClients();
}
UpdateGroupOrder();
inputInfo.editorState.activeGroup = null;
inputInfo.editorState.resizeGroup = false;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,243 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
[System.Serializable]
public class ValueConnectionKnob : ConnectionKnob
{
// Connections
new public List<ValueConnectionKnob> connections { get { return _connections.OfType<ValueConnectionKnob> ().ToList (); } }
// Knob Style
protected override Type styleBaseClass { get { return typeof(ValueConnectionType); } }
new protected ValueConnectionType ConnectionStyle { get { CheckConnectionStyle (); return (ValueConnectionType)_connectionStyle; } }
// Knob Value
public Type valueType { get { return ConnectionStyle.Type; } }
public bool IsValueNull { get { return value == null; } }
[System.NonSerialized]
private object _value = null;
private object value {
get { return _value; }
set {
_value = value;
if (direction == Direction.Out)
{
foreach (ValueConnectionKnob connectionKnob in connections)
connectionKnob.SetValue(value);
}
}
}
public void Init (Node body, string name, Direction dir, string type)
{
base.Init (body, name, dir);
styleID = type;
}
public void Init (Node body, string name, Direction dir, string type, NodeSide nodeSide, float nodeSidePosition = 0)
{
base.Init (body, name, dir, nodeSide, nodeSidePosition);
styleID = type;
}
new public ValueConnectionKnob connection (int index)
{
if (connections.Count <= index)
throw new IndexOutOfRangeException ("connections[" + index + "] of '" + name + "'");
return connections[index];
}
public override bool CanApplyConnection (ConnectionPort port)
{
ValueConnectionKnob valueKnob = port as ValueConnectionKnob;
if (valueKnob == null || !valueType.IsAssignableFrom (valueKnob.valueType))
return false;
return base.CanApplyConnection (port);
}
#region Knob Value
/// <summary>
/// Gets the knob value anonymously. Not advised as it may lead to unwanted behaviour!
/// </summary>
public object GetValue ()
{
return value;
}
/// <summary>
/// Gets the output value if the type matches or null. If possible, use strongly typed version instead.
/// </summary>
public object GetValue (Type type)
{
if (type == null)
throw new ArgumentException ("Trying to GetValue of knob " + name + " with null type!");
if (type.IsAssignableFrom (valueType))
return value?? (value = GetDefault (type));
throw new ArgumentException ("Trying to GetValue of type " + type.FullName + " for Output Type: " + valueType.FullName);
}
/// <summary>
/// Sets the output value if the type matches. If possible, use strongly typed version instead.
/// </summary>
public void SetValue (object Value)
{
if (Value != null && !valueType.IsAssignableFrom (Value.GetType ()))
throw new ArgumentException("Trying to SetValue of type " + Value.GetType().FullName + " for Output Type: " + valueType.FullName);
value = Value;
}
/// <summary>
/// Gets the output value if the type matches
/// </summary>
/// <returns>Value, if null default(T) (-> For reference types, null. For value types, default value)</returns>
public T GetValue<T> ()
{
if (typeof(T).IsAssignableFrom (valueType))
return (T)(value?? (value = GetDefault<T> ()));
Debug.LogError ("Trying to GetValue<" + typeof(T).FullName + "> for Output Type: " + valueType.FullName);
return GetDefault<T> ();
}
/// <summary>
/// Sets the output value if the type matches
/// </summary>
public void SetValue<T> (T Value)
{
if (valueType.IsAssignableFrom (typeof(T)))
value = Value;
else
Debug.LogError ("Trying to SetValue<" + typeof(T).FullName + "> for Output Type: " + valueType.FullName);
}
/// <summary>
/// Resets the output value to null.
/// </summary>
public void ResetValue ()
{
value = null;
}
/// <summary>
/// Returns the default value of type when a default constructor is existant or type is a value type, else null
/// </summary>
private static T GetDefault<T> ()
{
// Try to create using an empty constructor if existant
if (typeof(T).GetConstructor (System.Type.EmptyTypes) != null)
return System.Activator.CreateInstance<T> ();
// Else try to get default. Returns null only on reference types
return default(T);
}
/// <summary>
/// Returns the default value of type when a default constructor is existant, else null
/// </summary>
private static object GetDefault (Type type)
{
// Try to create using an empty constructor if existant
if (type.GetConstructor (System.Type.EmptyTypes) != null)
return System.Activator.CreateInstance (type);
return null;
}
#endregion
}
[AttributeUsage(AttributeTargets.Field)]
public class ValueConnectionKnobAttribute : ConnectionKnobAttribute
{
public Type ValueType;
public override Type ConnectionType { get { return typeof(ValueConnectionKnob); } }
public ValueConnectionKnobAttribute(string name, Direction direction, string type)
: base(name, direction, type) { }
public ValueConnectionKnobAttribute(string name, Direction direction, string type, ConnectionCount maxCount)
: base(name, direction, type, maxCount) { }
public ValueConnectionKnobAttribute(string name, Direction direction, string type, NodeSide nodeSide, float nodeSidePos = 0)
: base(name, direction, type, nodeSide, nodeSidePos) { }
public ValueConnectionKnobAttribute(string name, Direction direction, string type, ConnectionCount maxCount, NodeSide nodeSide, float nodeSidePos = 0)
: base(name, direction, type, maxCount, nodeSide, nodeSidePos) { }
// Directly typed
public ValueConnectionKnobAttribute(string name, Direction direction, Type type)
: base(name, direction) { Setup(type); }
public ValueConnectionKnobAttribute(string name, Direction direction, Type type, ConnectionCount maxCount)
: base(name, direction, maxCount) { Setup(type); }
public ValueConnectionKnobAttribute(string name, Direction direction, Type type, NodeSide nodeSide, float nodeSidePos = 0)
: base(name, direction, nodeSide, nodeSidePos) { Setup(type); }
public ValueConnectionKnobAttribute(string name, Direction direction, Type type, ConnectionCount maxCount, NodeSide nodeSide, float nodeSidePos = 0)
: base(name, direction, maxCount, nodeSide, nodeSidePos) { Setup(type); }
protected void Setup(Type type)
{
StyleID = type.FullName;
ValueType = type;
ConnectionPortStyles.GetValueConnectionType(type);
}
public override bool IsCompatibleWith (ConnectionPort port)
{
if (!(Direction == Direction.None && port.direction == Direction.None)
&& !(Direction == Direction.In && port.direction == Direction.Out)
&& !(Direction == Direction.Out && port.direction == Direction.In))
return false;
ValueConnectionKnob valueKnob = port as ValueConnectionKnob;
if (valueKnob == null)
return false;
Type knobType = ConnectionPortStyles.GetValueType (StyleID);
return knobType.IsAssignableFrom (valueKnob.valueType);
}
public override ConnectionPort CreateNew (Node body)
{
ValueConnectionKnob knob = ScriptableObject.CreateInstance<ValueConnectionKnob> ();
knob.Init (body, Name, Direction, StyleID, NodeSide, NodeSidePos);
knob.maxConnectionCount = MaxConnectionCount;
return knob;
}
public override void UpdateProperties (ConnectionPort port)
{
ValueConnectionKnob knob = (ValueConnectionKnob)port;
knob.name = Name;
knob.direction = Direction;
knob.styleID = StyleID;
knob.maxConnectionCount = MaxConnectionCount;
knob.side = NodeSide;
if (NodeSidePos != 0)
knob.sidePosition = NodeSidePos;
knob.sideOffset = 0;
}
}
[ReflectionUtility.ReflectionSearchIgnoreAttribute ()]
public class ValueConnectionType : ConnectionKnobStyle
{
protected Type type;
public virtual Type Type { get { return type; } }
public ValueConnectionType () : base () { }
public ValueConnectionType (Type valueType) : base (valueType.FullName)
{
identifier = valueType.FullName;
type = valueType;
}
public override bool isValid ()
{
bool valid = Type != null && InKnobTex != null && OutKnobTex != null;
if (!valid)
Debug.LogError("Type " + Identifier + " is invalid! Type-Null?" + (type == null) + ", InTex-Null?" + (InKnobTex == null) + ", OutTex-Null?" + (OutKnobTex == null));
return valid;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 017e2c7fd038ecb46b317c1e003f31dc
timeCreated: 1498244881
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,105 @@
using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
/// <summary>
/// Handles fetching and storing of all ConnectionPortStyle declarations
/// </summary>
public static class ConnectionPortStyles
{
private static Dictionary<string, ConnectionPortStyle> connectionPortStyles;
private static Dictionary<string, ValueConnectionType> connectionValueTypes;
/// <summary>
/// Fetches every ConnectionPortStyle, ConnectionKnobStyle or ValueConnectionType declaration in the script assemblies to provide the framework with custom connection port styles
/// </summary>
public static void FetchConnectionPortStyles ()
{
connectionPortStyles = new Dictionary<string, ConnectionPortStyle> ();
connectionValueTypes = new Dictionary<string, ValueConnectionType> ();
foreach (Type type in ReflectionUtility.getSubTypes (typeof(ConnectionPortStyle)))
{
ConnectionPortStyle portStyle = (ConnectionPortStyle)Activator.CreateInstance (type);
if (portStyle == null)
throw new UnityException ("Error with Connection Port Style Declaration " + type.FullName);
if (!portStyle.isValid ())
throw new Exception (type.BaseType.Name + " declaration " + portStyle.Identifier + " is invalid!");
if (connectionPortStyles.ContainsKey (portStyle.Identifier))
throw new Exception ("Duplicate ConnectionPortStyle declaration " + portStyle.Identifier + "!");
connectionPortStyles.Add (portStyle.Identifier, portStyle);
if (type.IsSubclassOf (typeof(ValueConnectionType)))
connectionValueTypes.Add (portStyle.Identifier, (ValueConnectionType)portStyle);
if (!portStyle.isValid())
Debug.LogError("Style " + portStyle.Identifier + " is invalid!");
}
}
/// <summary>
/// Gets the ValueConnectionType type the specified type name representates or creates it if not defined
/// </summary>
public static Type GetValueType (string typeName)
{
return ((ValueConnectionType)GetPortStyle (typeName, typeof(ValueConnectionType))).Type ?? typeof(void);
}
/// <summary>
/// Gets the ConnectionPortStyle for the specified style name or creates it if not defined
/// </summary>
public static ConnectionPortStyle GetPortStyle (string styleName, Type baseStyleClass = null)
{
if (connectionPortStyles == null || connectionPortStyles.Count == 0)
FetchConnectionPortStyles ();
if (baseStyleClass == null || !typeof(ConnectionPortStyle).IsAssignableFrom (typeof(ConnectionPortStyle)))
baseStyleClass = typeof(ConnectionPortStyle);
ConnectionPortStyle portStyle;
if (!connectionPortStyles.TryGetValue (styleName, out portStyle))
{ // No port style with the exact name exists
if (typeof(ValueConnectionType).IsAssignableFrom (baseStyleClass))
{ // A ValueConnectionType is searched, try by type name
Type type = Type.GetType (styleName);
if (type == null) // No type matching the name found either
{
Debug.LogError ("No ValueConnectionType could be found or created with name '" + styleName + "'!");
return null;
}
else // Matching type found, search or create type data based on type
portStyle = GetValueConnectionType(type);
}
else
{
portStyle = (ConnectionPortStyle)Activator.CreateInstance (baseStyleClass, styleName);
connectionPortStyles.Add (styleName, portStyle);
Debug.LogWarning("Created style from name " + styleName + "!");
}
}
if (!baseStyleClass.IsAssignableFrom (portStyle.GetType ()))
throw new Exception ("Cannot use Connection Style: '" + styleName + "' is not of type " + baseStyleClass.Name + "!");
if (!portStyle.isValid())
Debug.LogError("Fetched style " + portStyle.Identifier + " is invalid!");
return portStyle;
}
/// <summary>
/// Gets the ValueConnectionType for the specified type or creates it if not defined
/// </summary>
public static ValueConnectionType GetValueConnectionType (Type type)
{
if (connectionPortStyles == null || connectionPortStyles.Count == 0)
FetchConnectionPortStyles ();
ValueConnectionType valueType = connectionValueTypes.Values.FirstOrDefault ((ValueConnectionType data) => data.isValid () && data.Type == type);
if (valueType == null) // ValueConnectionType with type does not exist, create it
{
valueType = new ValueConnectionType (type);
connectionPortStyles.Add (type.FullName, valueType);
connectionValueTypes.Add (type.FullName, valueType);
}
return valueType;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4be5c7fe89350d249b9fd20d52cfb700
timeCreated: 1449162341
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
public class NodeCanvasManager
{
private static Dictionary<Type, NodeCanvasTypeData> CanvasTypes;
private static Action<Type> _menuCallback;
/// <summary>
/// Fetches every CanvasType Declaration in the script assemblies to provide the framework with custom canvas types
/// </summary>
public static void FetchCanvasTypes ()
{
CanvasTypes = new Dictionary<Type, NodeCanvasTypeData>();
foreach (Type type in ReflectionUtility.getSubTypes (typeof(NodeCanvas), typeof(NodeCanvasTypeAttribute)))
{
object[] nodeAttributes = type.GetCustomAttributes (typeof(NodeCanvasTypeAttribute), false);
NodeCanvasTypeAttribute attr = nodeAttributes[0] as NodeCanvasTypeAttribute;
CanvasTypes.Add(type, new NodeCanvasTypeData() { CanvasType = type, DisplayString = attr.Name });
}
}
/// <summary>
/// Returns all recorded canvas definitions found by the system
/// </summary>
public static List<NodeCanvasTypeData> getCanvasDefinitions ()
{
return CanvasTypes.Values.ToList ();
}
/// <summary>
/// Returns the NodeData for the given canvas
/// </summary>
public static NodeCanvasTypeData GetCanvasTypeData (NodeCanvas canvas)
{
return GetCanvasTypeData (canvas.GetType ());
}
/// <summary>
/// Returns the NodeData for the given canvas type
/// </summary>
public static NodeCanvasTypeData GetCanvasTypeData (Type canvasType)
{
NodeCanvasTypeData data;
CanvasTypes.TryGetValue (canvasType, out data);
return data;
}
/// <summary>
/// Returns the NodeData for the given canvas name (type name, display string, etc.)
/// </summary>
public static NodeCanvasTypeData GetCanvasTypeData (string name)
{
return CanvasTypes.Values.FirstOrDefault ((NodeCanvasTypeData data) => data.CanvasType.FullName.Contains (name) || data.DisplayString.Contains (name) || name.Contains (data.DisplayString));
}
/// <summary>
/// Checks whether the süecified nodeID is compatible with the given canvas type
/// </summary>
public static bool CheckCanvasCompability (string nodeID, Type canvasType)
{
NodeTypeData data = NodeTypes.getNodeData (nodeID);
return data.limitToCanvasTypes == null || data.limitToCanvasTypes.Length == 0 || data.limitToCanvasTypes.Contains (canvasType);
}
/// <summary>
/// Converts the given canvas to the specified type
/// </summary>
public static NodeCanvas ConvertCanvasType (NodeCanvas canvas, Type newType)
{
NodeCanvas convertedCanvas = canvas;
if (canvas.GetType () != newType && newType.IsSubclassOf (typeof(NodeCanvas)))
{
canvas.Validate();
canvas = NodeEditorSaveManager.CreateWorkingCopy (canvas);
convertedCanvas = NodeCanvas.CreateCanvas(newType);
convertedCanvas.nodes = canvas.nodes;
convertedCanvas.groups = canvas.groups;
convertedCanvas.editorStates = canvas.editorStates;
for (int i = 0; i < convertedCanvas.nodes.Count; i++)
{
if (!CheckCanvasCompability (convertedCanvas.nodes[i].GetID, newType))
{ // Check if nodes is even compatible with the canvas, if not delete it
convertedCanvas.nodes[i].Delete ();
i--;
}
}
convertedCanvas.Validate ();
}
return convertedCanvas;
}
#region Canvas Type Menu
public static void FillCanvasTypeMenu(ref GenericMenu menu, Action<Type> NodeCanvasSelection, string path = "")
{
_menuCallback = NodeCanvasSelection;
foreach (NodeCanvasTypeData data in CanvasTypes.Values)
menu.AddItem(new GUIContent(path + data.DisplayString), false, unwrapCanvasTypeCallback, (object)data);
}
#if UNITY_EDITOR
public static void FillCanvasTypeMenu(ref UnityEditor.GenericMenu menu, Action<Type> NodeCanvasSelection, string path = "")
{
_menuCallback = NodeCanvasSelection;
foreach (NodeCanvasTypeData data in CanvasTypes.Values)
menu.AddItem(new GUIContent(path + data.DisplayString), false, unwrapCanvasTypeCallback, (object)data);
}
#endif
private static void unwrapCanvasTypeCallback(object data)
{
NodeCanvasTypeData typeData = (NodeCanvasTypeData)data;
_menuCallback(typeData.CanvasType);
}
#endregion
}
public struct NodeCanvasTypeData
{
public string DisplayString;
public Type CanvasType;
}
public class NodeCanvasTypeAttribute : Attribute
{
public string Name;
public NodeCanvasTypeAttribute(string displayName)
{
Name = displayName;
}
}
}

View File

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

View File

@@ -0,0 +1,123 @@
using UnityEngine;
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
/// <summary>
/// Handles fetching and storing of all Node declarations
/// </summary>
public static class NodeTypes
{
private static Dictionary<string, NodeTypeData> nodes;
/// <summary>
/// Fetches every Node Declaration in the script assemblies to provide the framework with custom node types
/// </summary>
public static void FetchNodeTypes()
{
nodes = new Dictionary<string, NodeTypeData> ();
foreach (Type type in ReflectionUtility.getSubTypes (typeof(Node)))
{
object[] nodeAttributes = type.GetCustomAttributes(typeof(NodeAttribute), false);
NodeAttribute attr = nodeAttributes[0] as NodeAttribute;
if(attr == null || !attr.hide)
{ // Only regard if it is not marked as hidden
// Fetch node information
string ID, Title = "None";
FieldInfo IDField = type.GetField("ID");
if (IDField == null || attr == null)
{ // Cannot read ID from const field or need to read Title because of missing attribute -> Create sample to read from properties
Node sample = (Node)ScriptableObject.CreateInstance(type);
ID = sample.GetID;
Title = sample.Title;
UnityEngine.Object.DestroyImmediate(sample);
}
else // Can read ID directly from const field
ID = (string)IDField.GetValue(null);
// Create Data from information
NodeTypeData data = attr == null? // Switch between explicit information by the attribute or node information
new NodeTypeData(ID, Title, type, new Type[0]) :
new NodeTypeData(ID, attr.contextText, type, attr.limitToCanvasTypes);
nodes.Add (ID, data);
}
}
}
/// <summary>
/// Returns all recorded node definitions found by the system
/// </summary>
public static List<NodeTypeData> getNodeDefinitions ()
{
return nodes.Values.ToList ();
}
/// <summary>
/// Returns the NodeData for the given node type ID
/// </summary>
public static NodeTypeData getNodeData (string typeID)
{
NodeTypeData data;
nodes.TryGetValue (typeID, out data);
return data;
}
/// <summary>
/// Returns all node IDs that can automatically connect to the specified port.
/// If port is null, all node IDs are returned.
/// </summary>
public static List<string> getCompatibleNodes (ConnectionPort port)
{
if (port == null)
return NodeTypes.nodes.Keys.ToList ();
List<string> compatibleNodes = new List<string> ();
foreach (NodeTypeData nodeData in NodeTypes.nodes.Values)
{ // Iterate over all nodes to check compability of any of their connection ports
if (ConnectionPortManager.GetPortDeclarations (nodeData.typeID).Any (
(ConnectionPortDeclaration portDecl) => portDecl.portInfo.IsCompatibleWith (port)))
compatibleNodes.Add (nodeData.typeID);
}
return compatibleNodes;
}
}
/// <summary>
/// The NodeData contains the additional, editor specific data of a node type
/// </summary>
public struct NodeTypeData
{
public string typeID;
public string adress;
public Type type;
public Type[] limitToCanvasTypes;
public NodeTypeData(string ID, string name, Type nodeType, Type[] limitedCanvasTypes)
{
typeID = ID;
adress = name;
type = nodeType;
limitToCanvasTypes = limitedCanvasTypes;
}
}
/// <summary>
/// The NodeAttribute is used to specify editor specific data for a node type, later stored using a NodeData
/// </summary>
public class NodeAttribute : Attribute
{
public bool hide { get; private set; }
public string contextText { get; private set; }
public Type[] limitToCanvasTypes { get; private set; }
public NodeAttribute (bool HideNode, string ReplacedContextText, params Type[] limitedCanvasTypes)
{
hide = HideNode;
contextText = ReplacedContextText;
limitToCanvasTypes = limitedCanvasTypes;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,209 @@
using UnityEngine;
using System;
using System.Collections.Generic;
namespace NodeEditorFramework
{
public abstract partial class NodeEditorCallbackReceiver : MonoBehaviour
{
// Editor
public virtual void OnEditorStartUp () {}
// Save and Load
public virtual void OnLoadCanvas (NodeCanvas canvas) {}
public virtual void OnLoadEditorState (NodeEditorState editorState) {}
public virtual void OnSaveCanvas (NodeCanvas canvas) {}
public virtual void OnSaveEditorState (NodeEditorState editorState) {}
// Node
public virtual void OnAddNode (Node node) {}
public virtual void OnDeleteNode (Node node) {}
public virtual void OnMoveNode (Node node) {}
public virtual void OnAddConnectionPort (ConnectionPort knob) {}
// Connection
public virtual void OnAddConnection (ConnectionPort port1, ConnectionPort port2) {}
public virtual void OnRemoveConnection (ConnectionPort port1, ConnectionPort port2) {}
}
public static partial class NodeEditorCallbacks
{
private static int receiverCount;
private static List<NodeEditorCallbackReceiver> callbackReceiver;
public static void SetupReceivers ()
{
callbackReceiver = new List<NodeEditorCallbackReceiver> (MonoBehaviour.FindObjectsOfType<NodeEditorCallbackReceiver> ());
receiverCount = callbackReceiver.Count;
}
#region Editor (1)
public static Action OnEditorStartUp = null;
public static void IssueOnEditorStartUp ()
{
if (OnEditorStartUp != null)
OnEditorStartUp.Invoke ();
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnEditorStartUp ();
}
}
#endregion
#region Save and Load (4)
public static Action<NodeCanvas> OnLoadCanvas;
public static void IssueOnLoadCanvas (NodeCanvas canvas)
{
if (OnLoadCanvas != null)
OnLoadCanvas.Invoke (canvas);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnLoadCanvas (canvas) ;
}
}
public static Action<NodeEditorState> OnLoadEditorState;
public static void IssueOnLoadEditorState (NodeEditorState editorState)
{
if (OnLoadEditorState != null)
OnLoadEditorState.Invoke (editorState);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnLoadEditorState (editorState) ;
}
}
public static Action<NodeCanvas> OnSaveCanvas;
public static void IssueOnSaveCanvas (NodeCanvas canvas)
{
if (OnSaveCanvas != null)
OnSaveCanvas.Invoke (canvas);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnSaveCanvas (canvas) ;
}
}
public static Action<NodeEditorState> OnSaveEditorState;
public static void IssueOnSaveEditorState (NodeEditorState editorState)
{
if (OnSaveEditorState != null)
OnSaveEditorState.Invoke (editorState);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnSaveEditorState (editorState) ;
}
}
#endregion
#region Node (4)
public static Action<Node> OnAddNode;
public static void IssueOnAddNode (Node node)
{
if (OnAddNode != null)
OnAddNode.Invoke (node);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnAddNode (node);
}
}
public static Action<Node> OnDeleteNode;
public static void IssueOnDeleteNode (Node node)
{
if (OnDeleteNode != null)
OnDeleteNode.Invoke (node);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnDeleteNode (node);
}
node.OnDelete ();
}
public static Action<Node> OnMoveNode;
public static void IssueOnMoveNode (Node node)
{
if (OnMoveNode != null)
OnMoveNode.Invoke (node);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnMoveNode (node);
}
}
public static Action<ConnectionPort> OnAddConnectionPort;
public static void IssueOnAddConnectionPort (ConnectionPort connectionPort)
{
if (OnAddConnectionPort != null)
OnAddConnectionPort.Invoke (connectionPort);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnAddConnectionPort (connectionPort);
}
}
#endregion
#region Connection (2)
public static Action<ConnectionPort, ConnectionPort> OnAddConnection;
public static void IssueOnAddConnection (ConnectionPort port1, ConnectionPort port2)
{
if (OnAddConnection != null)
OnAddConnection.Invoke (port1, port2);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnAddConnection (port1, port2);
}
}
public static Action<ConnectionPort, ConnectionPort> OnRemoveConnection;
public static void IssueOnRemoveConnection (ConnectionPort port1, ConnectionPort port2)
{
if (OnRemoveConnection != null)
OnRemoveConnection.Invoke (port1, port2);
for (int cnt = 0; cnt < receiverCount; cnt++)
{
if (callbackReceiver [cnt] == null)
callbackReceiver.RemoveAt (cnt--);
else
callbackReceiver [cnt].OnRemoveConnection (port1, port2);
}
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,261 @@
using UnityEngine;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
public enum ConnectionDrawMethod { Bezier, StraightLine }
public static partial class NodeEditorGUI
{
internal static bool isEditorWindow;
// static GUI settings, textures and styles
public static int knobSize = 16;
public static Color NE_LightColor = new Color (0.4f, 0.4f, 0.4f);
public static Color NE_TextColor = new Color(0.8f, 0.8f, 0.8f);
public static Texture2D Background;
public static Texture2D AALineTex;
public static Texture2D GUIBox;
public static Texture2D GUIButton;
public static Texture2D GUIBoxSelection;
public static Texture2D GUIToolbar;
public static Texture2D GUIToolbarButton;
public static GUISkin nodeSkin;
public static GUISkin defaultSkin;
public static GUIStyle nodeLabel;
public static GUIStyle nodeLabelBold;
public static GUIStyle nodeLabelSelected;
public static GUIStyle nodeLabelCentered;
public static GUIStyle nodeLabelBoldCentered;
public static GUIStyle nodeLabelLeft;
public static GUIStyle nodeLabelRight;
public static GUIStyle nodeBox;
public static GUIStyle nodeBoxBold;
public static GUIStyle toolbar;
public static GUIStyle toolbarLabel;
public static GUIStyle toolbarDropdown;
public static GUIStyle toolbarButton;
public static bool Init ()
{
// Textures
Background = ResourceManager.LoadTexture ("Textures/background.png");
AALineTex = ResourceManager.LoadTexture ("Textures/AALine.png");
GUIBox = ResourceManager.LoadTexture ("Textures/NE_Box.png");
GUIButton = ResourceManager.LoadTexture ("Textures/NE_Button.png");
//GUIBoxSelection = ResourceManager.LoadTexture("Textures/BoxSelection.png");
GUIToolbar = ResourceManager.LoadTexture("Textures/NE_Toolbar.png");
GUIToolbarButton = ResourceManager.LoadTexture("Textures/NE_ToolbarButton.png");
if (!Background || !AALineTex || !GUIBox || !GUIButton || !GUIToolbar || !GUIToolbarButton)
return false;
// Skin & Styles
nodeSkin = Object.Instantiate (GUI.skin);
GUI.skin = nodeSkin;
foreach (GUIStyle style in GUI.skin)
{
style.fontSize = 11;
//style.normal.textColor = style.active.textColor = style.focused.textColor = style.hover.textColor = NE_TextColor;
}
// Label
nodeSkin.label.normal.textColor = NE_TextColor;
nodeLabel = nodeSkin.label;
nodeLabelBold = new GUIStyle (nodeLabel) { fontStyle = FontStyle.Bold };
nodeLabelSelected = new GUIStyle (nodeLabel);
nodeLabelSelected.normal.background = RTEditorGUI.ColorToTex (1, NE_LightColor);
nodeLabelCentered = new GUIStyle (nodeLabel) { alignment = TextAnchor.MiddleCenter };
nodeLabelBoldCentered = new GUIStyle (nodeLabelBold) { alignment = TextAnchor.MiddleCenter };
nodeLabelLeft = new GUIStyle (nodeLabel) { alignment = TextAnchor.MiddleLeft };
nodeLabelRight = new GUIStyle (nodeLabel) { alignment = TextAnchor.MiddleRight };
// Box
nodeSkin.box.normal.background = GUIBox;
nodeSkin.box.normal.textColor = NE_TextColor;
nodeSkin.box.active.textColor = NE_TextColor;
nodeBox = nodeSkin.box;
nodeBoxBold = new GUIStyle (nodeBox) { fontStyle = FontStyle.Bold };
// Button
nodeSkin.button.normal.textColor = NE_TextColor;
nodeSkin.button.normal.background = GUIButton;
// Toolbar
toolbar = GUI.skin.FindStyle("toolbar");
toolbarButton = GUI.skin.FindStyle("toolbarButton");
toolbarLabel = GUI.skin.FindStyle("toolbarButton");
toolbarDropdown = GUI.skin.FindStyle("toolbarDropdown");
if (toolbar == null || toolbarButton == null || toolbarLabel == null || toolbarDropdown == null)
{ // No editor styles available - use custom skin
toolbar = new GUIStyle(nodeSkin.box);
toolbar.normal.background = GUIToolbar;
toolbar.active.background = GUIToolbar;
toolbar.border = new RectOffset(0, 0, 1, 1);
toolbar.margin = new RectOffset(0, 0, 0, 0);
toolbar.padding = new RectOffset(10, 10, 1, 1);
toolbarLabel = new GUIStyle(nodeSkin.box);
toolbarLabel.normal.background = GUIToolbarButton;
toolbarLabel.border = new RectOffset(2, 2, 0, 0);
toolbarLabel.margin = new RectOffset(-2, -2, 0, 0);
toolbarLabel.padding = new RectOffset(6, 6, 4, 4);
toolbarButton = new GUIStyle(toolbarLabel);
toolbarButton.active.background = RTEditorGUI.ColorToTex(1, NE_LightColor);
toolbarDropdown = new GUIStyle(toolbarButton);
}
GUI.skin = null;
return true;
}
public static void StartNodeGUI (bool IsEditorWindow)
{
NodeEditor.checkInit(true);
isEditorWindow = IsEditorWindow;
defaultSkin = GUI.skin;
if (nodeSkin != null)
GUI.skin = nodeSkin;
}
public static void EndNodeGUI ()
{
GUI.skin = defaultSkin;
}
#region Connection Drawing
// Curve parameters
public static float curveBaseDirection = 1.5f, curveBaseStart = 2f, curveDirectionScale = 0.004f;
/// <summary>
/// Draws a node connection from start to end, horizontally
/// </summary>
public static void DrawConnection (Vector2 startPos, Vector2 endPos, Color col)
{
Vector2 startVector = startPos.x <= endPos.x? Vector2.right : Vector2.left;
DrawConnection (startPos, startVector, endPos, -startVector, col);
}
/// <summary>
/// Draws a node connection from start to end, horizontally
/// </summary>
public static void DrawConnection (Vector2 startPos, Vector2 endPos, ConnectionDrawMethod drawMethod, Color col)
{
Vector2 startVector = startPos.x <= endPos.x? Vector2.right : Vector2.left;
DrawConnection (startPos, startVector, endPos, -startVector, drawMethod, col);
}
/// <summary>
/// Draws a node connection from start to end with specified vectors
/// </summary>
public static void DrawConnection (Vector2 startPos, Vector2 startDir, Vector2 endPos, Vector2 endDir, Color col)
{
#if NODE_EDITOR_LINE_CONNECTION
DrawConnection (startPos, startDir, endPos, endDir, ConnectionDrawMethod.StraightLine, col);
#else
DrawConnection (startPos, startDir, endPos, endDir, ConnectionDrawMethod.Bezier, col);
#endif
}
/// <summary>
/// Draws a node connection from start to end with specified vectors
/// </summary>
public static void DrawConnection (Vector2 startPos, Vector2 startDir, Vector2 endPos, Vector2 endDir, ConnectionDrawMethod drawMethod, Color col)
{
if (drawMethod == ConnectionDrawMethod.Bezier)
{
NodeEditorGUI.OptimiseBezierDirections (startPos, ref startDir, endPos, ref endDir);
float dirFactor = 80;//Mathf.Pow ((startPos-endPos).magnitude, 0.3f) * 20;
//Debug.Log ("DirFactor is " + dirFactor + "with a bezier lenght of " + (startPos-endPos).magnitude);
RTEditorGUI.DrawBezier (startPos, endPos, startPos + startDir * dirFactor, endPos + endDir * dirFactor, col * Color.gray, null, 3);
}
else if (drawMethod == ConnectionDrawMethod.StraightLine)
RTEditorGUI.DrawLine (startPos, endPos, col * Color.gray, null, 3);
}
/// <summary>
/// Optimises the bezier directions scale so that the bezier looks good in the specified position relation.
/// Only the magnitude of the directions are changed, not their direction!
/// </summary>
public static void OptimiseBezierDirections (Vector2 startPos, ref Vector2 startDir, Vector2 endPos, ref Vector2 endDir)
{
Vector2 offset = (endPos - startPos) * curveDirectionScale;
float baseDir = Mathf.Min (offset.magnitude/curveBaseStart, 1) * curveBaseDirection;
Vector2 scale = new Vector2 (Mathf.Abs (offset.x) + baseDir, Mathf.Abs (offset.y) + baseDir);
// offset.x and offset.y linearly increase at scale of curveDirectionScale
// For 0 < offset.magnitude < curveBaseStart, baseDir linearly increases from 0 to curveBaseDirection. For offset.magnitude > curveBaseStart, baseDir = curveBaseDirection
startDir = Vector2.Scale(startDir.normalized, scale);
endDir = Vector2.Scale(endDir.normalized, scale);
}
/// <summary>
/// Gets the second connection vector that matches best, accounting for positions
/// </summary>
internal static Vector2 GetSecondConnectionVector (Vector2 startPos, Vector2 endPos, Vector2 firstVector)
{
if (firstVector.x != 0 && firstVector.y == 0)
return startPos.x <= endPos.x? -firstVector : firstVector;
else if (firstVector.y != 0 && firstVector.x == 0)
return startPos.y <= endPos.y? -firstVector : firstVector;
else
return -firstVector;
}
#endregion
/// <summary>
/// Unified method to generate a random HSV color value across versions
/// </summary>
public static Color RandomColorHSV(int seed, float hueMin, float hueMax, float saturationMin, float saturationMax, float valueMin, float valueMax)
{
// Set seed
#if UNITY_5_4_OR_NEWER
UnityEngine.Random.InitState (seed);
#else
UnityEngine.Random.seed = seed;
#endif
// Consistent random H,S,V values
float hue = UnityEngine.Random.Range(hueMin, hueMax);
float saturation = UnityEngine.Random.Range(saturationMin, saturationMax);
float value = UnityEngine.Random.Range(valueMin, valueMax);
// Convert HSV to RGB
#if UNITY_5_3_OR_NEWER
return UnityEngine.Color.HSVToRGB (hue, saturation, value, false);
#else
int hi = Mathf.FloorToInt(hue / 60) % 6;
float frac = hue / 60 - Mathf.Floor(hue / 60);
float v = value;
float p = value * (1 - saturation);
float q = value * (1 - frac * saturation);
float t = value * (1 - (1 - frac) * saturation);
if (hi == 0)
return new Color(v, t, p);
else if (hi == 1)
return new Color(q, v, p);
else if (hi == 2)
return new Color(p, v, t);
else if (hi == 3)
return new Color(p, q, v);
else if (hi == 4)
return new Color(t, p, v);
else
return new Color(v, p, q);
#endif
}
}
}

View File

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

View File

@@ -0,0 +1,317 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
/// <summary>
/// Collection of default Node Editor controls for the NodeEditorInputSystem
/// </summary>
public static class NodeEditorInputControls
{
#region Canvas Context Entries
[ContextFillerAttribute (ContextType.Canvas)]
private static void FillAddNodes (NodeEditorInputInfo inputInfo, GenericMenu canvasContextMenu)
{ // Fill context menu with nodes to add to the canvas
NodeEditorState state = inputInfo.editorState;
List<string> nodes = NodeTypes.getCompatibleNodes (state.connectKnob);
foreach (string node in nodes)
{ // Only add nodes to the context menu that are compatible
if (NodeCanvasManager.CheckCanvasCompability (node, inputInfo.editorState.canvas.GetType ()) && inputInfo.editorState.canvas.CanAddNode (node))
canvasContextMenu.AddItem (new GUIContent ("Add " + NodeTypes.getNodeData(node).adress), false, CreateNodeCallback, new NodeEditorInputInfo (node, state));
}
}
private static void CreateNodeCallback (object infoObj)
{
NodeEditorInputInfo callback = infoObj as NodeEditorInputInfo;
if (callback == null)
throw new UnityException ("Callback Object passed by context is not of type NodeEditorInputInfo!");
callback.SetAsCurrentEnvironment ();
Node.Create (callback.message, NodeEditor.ScreenToCanvasSpace (callback.inputPos), callback.editorState.connectKnob);
callback.editorState.connectKnob = null;
NodeEditor.RepaintClients ();
}
#endregion
#region Node Context Entries
[ContextEntryAttribute (ContextType.Node, "Delete Node")]
private static void DeleteNode (NodeEditorInputInfo inputInfo)
{
inputInfo.SetAsCurrentEnvironment ();
if (inputInfo.editorState.focusedNode != null)
{
inputInfo.editorState.focusedNode.Delete ();
inputInfo.inputEvent.Use ();
}
}
[ContextEntryAttribute (ContextType.Node, "Duplicate Node")]
private static void DuplicateNode (NodeEditorInputInfo inputInfo)
{
inputInfo.SetAsCurrentEnvironment ();
NodeEditorState state = inputInfo.editorState;
if (state.focusedNode != null && NodeEditor.curNodeCanvas.CanAddNode (state.focusedNode.GetID))
{ // Create new node of same type
Node duplicatedNode = Node.Create (state.focusedNode.GetID, NodeEditor.ScreenToCanvasSpace (inputInfo.inputPos), state.connectKnob);
state.selectedNode = state.focusedNode = duplicatedNode;
state.connectKnob = null;
inputInfo.inputEvent.Use ();
}
}
[HotkeyAttribute(KeyCode.Delete, EventType.KeyUp)]
private static void DeleteNodeKey(NodeEditorInputInfo inputInfo)
{
if (GUIUtility.keyboardControl > 0)
return;
if (inputInfo.editorState.focusedNode != null)
{
inputInfo.SetAsCurrentEnvironment();
inputInfo.editorState.focusedNode.Delete();
inputInfo.inputEvent.Use();
}
}
#endregion
#region Node Keyboard Control
// Main Keyboard_Move method
[HotkeyAttribute(KeyCode.UpArrow, EventType.KeyDown)]
[HotkeyAttribute(KeyCode.LeftArrow, EventType.KeyDown)]
[HotkeyAttribute(KeyCode.RightArrow, EventType.KeyDown)]
[HotkeyAttribute(KeyCode.DownArrow, EventType.KeyDown)]
private static void KB_MoveNode(NodeEditorInputInfo inputInfo)
{
if (GUIUtility.keyboardControl > 0)
return;
NodeEditorState state = inputInfo.editorState;
if (state.selectedNode != null)
{
Vector2 pos = state.selectedNode.rect.position;
int shiftAmount = inputInfo.inputEvent.shift? 50 : 10;
if (inputInfo.inputEvent.keyCode == KeyCode.RightArrow)
pos = new Vector2(pos.x + shiftAmount, pos.y);
else if (inputInfo.inputEvent.keyCode == KeyCode.LeftArrow)
pos = new Vector2(pos.x - shiftAmount, pos.y);
else if (inputInfo.inputEvent.keyCode == KeyCode.DownArrow)
pos = new Vector2(pos.x, pos.y + shiftAmount);
else if (inputInfo.inputEvent.keyCode == KeyCode.UpArrow)
pos = new Vector2(pos.x, pos.y - shiftAmount);
state.selectedNode.position = pos;
inputInfo.inputEvent.Use();
}
NodeEditor.RepaintClients();
}
#endregion
#region Node Dragging
[EventHandlerAttribute (EventType.MouseDown, 110)] // Priority over hundred to make it call after the GUI
private static void HandleNodeDraggingStart (NodeEditorInputInfo inputInfo)
{
if (GUIUtility.hotControl > 0)
return; // GUI has control
NodeEditorState state = inputInfo.editorState;
if (inputInfo.inputEvent.button == 0 && state.focusedNode != null && state.focusedNode == state.selectedNode && state.focusedConnectionKnob == null)
{ // Clicked inside the selected Node, so start dragging it
state.dragNode = true;
state.StartDrag ("node", inputInfo.inputPos, state.focusedNode.rect.position);
}
}
[EventHandlerAttribute (EventType.MouseDrag)]
private static void HandleNodeDragging (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
if (state.dragNode)
{ // If conditions apply, drag the selected node, else disable dragging
if (state.selectedNode != null && inputInfo.editorState.dragUserID == "node")
{ // Apply new position for the dragged node
state.UpdateDrag ("node", inputInfo.inputPos);
state.selectedNode.position = state.dragObjectPos;
NodeEditor.RepaintClients ();
}
else
state.dragNode = false;
}
}
[EventHandlerAttribute (EventType.MouseDown)]
[EventHandlerAttribute (EventType.MouseUp)]
private static void HandleNodeDraggingEnd (NodeEditorInputInfo inputInfo)
{
if (inputInfo.editorState.dragUserID == "node")
{
Vector2 totalDrag = inputInfo.editorState.EndDrag ("node");
if (inputInfo.editorState.dragNode && inputInfo.editorState.selectedNode != null)
{
inputInfo.editorState.selectedNode.position = totalDrag;
NodeEditorCallbacks.IssueOnMoveNode (inputInfo.editorState.selectedNode);
}
}
inputInfo.editorState.dragNode = false;
}
#endregion
#region Window Panning
[EventHandlerAttribute (EventType.MouseDown, 105)] // Priority over hundred to make it call after the GUI
private static void HandleWindowPanningStart (NodeEditorInputInfo inputInfo)
{
if (GUIUtility.hotControl > 0)
return; // GUI has control
NodeEditorState state = inputInfo.editorState;
if ((inputInfo.inputEvent.button == 0 || inputInfo.inputEvent.button == 2) && state.focusedNode == null)
{ // Left- or Middle clicked on the empty canvas -> Start panning
state.panWindow = true;
state.StartDrag ("window", inputInfo.inputPos, state.panOffset);
}
}
[EventHandlerAttribute (EventType.MouseDrag)]
private static void HandleWindowPanning (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
if (state.panWindow)
{ // Calculate change in panOffset
if (inputInfo.editorState.dragUserID == "window")
state.panOffset += state.UpdateDrag ("window", inputInfo.inputPos);
else
state.panWindow = false;
NodeEditor.RepaintClients ();
}
}
[EventHandlerAttribute (EventType.MouseDown)]
[EventHandlerAttribute (EventType.MouseUp)]
private static void HandleWindowPanningEnd (NodeEditorInputInfo inputInfo)
{
if (inputInfo.editorState.dragUserID == "window")
inputInfo.editorState.panOffset = inputInfo.editorState.EndDrag ("window");
inputInfo.editorState.panWindow = false;
}
#endregion
#region Connection
[EventHandlerAttribute (EventType.MouseDown)]
private static void HandleConnectionDrawing (NodeEditorInputInfo inputInfo)
{ // TODO: Revamp Multi-Multi knob editing
NodeEditorState state = inputInfo.editorState;
if (inputInfo.inputEvent.button == 0 && state.focusedConnectionKnob != null)
{ // Left-Clicked on a ConnectionKnob, handle editing
if (state.focusedConnectionKnob.maxConnectionCount == ConnectionCount.Multi)
{ // Knob with multiple connections clicked -> Draw new connection from it
state.connectKnob = state.focusedConnectionKnob;
inputInfo.inputEvent.Use ();
}
else if (state.focusedConnectionKnob.maxConnectionCount == ConnectionCount.Single)
{ // Knob with single connection clicked
if (state.focusedConnectionKnob.connected())
{ // Loose and edit existing connection from it
state.connectKnob = state.focusedConnectionKnob.connection(0);
state.focusedConnectionKnob.RemoveConnection(state.connectKnob);
inputInfo.inputEvent.Use();
}
else
{ // Not connected, draw a new connection from it
state.connectKnob = state.focusedConnectionKnob;
inputInfo.inputEvent.Use();
}
}
}
}
[EventHandlerAttribute (EventType.MouseUp)]
private static void HandleApplyConnection (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
if (inputInfo.inputEvent.button == 0 && state.connectKnob != null && state.focusedNode != null && state.focusedConnectionKnob != null && state.focusedConnectionKnob != state.connectKnob)
{ // A connection curve was dragged and released onto a connection knob
state.focusedConnectionKnob.TryApplyConnection (state.connectKnob);
inputInfo.inputEvent.Use ();
}
state.connectKnob = null;
}
#endregion
#region Zoom
[EventHandlerAttribute (EventType.ScrollWheel)]
private static void HandleZooming (NodeEditorInputInfo inputInfo)
{
inputInfo.editorState.zoom = (float)Math.Round (Math.Min (4.0, Math.Max (0.6, inputInfo.editorState.zoom + inputInfo.inputEvent.delta.y / 15)), 2);
NodeEditor.RepaintClients ();
}
#endregion
#region Navigation
[HotkeyAttribute (KeyCode.N, EventType.KeyDown)]
private static void HandleStartNavigating (NodeEditorInputInfo inputInfo)
{
if (GUIUtility.keyboardControl > 0)
return;
inputInfo.editorState.navigate = true;
}
[HotkeyAttribute (KeyCode.N, EventType.KeyUp)]
private static void HandleEndNavigating (NodeEditorInputInfo inputInfo)
{
if (GUIUtility.keyboardControl > 0)
return;
inputInfo.editorState.navigate = false;
}
#endregion
#region Node Snap
[EventHandlerAttribute(EventType.MouseUp, 60)]
[EventHandlerAttribute(EventType.MouseDown, 60)]
[EventHandlerAttribute(EventType.MouseDrag, 60)]
[HotkeyAttribute(KeyCode.LeftControl, EventType.KeyDown , 60)]
private static void HandleNodeSnap (NodeEditorInputInfo inputInfo)
{
if (inputInfo.inputEvent.modifiers == EventModifiers.Control || inputInfo.inputEvent.keyCode == KeyCode.LeftControl)
{
NodeEditorState state = inputInfo.editorState;
if (state.selectedNode != null)
{ // Snap selected Node's position to multiples of 10
state.selectedNode.position.x = Mathf.Round(state.selectedNode.rect.x / 10) * 10;
state.selectedNode.position.y = Mathf.Round(state.selectedNode.rect.y / 10) * 10;
NodeEditor.RepaintClients();
}
if (state.activeGroup != null)
{ // Snap active Group's position to multiples of 10
state.activeGroup.rect.x = Mathf.Round(state.activeGroup.rect.x / 10) * 10;
state.activeGroup.rect.y = Mathf.Round(state.activeGroup.rect.y / 10) * 10;
NodeEditor.RepaintClients();
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 76a77818258cdde43a807b810eb57074
timeCreated: 1466001829
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,549 @@
using UnityEngine;
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using NodeEditorFramework.Utilities;
#if UNITY_EDITOR
using MenuFunction = UnityEditor.GenericMenu.MenuFunction;
using MenuFunctionData = UnityEditor.GenericMenu.MenuFunction2;
#else
using MenuFunction = NodeEditorFramework.Utilities.OverlayGUI.CustomMenuFunction;
using MenuFunctionData = NodeEditorFramework.Utilities.OverlayGUI.CustomMenuFunctionData;
#endif
namespace NodeEditorFramework
{
/// <summary>
/// The NodeEditor Input System handles dynamic input controls.
/// Use the four attributes on functions to make the system recognize them as event handlers
/// Default Controls can be found in NodeEditorInputControls
/// </summary>
public static class NodeEditorInputSystem
{
#region Setup and Fetching
// NOTE: Using Lists of KeyValuePair as we 1. need it ordered in the first two cases and 2. we do not need extras from Dictionary anyway
private static List<KeyValuePair<EventHandlerAttribute, Delegate>> eventHandlers;
private static List<KeyValuePair<HotkeyAttribute, Delegate>> hotkeyHandlers;
private static List<KeyValuePair<ContextEntryAttribute, MenuFunctionData>> contextEntries;
private static List<KeyValuePair<ContextFillerAttribute, Delegate>> contextFillers;
/// <summary>
/// Fetches all event handlers
/// </summary>
public static void SetupInput ()
{
eventHandlers = new List<KeyValuePair<EventHandlerAttribute, Delegate>> ();
hotkeyHandlers = new List<KeyValuePair<HotkeyAttribute, Delegate>> ();
contextEntries = new List<KeyValuePair<ContextEntryAttribute, MenuFunctionData>> ();
contextFillers = new List<KeyValuePair<ContextFillerAttribute, Delegate>> ();
// Iterate through each static method
IEnumerable<Assembly> scriptAssemblies = AppDomain.CurrentDomain.GetAssemblies ().Where ((Assembly assembly) => assembly.FullName.Contains ("Assembly"));
foreach (Assembly assembly in scriptAssemblies)
{
foreach (Type type in assembly.GetTypes ())
{
foreach (MethodInfo method in type.GetMethods (BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
{
#region Event-Attributes recognition and storing
// Check the method's attributes for input handler definitions
Delegate actionDelegate = null;
foreach (object attr in method.GetCustomAttributes (true))
{
Type attrType = attr.GetType ();
if (attrType == typeof(EventHandlerAttribute))
{ // Method is an eventHandler
if (EventHandlerAttribute.AssureValidity (method, attr as EventHandlerAttribute))
{ // Method signature is correct, so we register this handler
if (actionDelegate == null) actionDelegate = Delegate.CreateDelegate (typeof(Action<NodeEditorInputInfo>), method);
eventHandlers.Add (new KeyValuePair<EventHandlerAttribute, Delegate> (attr as EventHandlerAttribute, actionDelegate));
}
}
else if (attrType == typeof(HotkeyAttribute))
{ // Method is an hotkeyHandler
if (HotkeyAttribute.AssureValidity (method, attr as HotkeyAttribute))
{ // Method signature is correct, so we register this handler
if (actionDelegate == null) actionDelegate = Delegate.CreateDelegate (typeof(Action<NodeEditorInputInfo>), method);
hotkeyHandlers.Add (new KeyValuePair<HotkeyAttribute, Delegate> (attr as HotkeyAttribute, actionDelegate));
}
}
else if (attrType == typeof(ContextEntryAttribute))
{ // Method is an contextEntry
if (ContextEntryAttribute.AssureValidity (method, attr as ContextEntryAttribute))
{ // Method signature is correct, so we register this handler
if (actionDelegate == null) actionDelegate = Delegate.CreateDelegate (typeof(Action<NodeEditorInputInfo>), method);
// Create a proper MenuFunction as a wrapper for the delegate that converts the object to NodeEditorInputInfo
MenuFunctionData menuFunction = (object callbackObj) =>
{
if (!(callbackObj is NodeEditorInputInfo))
throw new UnityException ("Callback Object passed by context is not of type NodeEditorMenuCallback!");
actionDelegate.DynamicInvoke (callbackObj as NodeEditorInputInfo);
};
contextEntries.Add (new KeyValuePair<ContextEntryAttribute, MenuFunctionData> (attr as ContextEntryAttribute, menuFunction));
}
}
else if (attrType == typeof(ContextFillerAttribute))
{ // Method is an contextFiller
if (ContextFillerAttribute.AssureValidity (method, attr as ContextFillerAttribute))
{ // Method signature is correct, so we register this handler
Delegate methodDel = Delegate.CreateDelegate (typeof(Action<NodeEditorInputInfo, GenericMenu>), method);
contextFillers.Add (new KeyValuePair<ContextFillerAttribute, Delegate> (attr as ContextFillerAttribute, methodDel));
}
}
}
#endregion
}
}
}
eventHandlers.Sort ((handlerA, handlerB) => handlerA.Key.priority.CompareTo (handlerB.Key.priority));
hotkeyHandlers.Sort ((handlerA, handlerB) => handlerA.Key.priority.CompareTo (handlerB.Key.priority));
}
#endregion
#region Invoking Dynamic Input Handlers
/// <summary>
/// Calls the eventHandlers for either late or early input (pre- or post GUI) with the inputInfo
/// </summary>
private static void CallEventHandlers (NodeEditorInputInfo inputInfo, bool late)
{
object[] parameter = new object[] { inputInfo };
foreach (KeyValuePair<EventHandlerAttribute, Delegate> eventHandler in eventHandlers)
{
if ((eventHandler.Key.handledEvent == null || eventHandler.Key.handledEvent == inputInfo.inputEvent.type) &&
(late? eventHandler.Key.priority >= 100 : eventHandler.Key.priority < 100))
{ // Event is happening and specified priority is ok with the late-state
eventHandler.Value.DynamicInvoke (parameter);
if (inputInfo.inputEvent.type == EventType.Used)
return;
}
}
}
/// <summary>
/// Calls the hotkeys that match the keyCode and mods with the inputInfo
/// </summary>
private static void CallHotkeys (NodeEditorInputInfo inputInfo, KeyCode keyCode, EventModifiers mods)
{
object[] parameter = new object[] { inputInfo };
foreach (KeyValuePair<HotkeyAttribute, Delegate> hotKey in hotkeyHandlers)
{
if (hotKey.Key.handledHotKey == keyCode &&
(hotKey.Key.modifiers == null || hotKey.Key.modifiers == mods) &&
(hotKey.Key.limitingEventType == null || hotKey.Key.limitingEventType == inputInfo.inputEvent.type))
{
hotKey.Value.DynamicInvoke (parameter);
if (inputInfo.inputEvent.type == EventType.Used)
return;
}
}
}
/// <summary>
/// Fills the contextMenu of the specified contextType with the inputInfo
/// </summary>
private static void FillContextMenu (NodeEditorInputInfo inputInfo, GenericMenu contextMenu, ContextType contextType)
{
foreach (KeyValuePair<ContextEntryAttribute, MenuFunctionData> contextEntry in contextEntries)
{ // Add all registered menu entries for the specified type to the contextMenu
if (contextEntry.Key.contextType == contextType)
contextMenu.AddItem (new GUIContent (contextEntry.Key.contextPath), false, contextEntry.Value, inputInfo);
}
object[] fillerParams = new object[] { inputInfo, contextMenu };
foreach (KeyValuePair<ContextFillerAttribute, Delegate> contextFiller in contextFillers)
{ // Let all registered menu fillers for the specified type add their entries to the contextMenu
if (contextFiller.Key.contextType == contextType)
contextFiller.Value.DynamicInvoke (fillerParams);
}
}
#endregion
#region Event Handling
/// <summary>
/// Processes pre-GUI input events using dynamic input handlers
/// </summary>
public static void HandleInputEvents (NodeEditorState state)
{
if (shouldIgnoreInput (state))
return;
// Call input and hotkey handlers
NodeEditorInputInfo inputInfo = new NodeEditorInputInfo (state);
CallEventHandlers (inputInfo, false);
CallHotkeys (inputInfo, Event.current.keyCode, Event.current.modifiers);
}
/// <summary>
/// Processes late post-GUI input events using dynamic input handlers
/// </summary>
public static void HandleLateInputEvents (NodeEditorState state)
{
if (shouldIgnoreInput (state))
return;
// Call late input handlers
NodeEditorInputInfo inputInfo = new NodeEditorInputInfo (state);
CallEventHandlers (inputInfo, true);
}
/// <summary>
/// Returns whether to account for input in the given state using the mousePosition
/// </summary>
internal static bool shouldIgnoreInput (NodeEditorState state)
{
if (state == null)
return true;
// Account for any opened popups
if (OverlayGUI.HasPopupControl ())
return true;
// Check if mouse is outside of canvas rect
if (!state.canvasRect.Contains (Event.current.mousePosition))
return true;
// Check if mouse is inside an ignoreInput rect
for (int ignoreCnt = 0; ignoreCnt < state.ignoreInput.Count; ignoreCnt++)
{
if (state.ignoreInput [ignoreCnt].Contains (Event.current.mousePosition))
return true;
}
return false;
}
#endregion
#region Essential Controls
// Contains only the most essential controls, rest is found in NodeEditorInputControls
// NODE SELECTION
private static NodeEditorState unfocusControlsForState;
[EventHandlerAttribute (-4)] // Absolute first to call!
private static void HandleFocussing (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
// Choose focused Node
state.focusedNode = NodeEditor.NodeAtPosition (NodeEditor.ScreenToCanvasSpace (inputInfo.inputPos), out state.focusedConnectionKnob);
// Perform focus changes in Repaint, which is the only suitable time to do this
if (unfocusControlsForState == state && Event.current.type == EventType.Repaint)
{
GUIUtility.hotControl = 0;
GUIUtility.keyboardControl = 0;
unfocusControlsForState = null;
}
}
[EventHandlerAttribute (EventType.MouseDown, -2)] // Absolute second to call!
private static void HandleSelecting (NodeEditorInputInfo inputInfo)
{
NodeEditorState state = inputInfo.editorState;
if (inputInfo.inputEvent.button == 0 && state.focusedNode != state.selectedNode)
{ // Select focussed Node
unfocusControlsForState = state;
state.selectedNode = state.focusedNode;
NodeEditor.RepaintClients ();
}
#if UNITY_EDITOR
if (state.selectedNode != null)
UnityEditor.Selection.activeObject = state.selectedNode;
else if (UnityEditor.Selection.activeObject is Node)
UnityEditor.Selection.activeObject = state.canvas;
#endif
}
// CONTEXT CLICKS
[EventHandlerAttribute (EventType.MouseDown, 0)] // One of the highest priorities after node selection
private static void HandleContextClicks (NodeEditorInputInfo inputInfo)
{
if (Event.current.button == 1)
{ // Handle context clicks on Node and canvas
GenericMenu contextMenu = new GenericMenu ();
if (inputInfo.editorState.focusedNode != null) // Node Context Click
FillContextMenu (inputInfo, contextMenu, ContextType.Node);
else // Editor Context Click
FillContextMenu (inputInfo, contextMenu, ContextType.Canvas);
contextMenu.ShowAsContext ();
Event.current.Use ();
}
}
#endregion
}
/// <summary>
/// Class that representates an input to handle containing all avaible data
/// </summary>
public class NodeEditorInputInfo
{
public string message;
public NodeEditorState editorState;
public Event inputEvent;
public Vector2 inputPos;
public NodeEditorInputInfo (NodeEditorState EditorState)
{
message = null;
editorState = EditorState;
inputEvent = Event.current;
inputPos = inputEvent.mousePosition;
}
public NodeEditorInputInfo (string Message, NodeEditorState EditorState)
{
message = Message;
editorState = EditorState;
inputEvent = Event.current;
inputPos = inputEvent.mousePosition;
}
/// <summary>
/// Sets both curEditorState and curNodeCanvas to these of the environment this input originates from
/// </summary>
public void SetAsCurrentEnvironment ()
{
NodeEditor.curEditorState = editorState;
NodeEditor.curNodeCanvas = editorState.canvas;
}
}
#region Event Attributes
/// <summary>
/// The EventHandlerAttribute is used to handle arbitrary events for the Node Editor.
/// 'priority' can additionally be specified. A priority over or equals hundred will be called AFTER the GUI
/// Method Signature must be [ Return: Void; Params: NodeEditorInputInfo, EventType ]
/// </summary>
[AttributeUsage (AttributeTargets.Method, AllowMultiple = true)]
public class EventHandlerAttribute : Attribute
{
public EventType? handledEvent { get; private set; }
public int priority { get; private set; }
/// <summary>
/// Handle all events of the specified eventType
/// </summary>
public EventHandlerAttribute(EventType eventType, int priorityValue)
{
handledEvent = eventType;
priority = priorityValue;
}
/// <summary>
/// Handle all events of the specified eventType
/// </summary>
public EventHandlerAttribute(int priorityValue)
{
handledEvent = null;
priority = priorityValue;
}
/// <summary>
/// Handle all events of the specified eventType
/// </summary>
public EventHandlerAttribute (EventType eventType)
{
handledEvent = eventType;
priority = 50;
}
/// <summary>
/// Handle all EventTypes
/// </summary>
public EventHandlerAttribute ()
{
handledEvent = null;
}
internal static bool AssureValidity (MethodInfo method, EventHandlerAttribute attr)
{
if (!method.IsGenericMethod && !method.IsGenericMethodDefinition && (method.ReturnType == null || method.ReturnType == typeof(void)))
{ // Check if the method has the correct signature
ParameterInfo[] methodParams = method.GetParameters ();
if (methodParams.Length == 1 && methodParams[0].ParameterType == typeof(NodeEditorInputInfo))
return true;
else
Debug.LogWarning ("Method " + method.Name + " has incorrect signature for EventHandlerAttribute!");
}
return false;
}
}
/// <summary>
/// The HotkeyAttribute is used to provide hotkeys for the Node Editor.
/// 'priority' can additionally be specified. A priority over or equals hundred will be called AFTER the GUI
/// Method Signature must be [ Return: Void; Params: NodeEditorInputInfo ]
/// </summary>
[AttributeUsage (AttributeTargets.Method, AllowMultiple = true)]
public class HotkeyAttribute : Attribute
{
public KeyCode handledHotKey { get; private set; }
public EventModifiers? modifiers { get; private set; }
public EventType? limitingEventType { get; private set; }
public int priority { get; private set; }
/// <summary>
/// Handle the specified hotkey
/// </summary>
public HotkeyAttribute (KeyCode handledKey)
{
handledHotKey = handledKey;
modifiers = null;
limitingEventType = null;
priority = 50;
}
/// <summary>
/// Handle the specified hotkey with modifiers
/// </summary>
public HotkeyAttribute (KeyCode handledKey, EventModifiers eventModifiers)
{
handledHotKey = handledKey;
modifiers = eventModifiers;
limitingEventType = null;
priority = 50;
}
/// <summary>
/// Handle the specified hotkey limited to the specified eventType
/// </summary>
public HotkeyAttribute (KeyCode handledKey, EventType LimitEventType)
{
handledHotKey = handledKey;
modifiers = null;
limitingEventType = LimitEventType;
priority = 50;
}
/// <summary>
/// Handle the specified hotkey limited to the specified eventType
/// </summary>
public HotkeyAttribute(KeyCode handledKey, EventType LimitEventType, int priorityValue)
{
handledHotKey = handledKey;
modifiers = null;
limitingEventType = LimitEventType;
priority = priorityValue;
}
/// <summary>
/// Handle the specified hotkey with modifiers limited to the specified eventType
/// </summary>
public HotkeyAttribute (KeyCode handledKey, EventModifiers eventModifiers, EventType LimitEventType)
{
handledHotKey = handledKey;
modifiers = eventModifiers;
limitingEventType = LimitEventType;
priority = 50;
}
/// <summary>
/// Handle the specified hotkey with modifiers limited to the specified eventType with specified priority
/// </summary>
public HotkeyAttribute (KeyCode handledKey, EventModifiers eventModifiers, EventType LimitEventType, int priorityValue)
{
handledHotKey = handledKey;
modifiers = eventModifiers;
limitingEventType = LimitEventType;
priority = priorityValue;
}
internal static bool AssureValidity (MethodInfo method, HotkeyAttribute attr)
{
if (!method.IsGenericMethod && !method.IsGenericMethodDefinition && (method.ReturnType == null || method.ReturnType == typeof(void)))
{ // Check if the method has the correct signature
ParameterInfo[] methodParams = method.GetParameters ();
if (methodParams.Length == 1 && methodParams[0].ParameterType.IsAssignableFrom (typeof(NodeEditorInputInfo)))
return true;
else
Debug.LogWarning ("Method " + method.Name + " has incorrect signature for HotkeyAttribute!");
}
return false;
}
}
/// <summary>
/// The type of a context menu. Defines on what occasion the context menu should show in the NodeEditor
/// </summary>
public enum ContextType { Node, Canvas, Toolbar }
/// <summary>
/// The ContextAttribute is used to register context entries in the NodeEditor.
/// This function will be called when the user clicked at the item at path
/// Type defines the type of context menu to appear in, like the right-click on a Node or the Canvas.
/// Method Signature must be [ Return: Void; Params: NodeEditorInputInfo ]
/// </summary>
[AttributeUsage (AttributeTargets.Method)]
public class ContextEntryAttribute : Attribute
{
public ContextType contextType { get; private set; }
public string contextPath { get; private set; }
/// <summary>
/// Place this function at path in the specified contextType
/// </summary>
public ContextEntryAttribute (ContextType type, string path)
{
contextType = type;
contextPath = path;
}
internal static bool AssureValidity (MethodInfo method, ContextEntryAttribute attr)
{
if (!method.IsGenericMethod && !method.IsGenericMethodDefinition && (method.ReturnType == null || method.ReturnType == typeof(void)))
{ // Check if the method has the correct signature
ParameterInfo[] methodParams = method.GetParameters ();
if (methodParams.Length == 1 && methodParams[0].ParameterType == typeof(NodeEditorInputInfo))
return true;
else
Debug.LogWarning ("Method " + method.Name + " has incorrect signature for ContextAttribute!");
}
return false;
}
}
/// <summary>
/// The ContextFillerAttribute is used to register context entries in the NodeEditor.
/// This function will be called to fill the context GenericMenu in any way it likes to.
/// Type defines the type of context menu to appear in, like the right-click on a Node or the Canvas.
/// Method Signature must be [ Return: Void; Params: NodeEditorInputInfo, GenericMenu ]
/// </summary>
[AttributeUsage (AttributeTargets.Method)]
public class ContextFillerAttribute : Attribute
{
public ContextType contextType { get; private set; }
/// <summary>
/// Fill the specified contextType
/// </summary>
public ContextFillerAttribute (ContextType type)
{
contextType = type;
}
internal static bool AssureValidity (MethodInfo method, ContextFillerAttribute attr)
{
if (!method.IsGenericMethod && !method.IsGenericMethodDefinition && (method.ReturnType == null || method.ReturnType == typeof(void)))
{ // Check if the method has the correct signature
ParameterInfo[] methodParams = method.GetParameters ();
if (methodParams.Length == 2 && methodParams[0].ParameterType == typeof(NodeEditorInputInfo) && methodParams[1].ParameterType == typeof(GenericMenu))
return true;
else
Debug.LogWarning ("Method " + method.Name + " has incorrect signature for ContextAttribute!");
}
return false;
}
}
#endregion
}

View File

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

View File

@@ -0,0 +1,978 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using NodeEditorFramework.IO;
using GenericMenu = NodeEditorFramework.Utilities.GenericMenu;
using RPGTALK.Nodes;
namespace NodeEditorFramework.Standard
{
public class NodeEditorInterface
{
public NodeEditorUserCache canvasCache;
public Action<GUIContent> ShowNotificationAction;
// GUI
public string sceneCanvasName = "";
public float toolbarHeight = 17;
// Modal Panel
public bool showModalPanel;
public Rect modalPanelRect = new Rect(20, 50, 250, 70);
public Action modalPanelContent;
// IO Format modal panel
private ImportExportFormat IOFormat;
private object[] IOLocationArgs;
private delegate bool? DefExportLocationGUI(string canvasName, ref object[] locationArgs);
private delegate bool? DefImportLocationGUI(ref object[] locationArgs);
private DefImportLocationGUI ImportLocationGUI;
private DefExportLocationGUI ExportLocationGUI;
public void ShowNotification(GUIContent message)
{
if (ShowNotificationAction != null)
ShowNotificationAction(message);
}
#region GUI
public void DrawToolbarGUI(Rect rect)
{
rect.height = toolbarHeight;
GUILayout.BeginArea (rect, NodeEditorGUI.toolbar);
GUILayout.BeginHorizontal();
float curToolbarHeight = 0;
//RPGTalk removed to make it more simple to use only RPGTalk functions.
if (GUILayout.Button("New File", NodeEditorGUI.toolbarButton, GUILayout.Width(70)))
{
if (EditorUtility.DisplayDialog("Are you sure you want to create a new canvas?", "Any unsaved changes on this one will be lost", "I'm sure", "Hold up...")){
canvasCache.NewNodeCanvas(typeof(RPGTalkNodeCanvas));
}
}
if (GUILayout.Button("Load TXT", NodeEditorGUI.toolbarButton, GUILayout.Width(70)))
{
if (EditorUtility.DisplayDialog("Are you sure you want to load a new canvas?", "Any unsaved changes on this one will be lost", "I'm sure", "Hold up...")){
LoadTXTFile();
}
}
if (GUILayout.Button("Save TXT", NodeEditorGUI.toolbarButton, GUILayout.Width(70)))
{
SaveTXTFile();
}
//RPGTalk commented to make the interface more simple to the end user
/*
if (GUILayout.Button("File", NodeEditorGUI.toolbarDropdown, GUILayout.Width(50)))
{
GenericMenu menu = new GenericMenu(!Application.isPlaying);
// New Canvas filled with canvas types
NodeCanvasManager.FillCanvasTypeMenu(ref menu, NewNodeCanvas, "New Canvas/");
menu.AddSeparator("");
menu.AddItem(new GUIContent("Save TXT"), false, SaveTXTFile);
// Load / Save
#if UNITY_EDITOR
menu.AddItem(new GUIContent("Load Canvas"), false, LoadCanvas);
menu.AddItem(new GUIContent("Reload Canvas"), false, ReloadCanvas);
menu.AddSeparator("");
if (canvasCache.nodeCanvas.allowSceneSaveOnly)
{
menu.AddDisabledItem(new GUIContent("Save Canvas"));
menu.AddDisabledItem(new GUIContent("Save Canvas As"));
}
else
{
menu.AddItem(new GUIContent("Save Canvas"), false, SaveCanvas);
menu.AddItem(new GUIContent("Save Canvas As"), false, SaveCanvasAs);
}
menu.AddSeparator("");
#endif
// Import / Export filled with import/export types
ImportExportManager.FillImportFormatMenu(ref menu, ImportCanvasCallback, "Import/");
if (canvasCache.nodeCanvas.allowSceneSaveOnly)
{
menu.AddDisabledItem(new GUIContent("Export"));
}
else
{
ImportExportManager.FillExportFormatMenu(ref menu, ExportCanvasCallback, "Export/");
}
menu.AddSeparator("");
// Scene Saving
string[] sceneSaves = NodeEditorSaveManager.GetSceneSaves();
if (sceneSaves.Length <= 0) // Display disabled item
menu.AddItem(new GUIContent("Load Canvas from Scene"), false, null);
else foreach (string sceneSave in sceneSaves) // Display scene saves to load
menu.AddItem(new GUIContent("Load Canvas from Scene/" + sceneSave), false, LoadSceneCanvasCallback, sceneSave);
menu.AddItem(new GUIContent("Save Canvas to Scene"), false, SaveSceneCanvasCallback);
// Show dropdown
menu.Show(new Vector2(5, toolbarHeight));
}*/
curToolbarHeight = Mathf.Max(curToolbarHeight, GUILayoutUtility.GetLastRect().yMax);
GUILayout.Space(10);
GUILayout.FlexibleSpace();
//RPGTalk removed to make it more simple
/*GUILayout.Label(new GUIContent("" + canvasCache.nodeCanvas.saveName + " (" + (canvasCache.nodeCanvas.livesInScene ? "Scene Save" : "Asset Save") + ")",
"Opened Canvas path: " + canvasCache.nodeCanvas.savePath), NodeEditorGUI.toolbarLabel);
GUILayout.Label("Type: " + canvasCache.typeData.DisplayString, NodeEditorGUI.toolbarLabel);
curToolbarHeight = Mathf.Max(curToolbarHeight, GUILayoutUtility.GetLastRect().yMax);
GUI.backgroundColor = new Color(1, 0.3f, 0.3f, 1);
if (GUILayout.Button("Force Re-init", NodeEditorGUI.toolbarButton, GUILayout.Width(100)))
{
NodeEditor.ReInit(true);
canvasCache.nodeCanvas.Validate();
} */
#if !UNITY_EDITOR
GUILayout.Space(5);
if (GUILayout.Button("Quit", NodeEditorGUI.toolbarButton, GUILayout.Width(100)))
Application.Quit ();
#endif
curToolbarHeight = Mathf.Max(curToolbarHeight, GUILayoutUtility.GetLastRect().yMax);
GUI.backgroundColor = Color.white;
GUILayout.EndHorizontal();
GUILayout.EndArea();
if (Event.current.type == EventType.Repaint)
toolbarHeight = curToolbarHeight;
}
private void SaveSceneCanvasPanel()
{
GUILayout.Label("Save Canvas To Scene");
GUILayout.BeginHorizontal();
sceneCanvasName = GUILayout.TextField(sceneCanvasName, GUILayout.ExpandWidth(true));
bool overwrite = NodeEditorSaveManager.HasSceneSave(sceneCanvasName);
if (overwrite)
GUILayout.Label(new GUIContent("!!!", "A canvas with the specified name already exists. It will be overwritten!"), GUILayout.ExpandWidth(false));
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Cancel"))
showModalPanel = false;
if (GUILayout.Button(new GUIContent(overwrite ? "Overwrite" : "Save", "Save the canvas to the Scene")))
{
showModalPanel = false;
if (!string.IsNullOrEmpty (sceneCanvasName))
canvasCache.SaveSceneNodeCanvas(sceneCanvasName);
}
GUILayout.EndHorizontal();
}
public void DrawModalPanel()
{
if (showModalPanel)
{
if (modalPanelContent == null)
return;
GUILayout.BeginArea(modalPanelRect, NodeEditorGUI.nodeBox);
modalPanelContent.Invoke();
GUILayout.EndArea();
}
}
#endregion
#region Menu Callbacks
private void NewNodeCanvas(Type canvasType)
{
canvasCache.NewNodeCanvas(canvasType);
}
#if UNITY_EDITOR
class FollowUpNode
{
public Node node;
public string followUpTitle;
public int identifier;
}
List<FollowUpNode> followUpNodes;
List<RPGTalkNode> createdNodes;
// RPGTalk added to load the TXT!
void LoadTXTFile()
{
//Get the actual selected path
string selectedPath = "Assets";
foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
{
selectedPath = AssetDatabase.GetAssetPath(obj);
if (File.Exists(selectedPath))
{
selectedPath = Path.GetDirectoryName(selectedPath);
}
break;
}
string path = EditorUtility.OpenFilePanel("Load dialog TXT", selectedPath, "txt");
//first, clear the canvas.
canvasCache.NewNodeCanvas(typeof(RPGTalkNodeCanvas));
// read the TXT file into the elements list
StreamReader reader = new StreamReader(path);
string txtFile = reader.ReadToEnd();
reader.Close();
StringReader sReader = new StringReader(txtFile);
string line = sReader.ReadLine();
int currentLine = 0;
if (line == null)
{
Debug.LogError("There was an error reading your file! Check your encoding settings.");
return;
}
RPGTalkNode fatherNode = null;
Vector2 position = Vector2.zero;
followUpNodes = new List<FollowUpNode>();
createdNodes = new List<RPGTalkNode>();
while (line != null)
{
//If the line is empty, we will just ignore it
if (string.IsNullOrEmpty(line))
{
line = sReader.ReadLine();
currentLine++;
continue;
}
//Lets check with this is an oppening node
int titleLine = line.IndexOf("[title=");
if (titleLine != -1)
{
string title = "";
int titleEnd = line.IndexOf("]",titleLine);
if (titleEnd != -1)
{
title = line.Substring(titleLine + 7, titleEnd - (titleLine + 7));
}
else
{
Debug.LogError("Error reading title");
}
if (title.Length > 0)
{
//Good. We got a title. Let's find out if it is an oppening or a closing one
if (title.IndexOf("_begin") != -1)
{
title = title.Substring(0, title.IndexOf("_begin"));
CreateNode(ref line, ref position, ref currentLine, title, ref sReader, ref fatherNode,true);
continue;
}
else if (title.IndexOf("_end") != -1)
{
//end last title node
position.y += 250;
position.x = 0;
fatherNode = null;
line = sReader.ReadLine();
currentLine++;
continue;
}
else
{
Debug.LogError("Right now, Node Editor only reads TXT made with node editor. You can change your TXT title to have _begin and _end tags");
}
}
}// end if title
//Let's check if this line is a save
if (line.IndexOf("[save") != -1 && line.IndexOf("]") != -1)
{
//We do have one!
int initialBracket = line.IndexOf("[save");
int finalBracket = -1;
if (initialBracket != -1)
{
finalBracket = line.IndexOf("]", initialBracket);
}
//There still are any '[save' and it is before a ']'?
if (initialBracket < finalBracket)
{
//Everything fine until now. Now let's check the start and break variables
int indexOfStart = line.IndexOf("start=", initialBracket + 5);
int endOfStart = line.IndexOf(" ", indexOfStart);
if (endOfStart == -1)
{
endOfStart = line.IndexOf("]", indexOfStart);
}
int indexOfBreak = line.IndexOf("break=", initialBracket + 5);
int endOfBreak = line.IndexOf(" ", indexOfBreak);
if (endOfBreak == -1)
{
endOfBreak = line.IndexOf("]", indexOfBreak);
}
int indexOfSavedData = line.IndexOf("data=", initialBracket + 5);
int endOfSavedData = line.IndexOf(" ", indexOfSavedData);
if (endOfSavedData == -1)
{
endOfSavedData = line.IndexOf("]", indexOfSavedData);
}
int indexOfModifier = line.IndexOf("mod=", initialBracket + 5);
int endOfModifier = line.IndexOf(" ", indexOfModifier);
if (endOfModifier == -1)
{
endOfModifier = line.IndexOf("]", indexOfModifier);
}
if (indexOfStart != -1 && indexOfBreak != -1 && endOfBreak != -1 && endOfStart != -1
&& indexOfSavedData != -1 && endOfSavedData != -1 && indexOfModifier != -1 && endOfModifier != -1)
{
string newLineToStart = line.Substring(indexOfStart + 6, endOfStart - (indexOfStart + 6));
string newLineToBreak = line.Substring(indexOfBreak + 6, endOfBreak - (indexOfBreak + 6));
string newSavedData = line.Substring(indexOfSavedData + 5, endOfSavedData - (indexOfSavedData + 5));
string newModfier = line.Substring(indexOfModifier + 4, endOfModifier - (indexOfModifier + 4));
int intModifier;
if (newLineToStart.Length > 0 && newLineToBreak.Length > 0 && newSavedData.Length > 0 && int.TryParse(newModfier, out intModifier))
{
//Good, this a valid save. Let's create the node
RPGTalkSaveNode thisNode = Node.Create("rpgtalkSaveNode", position) as RPGTalkSaveNode;
position.x += 300;
thisNode.modifier = intModifier;
thisNode.savedData = newSavedData;
//Lets keep this node to move and connect later
FollowUpNode followUp = new FollowUpNode();
followUp.node = thisNode;
followUp.followUpTitle = newLineToStart;
//followUp.identifier = choiceNum;
line = sReader.ReadLine();
currentLine++;
followUpNodes.Add(followUp);
//Connect the node
if (fatherNode != null)
{
ConnectionPort myPort = null;
foreach (ConnectionPort port in thisNode.connectionPorts)
{
if (port.direction == Direction.In)
{
myPort = port;
break;
}
}
foreach (ConnectionPort port in fatherNode.connectionPorts)
{
if (port.direction == Direction.Out)
{
port.ApplyConnection(myPort);
break;
}
}
}
continue;
}
else
{
Debug.LogWarning("There was a problem in your save variables. Check The spelling");
}
}
else
{
Debug.LogWarning("Found a [save] variable in the text but it didn't had a variable. Check The spelling");
}
}
}
//This is none of the special lines. So it must be a common node.
CreateNode(ref line, ref position, ref currentLine, "", ref sReader, ref fatherNode);
}
//Everything created. Everthing fine. Let's now add connections to the follow up nodes (choices and saves)
foreach (FollowUpNode follow in followUpNodes)
{
//the follow up node is exclusive to the prior node? if it isn't, we don't want to move it. Just add the connection
if (follow.followUpTitle.IndexOf("FollowUp_") != -1 && follow.followUpTitle.IndexOf("Choice_" + follow.identifier.ToString()) != -1)
{
//TODO: Move the followups
}
//Find out what node should if be connected to
foreach (RPGTalkNode talkNode in createdNodes)
{
string removeBegin = follow.followUpTitle.Substring(0, follow.followUpTitle.LastIndexOf("_begin"));
if (talkNode.CutsceneTitle == removeBegin)
{
//Connect the nodes
ConnectionPort myPort = null;
foreach (ConnectionPort port in follow.node.connectionPorts)
{
if (port.direction == Direction.Out)
{
myPort = port;
break;
}
}
foreach (ConnectionPort port in talkNode.connectionPorts)
{
if (port.direction == Direction.In)
{
port.ApplyConnection(myPort);
break;
}
}
break;
}
}
}
}
void CreateNode(ref string line, ref Vector2 position, ref int currentLine, string cutsceneTitle, ref StringReader sReader, ref RPGTalkNode fatherNode, bool startNode = false)
{
RPGTalkNode thisNode = null;
int characterID = 0;
int expressionID = 0;
string dialoger;
//Good. Let's create the node
thisNode = Node.Create("rpgtalkNode", position) as RPGTalkNode;
position.x += 400;
//If it is a [title] line, we don't want to actually read it, but the line after it
if (startNode)
{
thisNode.CutsceneTitle = cutsceneTitle;
line = sReader.ReadLine();
currentLine++;
}
//dialoger
GetDialoger(line, out line, out characterID, out expressionID, out dialoger);
thisNode.characterID = characterID;
thisNode.expressionID = expressionID;
//questions
string questionID;
GetQuestion(line, out line, out questionID);
thisNode.DialogLine = line;
//Connect the node
if (fatherNode != null)
{
ConnectionPort myPort = null;
foreach (ConnectionPort port in thisNode.connectionPorts)
{
if (port.direction == Direction.In)
{
myPort = port;
break;
}
}
foreach (ConnectionPort port in fatherNode.connectionPorts)
{
if (port.direction == Direction.Out)
{
port.ApplyConnection(myPort);
break;
}
}
}
fatherNode = thisNode;
if (questionID.Length > 0)
{
thisNode.questionID = questionID;
line = sReader.ReadLine();
currentLine++;
int choices = 0;
float originalY = position.y;
while (line != null && line.IndexOf("[choice]") != -1)
{
choices++;
if (choices % 2 == 0)
{
position.y = originalY + 100;
}
else
{
position.y = originalY - 40;
}
CreateChoiceNode(ref line, ref position, ref fatherNode ,choices, ref sReader, ref currentLine);
line = sReader.ReadLine();
currentLine++;
if (choices % 2 == 0 || line == null || line.IndexOf("[choice]") == -1)
{
position.x += 180;
}
continue;
}
position.y = originalY;
}
else
{
line = sReader.ReadLine();
currentLine++;
}
createdNodes.Add(thisNode);
}
void GetNewTalkTag(ref string line, ref Vector2 position, ref int currentLine, ref StringReader sReader, Node fatherNode, int choiceNum = -1)
{
//check if the user have some newtalk and the line asks for one
if (line.IndexOf("[newtalk") != -1 && line.IndexOf("]") != -1)
{
//We do have one!
int initialBracket = line.IndexOf("[newtalk");
int finalBracket = -1;
if (initialBracket != -1)
{
finalBracket = line.IndexOf("]", initialBracket);
}
//There still are any '[newtalk' and it is before a ']'?
if (initialBracket < finalBracket)
{
//Everything fine until now. Now let's check the start and break variables
int indexOfStart = line.IndexOf("start=", initialBracket + 8);
int endOfStart = line.IndexOf(" ", indexOfStart);
if (endOfStart == -1)
{
endOfStart = line.IndexOf("]", indexOfStart);
}
int indexOfBreak = line.IndexOf("break=", initialBracket + 8);
int endOfBreak = line.IndexOf(" ", indexOfBreak);
if (endOfBreak == -1)
{
endOfBreak = line.IndexOf("]", indexOfBreak);
}
if (indexOfStart != -1 && indexOfBreak != -1 && endOfBreak != -1 && endOfStart != -1)
{
string newLineToStart = line.Substring(indexOfStart + 6, endOfStart - (indexOfStart + 6));
string newLineToBreak = line.Substring(indexOfBreak + 6, endOfBreak - (indexOfBreak + 6));
if (newLineToStart.Length > 0 && newLineToBreak.Length > 0)
{
line = line.Substring(0, initialBracket) + line.Substring(finalBracket + 1);
//Lets keep this node to move and connect later
FollowUpNode followUp = new FollowUpNode();
followUp.node = fatherNode;
followUp.followUpTitle = newLineToStart;
followUp.identifier = choiceNum;
followUpNodes.Add(followUp);
}
else
{
Debug.LogWarning("There was a problem in your start=x or break=y. Check The spelling");
}
}
else
{
Debug.LogWarning("Found a [newtalk] variable in the text but it didn't had start=x or break=y. Check The spelling");
}
}
}
}
void CreateChoiceNode(ref string line, ref Vector2 position, ref RPGTalkNode fatherNode, int choiceNum, ref StringReader sReader, ref int currentLine)
{
int initialBracket = line.IndexOf("[choice]");
//Ok! Let's isolate its string
line = line.Substring(initialBracket + 8);
RPGTalkChoiceNode choiceNode = Node.Create("rpgtalkChoiceNode", position) as RPGTalkChoiceNode;
//Connect the node
ConnectionPort myPort = null;
foreach (ConnectionPort port in choiceNode.connectionPorts)
{
if (port.direction == Direction.In)
{
myPort = port;
break;
}
}
foreach (ConnectionPort port in fatherNode.connectionPorts)
{
if (port.direction == Direction.Out)
{
port.ApplyConnection(myPort);
break;
}
}
//Let's check if it has a follow up
GetNewTalkTag(ref line, ref position, ref currentLine, ref sReader, choiceNode, choiceNum - 1);
choiceNode.DialogLine = line;
}
void GetQuestion(string line, out string returnLine, out string questionID)
{
questionID = "";
//check if the user have some question and the line asks for one
if (line.IndexOf("[question=") != -1 && line.IndexOf("]", line.IndexOf("[question=")) != -1)
{
int initialBracket = line.IndexOf("[question=");
int finalBracket = line.IndexOf("]", initialBracket);
//Ok, new question around! Let's get its id
questionID = line.Substring(initialBracket + 10, finalBracket - (initialBracket + 10));
line = line.Substring(0, initialBracket) + line.Substring(finalBracket + 1);
}
returnLine = line;
}
void GetDialoger(string line, out string returnLine, out int characterID, out int expressionID, out string dialoger)
{
returnLine = line;
characterID = 0;
expressionID = 0;
dialoger = "";
if (line.IndexOf(':') != -1)
{
string[] splitLine = line.Split(new char[] { ':' }, 2);
dialoger = splitLine[0].Trim();
returnLine = splitLine[1].Trim();
RPGTALK.Helper.RPGTalkCharacter[] characters = (NodeEditor.curNodeCanvas as RPGTalkNodeCanvas).GetAllCharactersInGame();
for (int i = 0; i < characters.Length; i++)
{
if (characters[i].dialoger == dialoger)
{
characterID = i + 1;
//expression
string expression = GetExpression(returnLine, out returnLine);
string[] expressions = (NodeEditor.curNodeCanvas as RPGTalkNodeCanvas).GetExpressionsNamesByCharacterName(dialoger);
for (int j = 0; j < expressions.Length; j++)
{
if (expressions[j] == expression)
{
expressionID = j;
break;
}
}
break;
}
}
}
}
string GetExpression(string line, out string returnLine)
{
//check if the user have some expression and the line asks for one
if (line.IndexOf("[expression=") != -1 && line.IndexOf("]") != -1)
{
//We do have one!
int initialBracket = line.IndexOf("[expression=");
int finalBracket = -1;
if (initialBracket != -1)
{
finalBracket = line.IndexOf("]", initialBracket);
}
//There still are any '[expression=' and it is before a ']'?
if (initialBracket < finalBracket)
{
if (line.Substring(initialBracket + 12, finalBracket - (initialBracket + 12)).Length > 0)
{
returnLine = line.Substring(0, initialBracket) +
line.Substring(finalBracket + 1);
return line.Substring(initialBracket + 12, finalBracket - (initialBracket + 12));
}
else
{
Debug.LogWarning("Found a [expression=x] variable in the text but something is wrong with it. Check The spelling");
}
}
}
returnLine = line;
return "";
}
// RPGTalk added to save the TXT!
void SaveTXTFile()
{
RPGTalkNodeCanvas canvas = (NodeEditor.curNodeCanvas as RPGTalkNodeCanvas);
string file = "";
//force remaking the lines
foreach(RPGTalkNode node in canvas.GetStartCutsceneNodes())
{
file += "[title=" + node.CutsceneTitle + "_begin]\n";
int actualLine = file.Split('\n').Length;
file += node.GetText(actualLine);
//file += node.GetText();
file += "[title=" + node.CutsceneTitle + "_end]\n";
}
file = "";
//Actually write
foreach (RPGTalkNode node in canvas.GetStartCutsceneNodes())
{
file += "[title=" + node.CutsceneTitle + "_begin]\n";
file += node.GetText();
file += "[title=" + node.CutsceneTitle + "_end]\n";
}
string path = UnityEditor.EditorUtility.SaveFilePanelInProject("Save Dialog as TXT", "NewDialog", "txt", "");
if (!string.IsNullOrEmpty(path))
{
File.WriteAllText(path, file);
}
}
private void LoadCanvas()
{
string path = UnityEditor.EditorUtility.OpenFilePanel("Load Node Canvas", NodeEditor.editorPath + "Resources/Saves/", "asset");
if (!path.Contains(Application.dataPath))
{
if (!string.IsNullOrEmpty(path))
ShowNotification(new GUIContent("You should select an asset inside your project folder!"));
}
else
canvasCache.LoadNodeCanvas(path);
}
private void ReloadCanvas()
{
string path = canvasCache.nodeCanvas.savePath;
if (!string.IsNullOrEmpty(path))
{
if (path.StartsWith("SCENE/"))
canvasCache.LoadSceneNodeCanvas(path.Substring(6));
else
canvasCache.LoadNodeCanvas(path);
ShowNotification(new GUIContent("Canvas Reloaded!"));
}
else
ShowNotification(new GUIContent("Cannot reload canvas as it has not been saved yet!"));
}
private void SaveCanvas()
{
string path = canvasCache.nodeCanvas.savePath;
if (!string.IsNullOrEmpty(path))
{
if (path.StartsWith("SCENE/"))
canvasCache.SaveSceneNodeCanvas(path.Substring(6));
else
canvasCache.SaveNodeCanvas(path);
ShowNotification(new GUIContent("Canvas Saved!"));
}
else
ShowNotification(new GUIContent("No save location found. Use 'Save As'!"));
}
private void SaveCanvasAs()
{
string panelPath = NodeEditor.editorPath + "Resources/Saves/";
if (canvasCache.nodeCanvas != null && !string.IsNullOrEmpty(canvasCache.nodeCanvas.savePath))
panelPath = canvasCache.nodeCanvas.savePath;
string path = UnityEditor.EditorUtility.SaveFilePanelInProject("Save Node Canvas", "Node Canvas", "asset", "", panelPath);
if (!string.IsNullOrEmpty(path))
canvasCache.SaveNodeCanvas(path);
}
#endif
private void LoadSceneCanvasCallback(object canvas)
{
canvasCache.LoadSceneNodeCanvas((string)canvas);
sceneCanvasName = canvasCache.nodeCanvas.name;
}
private void SaveSceneCanvasCallback()
{
modalPanelContent = SaveSceneCanvasPanel;
showModalPanel = true;
}
private void ImportCanvasCallback(string formatID)
{
IOFormat = ImportExportManager.ParseFormat(formatID);
if (IOFormat.RequiresLocationGUI)
{
ImportLocationGUI = IOFormat.ImportLocationArgsGUI;
modalPanelContent = ImportCanvasGUI;
showModalPanel = true;
}
else if (IOFormat.ImportLocationArgsSelection(out IOLocationArgs))
canvasCache.SetCanvas(ImportExportManager.ImportCanvas(IOFormat, IOLocationArgs));
}
private void ImportCanvasGUI()
{
if (ImportLocationGUI != null)
{
bool? state = ImportLocationGUI(ref IOLocationArgs);
if (state == null)
return;
if (state == true)
canvasCache.SetCanvas(ImportExportManager.ImportCanvas(IOFormat, IOLocationArgs));
ImportLocationGUI = null;
modalPanelContent = null;
showModalPanel = false;
}
else
showModalPanel = false;
}
private void ExportCanvasCallback(string formatID)
{
IOFormat = ImportExportManager.ParseFormat(formatID);
if (IOFormat.RequiresLocationGUI)
{
ExportLocationGUI = IOFormat.ExportLocationArgsGUI;
modalPanelContent = ExportCanvasGUI;
showModalPanel = true;
}
else if (IOFormat.ExportLocationArgsSelection(canvasCache.nodeCanvas.saveName, out IOLocationArgs))
ImportExportManager.ExportCanvas(canvasCache.nodeCanvas, IOFormat, IOLocationArgs);
}
private void ExportCanvasGUI()
{
if (ExportLocationGUI != null)
{
bool? state = ExportLocationGUI(canvasCache.nodeCanvas.saveName, ref IOLocationArgs);
if (state == null)
return;
if (state == true)
ImportExportManager.ExportCanvas(canvasCache.nodeCanvas, IOFormat, IOLocationArgs);
ImportLocationGUI = null;
modalPanelContent = null;
showModalPanel = false;
}
else
showModalPanel = false;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,365 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
using NodeEditorFramework.IO;
using Object = UnityEngine.Object;
namespace NodeEditorFramework
{
/// <summary>
/// Central class of NodeEditor providing the GUI to draw the Node Editor Canvas, bundling all other parts of the Framework
/// </summary>
public static partial class NodeEditor
{
public static string editorPath = "Assets/Plugins/Node_Editor/";
// The NodeCanvas which represents the currently drawn Node Canvas; globally accessed
public static NodeCanvas curNodeCanvas;
public static NodeEditorState curEditorState;
// GUI callback control
internal static Action NEUpdate;
public static void Update () { if (NEUpdate != null) NEUpdate (); }
public static Action ClientRepaints;
public static void RepaintClients () { if (ClientRepaints != null) ClientRepaints (); }
// Canvas Editing
private static Stack<NodeCanvas> editCanvasStack = new Stack<NodeCanvas> (4);
private static Stack<NodeEditorState> editEditorStateStack = new Stack<NodeEditorState> (4);
// Initiation
private static bool initiatedBase;
private static bool initiatedGUI;
public static bool InitiationError;
#region Setup
/// <summary>
/// Initiates the Node Editor if it wasn't yet
/// </summary>
public static void checkInit(bool GUIFunction)
{
if (!InitiationError)
{
if (!initiatedBase)
setupBaseFramework();
if (GUIFunction && !initiatedGUI)
setupGUI();
}
}
/// <summary>
/// Resets the initiation state so next time calling checkInit it will re-initiate
/// </summary>
public static void resetInit()
{
InitiationError = initiatedBase = initiatedGUI = false;
}
/// <summary>
/// Re-Inits the NodeCanvas regardless of whetehr it was initiated before
/// </summary>
public static void ReInit (bool GUIFunction)
{
InitiationError = initiatedBase = initiatedGUI = false;
setupBaseFramework ();
if (GUIFunction)
setupGUI ();
}
/// <summary>
/// Setup of the base framework. Enough to manage and calculate canvases.
/// </summary>
private static void setupBaseFramework ()
{
CheckEditorPath ();
// Init Resource system. Can be called anywhere else, too, if it's needed before.
ResourceManager.SetDefaultResourcePath (editorPath + "Resources/");
// Run fetching algorithms searching the script assemblies for Custom Nodes / Connection Types / NodeCanvas Types
ConnectionPortStyles.FetchConnectionPortStyles();
NodeTypes.FetchNodeTypes ();
NodeCanvasManager.FetchCanvasTypes ();
ConnectionPortManager.FetchNodeConnectionDeclarations ();
ImportExportManager.FetchIOFormats ();
// Setup Callback system
NodeEditorCallbacks.SetupReceivers ();
NodeEditorCallbacks.IssueOnEditorStartUp ();
// Init input
NodeEditorInputSystem.SetupInput ();
#if UNITY_EDITOR
UnityEditor.EditorApplication.update -= Update;
UnityEditor.EditorApplication.update += Update;
#endif
initiatedBase = true;
}
/// <summary>
/// Setup of the GUI. Only called when a GUI representation is actually used.
/// </summary>
private static void setupGUI ()
{
if (!initiatedBase)
setupBaseFramework();
initiatedGUI = false;
// Init GUIScaleUtility. This fetches reflected calls and might throw a message notifying about incompability.
GUIScaleUtility.CheckInit ();
if (!NodeEditorGUI.Init ())
{
InitiationError = true;
return;
}
#if UNITY_EDITOR
RepaintClients ();
#endif
initiatedGUI = true;
}
/// <summary>
/// Checks the editor path and corrects it when possible.
/// </summary>
public static void CheckEditorPath ()
{
#if UNITY_EDITOR
Object script = UnityEditor.AssetDatabase.LoadAssetAtPath (editorPath + "Framework/NodeEditor.cs", typeof(Object));
if (script == null)
{ // Not installed in default path
string[] assets = UnityEditor.AssetDatabase.FindAssets ("NodeEditorCallbackReceiver"); // Something relatively unique
if (assets.Length != 1)
{
assets = UnityEditor.AssetDatabase.FindAssets ("ConnectionPortManager"); // Another try
if (assets.Length != 1)
throw new UnityException ("Node Editor: Not installed in default directory '" + editorPath + "'! Correct path could not be detected! Please correct the editorPath variable in NodeEditor.cs!");
}
string correctEditorPath = UnityEditor.AssetDatabase.GUIDToAssetPath (assets[0]);
int subFolderIndex = correctEditorPath.LastIndexOf ("Framework/");
if (subFolderIndex == -1)
throw new UnityException ("Node Editor: Not installed in default directory '" + editorPath + "'! Correct path could not be detected! Please correct the editorPath variable in NodeEditor.cs!");
correctEditorPath = correctEditorPath.Substring (0, subFolderIndex);
//RPGTalk commented to make it easier for the noob user
/*Debug.LogWarning ("Node Editor: Not installed in default directory '" + editorPath + "'! " +
"Editor-only automatic detection adjusted the path to " + correctEditorPath + ", but if you plan to use at runtime, please correct the editorPath variable in NodeEditor.cs!");
*/
editorPath = correctEditorPath;
}
#endif
}
#endregion
#region GUI
/// <summary>
/// Draws the Node Canvas on the screen in the rect specified by editorState
/// </summary>
public static void DrawCanvas (NodeCanvas nodeCanvas, NodeEditorState editorState)
{
if (nodeCanvas == null || editorState == null || !editorState.drawing)
return;
checkInit (true);
DrawSubCanvas (nodeCanvas, editorState);
}
/// <summary>
/// Draws the Node Canvas on the screen in the rect specified by editorState without one-time wrappers like GUISkin and OverlayGUI. Made for nested Canvases (WIP)
/// </summary>
private static void DrawSubCanvas (NodeCanvas nodeCanvas, NodeEditorState editorState)
{
if (!editorState.drawing)
return;
BeginEditingCanvas (nodeCanvas, editorState);
if (curNodeCanvas == null || curEditorState == null || !curEditorState.drawing)
return;
if (Event.current.type == EventType.Repaint)
{ // Draw Background when Repainting
// Offset from origin in tile units
Vector2 tileOffset = new Vector2 (-(curEditorState.zoomPos.x * curEditorState.zoom + curEditorState.panOffset.x) / NodeEditorGUI.Background.width,
((curEditorState.zoomPos.y - curEditorState.canvasRect.height) * curEditorState.zoom + curEditorState.panOffset.y) / NodeEditorGUI.Background.height);
// Amount of tiles
Vector2 tileAmount = new Vector2 (Mathf.Round (curEditorState.canvasRect.width * curEditorState.zoom) / NodeEditorGUI.Background.width,
Mathf.Round (curEditorState.canvasRect.height * curEditorState.zoom) / NodeEditorGUI.Background.height);
// Draw tiled background
GUI.DrawTextureWithTexCoords (curEditorState.canvasRect, NodeEditorGUI.Background, new Rect (tileOffset, tileAmount));
}
// Handle input events
NodeEditorInputSystem.HandleInputEvents (curEditorState);
if (Event.current.type != EventType.Layout)
curEditorState.ignoreInput = new List<Rect> ();
// We're using a custom scale method, as default one is messing up clipping rect
Rect canvasRect = curEditorState.canvasRect;
curEditorState.zoomPanAdjust = GUIScaleUtility.BeginScale (ref canvasRect, curEditorState.zoomPos, curEditorState.zoom, NodeEditorGUI.isEditorWindow, false);
// ---- BEGIN SCALE ----
// Some features which require zoomed drawing:
if (curEditorState.navigate)
{ // Draw a curve to the origin/active node for orientation purposes
Vector2 startPos = (curEditorState.selectedNode != null? curEditorState.selectedNode.rect.center : curEditorState.panOffset) + curEditorState.zoomPanAdjust;
Vector2 endPos = Event.current.mousePosition;
RTEditorGUI.DrawLine (startPos, endPos, Color.green, null, 3);
RepaintClients ();
}
if (curEditorState.connectKnob != null)
{ // Draw the currently drawn connection
curEditorState.connectKnob.DrawConnection (Event.current.mousePosition);
RepaintClients ();
}
// Draw the groups below everything else
for (int groupCnt = 0; groupCnt < curNodeCanvas.groups.Count; groupCnt++)
{
NodeGroup group = curNodeCanvas.groups[groupCnt];
if (Event.current.type == EventType.Layout)
group.isClipped = !curEditorState.canvasViewport.Overlaps(group.fullAABBRect);
if (!group.isClipped)
group.DrawGroup();
}
// Push the active node to the top of the draw order.
if (Event.current.type == EventType.Layout && curEditorState.selectedNode != null)
{
curNodeCanvas.nodes.Remove (curEditorState.selectedNode);
curNodeCanvas.nodes.Add (curEditorState.selectedNode);
}
// Draw the transitions and connections. Has to be drawn before nodes as transitions originate from node centers
for (int nodeCnt = 0; nodeCnt < curNodeCanvas.nodes.Count; nodeCnt++)
curNodeCanvas.nodes [nodeCnt].DrawConnections ();
// Draw the nodes
for (int nodeCnt = 0; nodeCnt < curNodeCanvas.nodes.Count; nodeCnt++)
{
Node node = curNodeCanvas.nodes [nodeCnt];
if (Event.current.type == EventType.Layout)
node.isClipped = !curEditorState.canvasViewport.Overlaps(curNodeCanvas.nodes[nodeCnt].fullAABBRect);
if (!node.isClipped)
{
node.DrawNode();
if (Event.current.type == EventType.Repaint)
node.DrawKnobs();
}
}
// ---- END SCALE ----
// End scaling group
GUIScaleUtility.EndScale ();
// Handle input events with less priority than node GUI controls
NodeEditorInputSystem.HandleLateInputEvents (curEditorState);
EndEditingCanvas ();
}
/// <summary>
/// Sets the specified canvas as the current context most functions work on
/// </summary>
public static void BeginEditingCanvas (NodeCanvas canvas)
{
NodeEditorState state = canvas.editorStates.Length >= 1? canvas.editorStates[0] : null;
BeginEditingCanvas (canvas, state);
}
/// <summary>
/// Sets the specified canvas as the current context most functions work on
/// </summary>
public static void BeginEditingCanvas (NodeCanvas canvas, NodeEditorState state)
{
if (state != null && state.canvas != canvas)
state = null; // State does not belong to the canvas
editCanvasStack.Push (canvas);
editEditorStateStack.Push (state);
curNodeCanvas = canvas;
curEditorState = state;
}
/// <summary>
/// Restores the previously edited canvas as the current context
/// </summary>
public static void EndEditingCanvas ()
{
curNodeCanvas = editCanvasStack.Pop ();
curEditorState = editEditorStateStack.Pop ();
}
#endregion
#region Space Transformations
/// <summary>
/// Returns the node at the specified canvas-space position in the current editor
/// </summary>
public static Node NodeAtPosition (Vector2 canvasPos)
{
ConnectionKnob focusedKnob;
return NodeAtPosition (curEditorState, canvasPos, out focusedKnob);
}
/// <summary>
/// Returns the node at the specified canvas-space position in the current editor and returns a possible focused knob aswell
/// </summary>
public static Node NodeAtPosition (Vector2 canvasPos, out ConnectionKnob focusedKnob)
{
return NodeAtPosition (curEditorState, canvasPos, out focusedKnob);
}
/// <summary>
/// Returns the node at the specified canvas-space position in the specified editor and returns a possible focused knob aswell
/// </summary>
public static Node NodeAtPosition (NodeEditorState editorState, Vector2 canvasPos, out ConnectionKnob focusedKnob)
{
focusedKnob = null;
if (editorState == null || NodeEditorInputSystem.shouldIgnoreInput (editorState))
return null;
NodeCanvas canvas = editorState.canvas;
for (int nodeCnt = canvas.nodes.Count-1; nodeCnt >= 0; nodeCnt--)
{ // Check from top to bottom because of the render order
Node node = canvas.nodes [nodeCnt];
if (node.ClickTest (canvasPos, out focusedKnob))
return node; // Node is clicked on
}
return null;
}
/// <summary>
/// Transforms screen space elements in the current editor into canvas space (Level of Nodes, ...)
/// </summary>
public static Vector2 ScreenToCanvasSpace (Vector2 screenPos)
{
return ScreenToCanvasSpace (curEditorState, screenPos);
}
/// <summary>
/// Transforms screen space elements in the specified editor into canvas space (Level of Nodes, ...)
/// </summary>
public static Vector2 ScreenToCanvasSpace (NodeEditorState editorState, Vector2 screenPos)
{
return (screenPos - editorState.canvasRect.position - editorState.zoomPos) * editorState.zoom - editorState.panOffset;
}
#endregion
}
}

View File

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

View File

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

View File

@@ -0,0 +1,212 @@
using System.IO;
using UnityEngine;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework.IO
{
/// <summary>
/// Base class of an arbitrary Import/Export format based directly on the NodeCanvas
/// </summary>
public abstract class ImportExportFormat
{
/// <summary>
/// Identifier for this format, must be unique (e.g. 'XML')
/// </summary>
public abstract string FormatIdentifier { get; }
/// <summary>
/// Optional format description (e.g. 'Legacy', shown as 'XML (Legacy)')
/// </summary>
public virtual string FormatDescription { get { return ""; } }
/// <summary>
/// Optional extension for this format if saved as a file, e.g. 'xml', default equals FormatIdentifier
/// </summary>
public virtual string FormatExtension { get { return FormatIdentifier; } }
/// <summary>
/// Returns whether the location selection needs to be performed through a GUI (e.g. for a custom database access)
/// If true, the Import-/ExportLocationArgsGUI functions are called, else Import-/ExportLocationArgsSelection
/// </summary>
public virtual bool RequiresLocationGUI { get {
#if UNITY_EDITOR
return false; // In the editor, use file browser seletion
#else
return true; // At runtime, use GUI to select a file in a fixed folder
#endif
}
}
/// <summary>
/// Folder for runtime IO operations relative to the game folder.
/// </summary>
public virtual string RuntimeIOPath { get { return "Files/NodeEditor/"; } }
#if !UNITY_EDITOR
private string fileSelection = "";
private Rect fileSelectionMenuRect;
#endif
/// <summary>
/// Called only if RequiresLocationGUI is true.
/// Displays GUI filling in locationArgs with the information necessary to locate the import operation.
/// Override along with RequiresLocationGUI for custom database access.
/// Return true or false to perform or cancel the import operation.
/// </summary>
public virtual bool? ImportLocationArgsGUI (ref object[] locationArgs)
{
#if UNITY_EDITOR
return ImportLocationArgsSelection (out locationArgs);
#else
GUILayout.Label("Import canvas from " + FormatIdentifier);
GUILayout.BeginHorizontal();
GUILayout.Label(RuntimeIOPath, GUILayout.ExpandWidth(false));
if (GUILayout.Button(string.IsNullOrEmpty(fileSelection)? "Select..." : fileSelection + "." + FormatExtension, GUILayout.ExpandWidth(true)))
{
// Find save files
DirectoryInfo dir = Directory.CreateDirectory(RuntimeIOPath);
FileInfo[] files = dir.GetFiles("*." + FormatExtension);
// Fill save file selection menu
GenericMenu fileSelectionMenu = new GenericMenu(false);
foreach (FileInfo file in files)
fileSelectionMenu.AddItem(new GUIContent(file.Name), false, () => fileSelection = Path.GetFileNameWithoutExtension(file.Name));
fileSelectionMenu.DropDown(fileSelectionMenuRect);
}
if (Event.current.type == EventType.Repaint)
{
Rect popupPos = GUILayoutUtility.GetLastRect();
fileSelectionMenuRect = new Rect(popupPos.x + 2, popupPos.yMax + 2, popupPos.width - 4, 0);
}
GUILayout.EndHorizontal();
// Finish operation buttons
GUILayout.BeginHorizontal();
if (GUILayout.Button("Cancel"))
return false;
if (GUILayout.Button("Import"))
{
if (string.IsNullOrEmpty(fileSelection) || !File.Exists(RuntimeIOPath + fileSelection + "." + FormatExtension))
return false;
fileSelection = Path.GetFileNameWithoutExtension(fileSelection);
locationArgs = new object[] { RuntimeIOPath + fileSelection + "." + FormatExtension };
return true;
}
GUILayout.EndHorizontal();
return null;
#endif
}
/// <summary>
/// Called only if RequiresLocationGUI is false.
/// Returns the information necessary to locate the import operation.
/// By default it lets the user select a path as string[1].
/// </summary>
public virtual bool ImportLocationArgsSelection (out object[] locationArgs)
{
string path = null;
#if UNITY_EDITOR
path = UnityEditor.EditorUtility.OpenFilePanel(
"Import " + FormatIdentifier + (!string.IsNullOrEmpty (FormatDescription)? (" (" + FormatDescription + ")") : ""),
"Assets", FormatExtension.ToLower ());
#endif
locationArgs = new object[] { path };
return !string.IsNullOrEmpty (path);
}
/// <summary>
/// Called only if RequiresLocationGUI is true.
/// Displays GUI filling in locationArgs with the information necessary to locate the export operation.
/// Override along with RequiresLocationGUI for custom database access.
/// Return true or false to perform or cancel the export operation.
/// </summary>
public virtual bool? ExportLocationArgsGUI (string canvasName, ref object[] locationArgs)
{
#if UNITY_EDITOR
return ExportLocationArgsSelection(canvasName, out locationArgs);
#else
GUILayout.Label("Export canvas to " + FormatIdentifier);
// File save field
GUILayout.BeginHorizontal();
GUILayout.Label(RuntimeIOPath, GUILayout.ExpandWidth(false));
fileSelection = GUILayout.TextField(fileSelection, GUILayout.ExpandWidth(true));
GUILayout.Label("." + FormatExtension, GUILayout.ExpandWidth (false));
GUILayout.EndHorizontal();
// Finish operation buttons
GUILayout.BeginHorizontal();
if (GUILayout.Button("Cancel"))
return false;
if (GUILayout.Button("Export"))
{
if (string.IsNullOrEmpty(fileSelection))
return false;
fileSelection = Path.GetFileNameWithoutExtension(fileSelection);
locationArgs = new object[] { RuntimeIOPath + fileSelection + "." + FormatExtension };
return true;
}
GUILayout.EndHorizontal();
return null;
#endif
}
/// <summary>
/// Called only if RequiresLocationGUI is false.
/// Returns the information necessary to locate the export operation.
/// By default it lets the user select a path as string[1].
/// </summary>
public virtual bool ExportLocationArgsSelection (string canvasName, out object[] locationArgs)
{
string path = null;
#if UNITY_EDITOR
path = UnityEditor.EditorUtility.SaveFilePanel(
"Export " + FormatIdentifier + (!string.IsNullOrEmpty (FormatDescription)? (" (" + FormatDescription + ")") : ""),
"Assets", canvasName, FormatExtension.ToLower ());
#endif
locationArgs = new object[] { path };
return !string.IsNullOrEmpty (path);
}
/// <summary>
/// Imports the canvas at the location specified in the args (usually string[1] containing the path) and returns it's simplified canvas data
/// </summary>
public abstract NodeCanvas Import (params object[] locationArgs);
/// <summary>
/// Exports the given simplified canvas data to the location specified in the args (usually string[1] containing the path)
/// </summary>
public abstract void Export (NodeCanvas canvas, params object[] locationArgs);
}
/// <summary>
/// Base class of an arbitrary Import/Export format based on a simple structural data best for most formats
/// </summary>
public abstract class StructuredImportExportFormat : ImportExportFormat
{
public override NodeCanvas Import (params object[] locationArgs)
{
CanvasData data = ImportData (locationArgs);
if (data == null)
return null;
return ImportExportManager.ConvertToNodeCanvas (data);
}
public override void Export (NodeCanvas canvas, params object[] locationArgs)
{
CanvasData data = ImportExportManager.ConvertToCanvasData (canvas);
ExportData (data, locationArgs);
}
/// <summary>
/// Imports the canvas at the location specified in the args (usually string[1] containing the path) and returns it's simplified canvas data
/// </summary>
public abstract CanvasData ImportData (params object[] locationArgs);
/// <summary>
/// Exports the given simplified canvas data to the location specified in the args (usually string[1] containing the path)
/// </summary>
public abstract void ExportData (CanvasData data, params object[] locationArgs);
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 961d85cbdc1829442907cdcfd40aaf01
timeCreated: 1497884522
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework.IO
{
/// <summary>
/// Manager for Import Export operations, including fetching of all supported formats
/// </summary>
public static class ImportExportManager
{
private static Dictionary<string, ImportExportFormat> IOFormats;
private static Action<string> _importMenuCallback;
private static Action<string> _exportMenuCallback;
/// <summary>
/// Fetches every IO Format in the script assemblies to provide the framework with custom import and export formats
/// </summary>
public static void FetchIOFormats()
{
IOFormats = new Dictionary<string, ImportExportFormat>();
foreach (Type type in ReflectionUtility.getSubTypes (typeof(ImportExportFormat)))
{
ImportExportFormat formatter = (ImportExportFormat)Activator.CreateInstance (type);
IOFormats.Add (formatter.FormatIdentifier, formatter);
}
}
/// <summary>
/// Returns the format specified by the given IO format identifier
/// </summary>
public static ImportExportFormat ParseFormat (string IOformat)
{
ImportExportFormat formatter;
if (IOFormats.TryGetValue (IOformat, out formatter))
return formatter;
else
throw new ArgumentException ("Unknown format '" + IOformat + "'");
}
/// <summary>
/// Imports the canvas with the given formatter from the location specified in the args and returns it
/// </summary>
public static NodeCanvas ImportCanvas (ImportExportFormat formatter, params object[] args)
{
return formatter.Import (args);
}
/// <summary>
/// Exports the given canvas with the given formatter to the location specified in the args
/// </summary>
public static void ExportCanvas (NodeCanvas canvas, ImportExportFormat formatter, params object[] args)
{
formatter.Export (canvas, args);
}
#region Canvas Type Menu
public static void FillImportFormatMenu(ref GenericMenu menu, Action<string> IOFormatSelection, string path = "")
{
_importMenuCallback = IOFormatSelection;
foreach (string formatID in IOFormats.Keys)
menu.AddItem(new GUIContent(path + formatID), false, unwrapInputFormatIDCallback, (object)formatID);
}
#if UNITY_EDITOR
public static void FillImportFormatMenu(ref UnityEditor.GenericMenu menu, Action<string> IOFormatSelection, string path = "")
{
_importMenuCallback = IOFormatSelection;
foreach (string formatID in IOFormats.Keys)
menu.AddItem(new GUIContent(path + formatID), false, unwrapInputFormatIDCallback, (object)formatID);
}
#endif
public static void FillExportFormatMenu(ref GenericMenu menu, Action<string> IOFormatSelection, string path = "")
{
_exportMenuCallback = IOFormatSelection;
foreach (string formatID in IOFormats.Keys)
menu.AddItem(new GUIContent(path + formatID), false, unwrapExportFormatIDCallback, (object)formatID);
}
#if UNITY_EDITOR
public static void FillExportFormatMenu(ref UnityEditor.GenericMenu menu, Action<string> IOFormatSelection, string path = "")
{
_exportMenuCallback = IOFormatSelection;
foreach (string formatID in IOFormats.Keys)
menu.AddItem(new GUIContent(path + formatID), false, unwrapExportFormatIDCallback, (object)formatID);
}
#endif
private static void unwrapInputFormatIDCallback(object formatID)
{
_importMenuCallback((string)formatID);
}
private static void unwrapExportFormatIDCallback(object formatID)
{
_exportMenuCallback((string)formatID);
}
#endregion
#region Converter
/// <summary>
/// Converts the NodeCanvas to a simplified CanvasData
/// </summary>
internal static CanvasData ConvertToCanvasData (NodeCanvas canvas)
{
if (canvas == null)
return null;
// Validate canvas and create canvas data for it
canvas.Validate ();
CanvasData canvasData = new CanvasData (canvas);
// Store Lookup-Table for all ports
Dictionary<ConnectionPort, PortData> portDatas = new Dictionary<ConnectionPort, PortData>();
foreach (Node node in canvas.nodes)
{
// Create node data
NodeData nodeData = new NodeData (node);
canvasData.nodes.Add (nodeData.nodeID, nodeData);
foreach (ConnectionPortDeclaration portDecl in ConnectionPortManager.GetPortDeclarationEnumerator(node))
{ // Fetch all static connection port declarations and record them
ConnectionPort port = (ConnectionPort)portDecl.portField.GetValue(node);
PortData portData = new PortData(nodeData, port, portDecl.portField.Name);
nodeData.connectionPorts.Add(portData);
portDatas.Add(port, portData);
}
foreach (ConnectionPort port in node.dynamicConnectionPorts)
{ // Fetch all dynamic connection ports and record them
PortData portData = new PortData(nodeData, port);
nodeData.connectionPorts.Add(portData);
portDatas.Add(port, portData);
}
// Fetch all serialized node variables specific to each node's implementation
FieldInfo[] serializedFields = ReflectionUtility.getSerializedFields (node.GetType (), typeof(Node));
foreach (FieldInfo field in serializedFields)
{ // Create variable data and enter the
if (field.FieldType.IsSubclassOf(typeof(ConnectionPort)))
continue;
VariableData varData = new VariableData (field);
nodeData.variables.Add (varData);
object varValue = field.GetValue (node);
if (field.FieldType.IsValueType) // Store value of the object
varData.value = varValue;
else // Store reference to the object
varData.refObject = canvasData.ReferenceObject (varValue);
}
}
foreach (PortData portData in portDatas.Values)
{ // Record the connections of this port
foreach (ConnectionPort conPort in portData.port.connections)
{
PortData conPortData; // Get portData associated with the connection port
if (portDatas.TryGetValue(conPort, out conPortData))
canvasData.RecordConnection(portData, conPortData);
}
}
foreach (NodeGroup group in canvas.groups)
{ // Record all groups
canvasData.groups.Add(new GroupData(group));
}
canvasData.editorStates = new EditorStateData[canvas.editorStates.Length];
for (int i = 0; i < canvas.editorStates.Length; i++)
{ // Record all editorStates
NodeEditorState state = canvas.editorStates[i];
NodeData selected = state.selectedNode == null ? null : canvasData.FindNode(state.selectedNode);
canvasData.editorStates[i] = new EditorStateData(selected, state.panOffset, state.zoom);
}
return canvasData;
}
/// <summary>
/// Converts the simplified CanvasData back to a proper NodeCanvas
/// </summary>
internal static NodeCanvas ConvertToNodeCanvas (CanvasData canvasData)
{
if (canvasData == null)
return null;
NodeCanvas nodeCanvas = NodeCanvas.CreateCanvas(canvasData.type);
nodeCanvas.name = nodeCanvas.saveName = canvasData.name;
nodeCanvas.nodes.Clear();
NodeEditor.BeginEditingCanvas(nodeCanvas);
foreach (NodeData nodeData in canvasData.nodes.Values)
{ // Read all nodes
Node node = Node.Create (nodeData.typeID, nodeData.nodePos, null, true, false);
if (!string.IsNullOrEmpty(nodeData.name))
node.name = nodeData.name;
if (node == null)
continue;
foreach (ConnectionPortDeclaration portDecl in ConnectionPortManager.GetPortDeclarationEnumerator(node))
{ // Find stored ports for each node port declaration
PortData portData = nodeData.connectionPorts.Find((PortData data) => data.name == portDecl.portField.Name);
if (portData != null) // Stored port has been found, record
portData.port = (ConnectionPort)portDecl.portField.GetValue(node);
}
foreach (PortData portData in nodeData.connectionPorts.Where(port => port.dynamic))
{ // Find stored dynamic connection ports
if (portData.port != null) // Stored port has been recreated
{
portData.port.body = node;
node.dynamicConnectionPorts.Add(portData.port);
}
}
foreach (VariableData varData in nodeData.variables)
{ // Restore stored variable to node
FieldInfo field = node.GetType().GetField(varData.name);
if (field != null)
field.SetValue(node, varData.refObject != null ? varData.refObject.data : varData.value);
}
}
foreach (ConnectionData conData in canvasData.connections)
{ // Restore all connections
if (conData.port1.port == null || conData.port2.port == null)
{ // Not all ports where saved in canvasData
Debug.Log("Incomplete connection " + conData.port1.name + " and " + conData.port2.name + "!");
continue;
}
conData.port1.port.TryApplyConnection(conData.port2.port, true);
}
foreach (GroupData groupData in canvasData.groups)
{ // Recreate groups
NodeGroup group = new NodeGroup();
group.title = groupData.name;
group.rect = groupData.rect;
group.color = groupData.color;
nodeCanvas.groups.Add(group);
}
nodeCanvas.editorStates = new NodeEditorState[canvasData.editorStates.Length];
for (int i = 0; i < canvasData.editorStates.Length; i++)
{ // Read all editorStates
EditorStateData stateData = canvasData.editorStates[i];
NodeEditorState state = ScriptableObject.CreateInstance<NodeEditorState>();
state.selectedNode = stateData.selectedNode == null ? null : canvasData.FindNode(stateData.selectedNode.nodeID).node;
state.panOffset = stateData.panOffset;
state.zoom = stateData.zoom;
state.canvas = nodeCanvas;
state.name = "EditorState";
nodeCanvas.editorStates[i] = state;
}
NodeEditor.EndEditingCanvas();
return nodeCanvas;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7761e7c3d61739f4bbb8b023e3e91adc
timeCreated: 1497983354
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace NodeEditorFramework.IO
{
public class CanvasData
{
public NodeCanvas canvas;
public string name;
public Type type;
public EditorStateData[] editorStates;
public List<GroupData> groups = new List<GroupData>();
public Dictionary<int, NodeData> nodes = new Dictionary<int, NodeData>();
public List<ConnectionData> connections = new List<ConnectionData>();
public Dictionary<int, ObjectData> objects = new Dictionary<int, ObjectData>();
public CanvasData(NodeCanvas nodeCanvas)
{
canvas = nodeCanvas;
name = nodeCanvas.name;
type = nodeCanvas.GetType();
}
public CanvasData(Type canvasType, string canvasName)
{
type = canvasType;
name = canvasName;
}
public ObjectData ReferenceObject(object obj)
{
if (obj == null)
return null;
foreach (ObjectData data in objects.Values)
{
if (data.data == obj)
return data;
}
ObjectData objData = new ObjectData(obj);
objects.Add(objData.refID, objData);
return objData;
}
public ObjectData FindObject(int refID)
{
ObjectData data;
objects.TryGetValue(refID, out data);
return data;
}
public NodeData FindNode(Node node)
{
foreach (NodeData data in nodes.Values)
{
if (data.node == node)
return data;
}
return null;
}
public NodeData FindNode(int nodeID)
{
NodeData data;
nodes.TryGetValue(nodeID, out data);
return data;
}
public bool RecordConnection(PortData portData1, PortData portData2)
{
if (!portData1.connections.Contains(portData2))
portData1.connections.Add(portData2);
if (!portData2.connections.Contains(portData1))
portData2.connections.Add(portData1);
if (!connections.Exists((ConnectionData conData) => conData.isPart(portData1) && conData.isPart(portData2)))
{ // Connection hasn't already been recorded
ConnectionData conData = new ConnectionData(portData1, portData2);
connections.Add(conData);
return true;
}
return false;
}
}
public class EditorStateData
{
public NodeData selectedNode;
public Vector2 panOffset;
public float zoom;
public EditorStateData(NodeData SelectedNode, Vector2 PanOffset, float Zoom)
{
selectedNode = SelectedNode;
panOffset = PanOffset;
zoom = Zoom;
}
}
public class GroupData
{
public string name;
public Rect rect;
public Color color;
public GroupData(NodeGroup group)
{
name = group.title;
rect = group.rect;
color = group.color;
}
public GroupData(string Name, Rect Rect, Color Color)
{
name = Name;
rect = Rect;
color = Color;
}
}
public class NodeData
{
public Node node;
public string name;
public int nodeID;
public string typeID;
public Vector2 nodePos;
public Type type;
public List<PortData> connectionPorts = new List<PortData>();
public List<VariableData> variables = new List<VariableData>();
public NodeData(Node n)
{
node = n;
name = n.name;
typeID = node.GetID;
nodeID = node.GetHashCode();
nodePos = node.rect.position;
type = n.GetType();
}
public NodeData(string Name, string TypeID, int NodeID, Vector2 Pos)
{
name = Name;
typeID = TypeID;
nodeID = NodeID;
nodePos = Pos;
type = NodeTypes.getNodeData(typeID).type;
}
}
public class PortData
{
public ConnectionPort port;
public int portID;
public NodeData body;
public string name;
public bool dynamic = false;
public Type dynaType;
public List<PortData> connections = new List<PortData>();
// STATIC
public PortData(NodeData Body, ConnectionPort Port, string VarName)
{
port = Port;
portID = port.GetHashCode();
body = Body;
name = VarName;
}
public PortData(NodeData Body, string VarName, int PortID)
{
portID = PortID;
body = Body;
name = VarName;
}
// DYNAMIC
public PortData(NodeData Body, ConnectionPort DynamicPort)
{
dynamic = true;
port = DynamicPort;
portID = port.GetHashCode();
body = Body;
name = DynamicPort.name;
dynaType = DynamicPort.GetType();
}
public PortData(NodeData Body, ConnectionPort DynamicPort, int PortID)
{
dynamic = true;
port = DynamicPort;
portID = PortID;
body = Body;
name = DynamicPort.name;
dynaType = DynamicPort.GetType();
}
}
public class ConnectionData
{
public PortData port1;
public PortData port2;
public ConnectionData(PortData Port1, PortData Port2)
{
port1 = Port1;
port2 = Port2;
}
public bool isPart (PortData port)
{
return port1 == port || port2 == port;
}
}
public class VariableData
{
public string name;
public ObjectData refObject;
public object value;
public VariableData(FieldInfo field)
{
name = field.Name;
}
public VariableData(string fieldName)
{
name = fieldName;
}
}
public class ObjectData
{
public int refID;
public Type type;
public object data;
public ObjectData(object obj)
{
if (obj == null)
throw new ArgumentNullException("obj");
refID = obj.GetHashCode();
// Some types like MonoScript implement no proper GetHashCode function
if (Mathf.Abs (refID) < 10)
refID = (int)UnityEngine.Random.value * int.MaxValue;
type = obj.GetType();
data = obj;
}
public ObjectData(int objRefID, object obj)
{
refID = objRefID;
type = obj.GetType();
data = obj;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 84af943011bc7e6448cd010e393c6c02
timeCreated: 1498754587
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using UnityEngine;
namespace NodeEditorFramework
{
public partial class NodeCanvasSceneSave : MonoBehaviour
{
public string saveName;
public NodeCanvas savedNodeCanvas;
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3f1cd8579a6c9ec4b85d9d62cd5ba58b
timeCreated: 1464014559
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,613 @@
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using NodeEditorFramework.Utilities;
#if UNITY_5_3_OR_NEWER || UNITY_5_3
using UnityEngine.SceneManagement;
#endif
namespace NodeEditorFramework
{
/// <summary>
/// Manager handling all save and load operations on NodeCanvases and NodeEditorStates of the Node Editor, both as assets and in the scene
/// </summary>
public static partial class NodeEditorSaveManager
{
#region Scene Saving
private static GameObject sceneSaveHolder;
private const string sceneSaveHolderName = "NodeEditor_SceneSaveHolder";
#region Utility
/// <summary>
/// Gets all existing stored saves in the current scenes and returns their names
/// </summary>
public static string[] GetSceneSaves()
{
return Object.FindObjectsOfType<NodeCanvasSceneSave>()
.Where((NodeCanvasSceneSave save) => save.savedNodeCanvas != null && save.saveName.ToLower() != "lastsession")
#if UNITY_5_3_OR_NEWER || UNITY_5_3
.Select((NodeCanvasSceneSave save) => SceneManager.sceneCount > 1 ? (save.gameObject.scene.name + "::" + save.saveName) : save.saveName)
#else
.Select((NodeCanvasSceneSave save) => save.saveName)
#endif
.ToArray();
}
/// <summary>
/// Returns whether a sceneSave with the specified name exists in the current scene
/// </summary>
public static bool HasSceneSave (string saveName)
{
NodeCanvasSceneSave save = FindSceneSave(saveName);
return save != null && save.savedNodeCanvas != null;
}
/// <summary>
/// Deletes the nodeCanvas stored in the current scene under the specified name
/// </summary>
public static void DeleteSceneNodeCanvas (string saveName)
{
if (string.IsNullOrEmpty (saveName))
return;
NodeCanvasSceneSave sceneSave = FindSceneSave (saveName);
if (sceneSave != null)
{
#if UNITY_EDITOR
Object.DestroyImmediate (sceneSave);
#else
Object.Destroy (sceneSave);
#endif
}
}
/// <summary>
/// Returns the scene save with the specified name in the current scene
/// </summary>
private static NodeCanvasSceneSave FindSceneSave(string saveName, bool forceCreation = false)
{
string scene = null;
#if UNITY_5_3_OR_NEWER || UNITY_5_3
bool sceneSpecified = saveName.Contains("::");
if (sceneSpecified)
{
string[] sp = saveName.Split(new string[] { "::" }, System.StringSplitOptions.None);
scene = sp[0];
saveName = sp[1];
}
#endif
NodeCanvasSceneSave sceneSave = Object.FindObjectsOfType<NodeCanvasSceneSave>()
#if UNITY_5_3_OR_NEWER || UNITY_5_3 // Filter by scene
.Where((NodeCanvasSceneSave save) => !sceneSpecified || save.savedNodeCanvas == null || save.gameObject.scene.name == scene)
#endif
.FirstOrDefault((NodeCanvasSceneSave save) =>
save.saveName.ToLower() == saveName.ToLower() ||
(save.savedNodeCanvas != null && save.savedNodeCanvas.name.ToLower() == saveName.ToLower()));
if (sceneSave == null && forceCreation)
sceneSave = CreateSceneSave(saveName, scene);
return sceneSave;
}
/// <summary>
/// Creates a scene save with the specified name in the current scene
/// </summary>
private static NodeCanvasSceneSave CreateSceneSave(string saveName, string sceneName = null)
{
GameObject holder = GetSceneSaveHolder(sceneName);
NodeCanvasSceneSave sceneSave = holder.AddComponent<NodeCanvasSceneSave>();
sceneSave.saveName = saveName;
return sceneSave;
}
/// <summary>
/// Gets the current sceneSaveHolder or creates it, optionally in the specified scene
/// </summary>
private static GameObject GetSceneSaveHolder(string sceneName = null)
{
#if UNITY_5_3_OR_NEWER || UNITY_5_3 // Reset sceneSaveHolder if it's in the wrong scene
bool sceneSpecified = !string.IsNullOrEmpty(sceneName);
if (sceneSpecified && sceneSaveHolder != null && sceneSaveHolder.scene.name != sceneName)
sceneSaveHolder = null;
#endif
if (sceneSaveHolder == null)
{ // Fetch scene save holder from scene
sceneSaveHolder = Object.FindObjectsOfType<NodeCanvasSceneSave>()
.Select(s => s.gameObject).Distinct()
.OrderBy(g => g.name == sceneSaveHolderName ? 1 : 2)
#if UNITY_5_3_OR_NEWER || UNITY_5_3
.Where(g => !sceneSpecified || g.scene.name == sceneName)
#endif
.FirstOrDefault();
}
if (sceneSaveHolder == null)
{ // Create new scene save holder in scene
#if UNITY_5_3_OR_NEWER || UNITY_5_3 // Make sure it is created in the desired scene
if (sceneSpecified && SceneManager.sceneCount > 1)
SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName));
#endif
sceneSaveHolder = new GameObject(sceneSaveHolderName);
}
if (sceneSaveHolder != null)
sceneSaveHolder.hideFlags = HideFlags.None;//HideFlags.HideInHierarchy | HideFlags.HideInInspector;
return sceneSaveHolder;
}
#endregion
/// <summary>
/// Saves the nodeCanvas in the current scene under the specified name, optionally as a working copy and overwriting any existing save at path
/// If the specified canvas is stored as an asset, the saved canvas will loose the reference to the asset
/// </summary>
public static void SaveSceneNodeCanvas (string saveName, ref NodeCanvas nodeCanvas, bool createWorkingCopy, bool safeOverwrite = true)
{
if (string.IsNullOrEmpty (saveName)) throw new System.ArgumentNullException ("Cannot save Canvas to scene: No save name specified!");
if (nodeCanvas == null) throw new System.ArgumentNullException ("Cannot save NodeCanvas: The specified NodeCanvas that should be saved as '" + saveName + "' is null!");
if (nodeCanvas.GetType () == typeof(NodeCanvas)) throw new System.ArgumentException ("Cannot save NodeCanvas: The NodeCanvas has no explicit type! Please convert it to a valid sub-type of NodeCanvas!");
if (saveName.StartsWith ("SCENE/"))
saveName = saveName.Substring (6);
nodeCanvas.Validate();
if (!nodeCanvas.livesInScene
#if UNITY_EDITOR // Make sure the canvas has no reference to an asset
|| UnityEditor.AssetDatabase.Contains (nodeCanvas)
#endif
) {
Debug.LogWarning ("Creating scene save '" + nodeCanvas.name + "' for canvas saved as an asset! Forcing creation of working copy!");
nodeCanvas = CreateWorkingCopy(nodeCanvas);
}
// Update the source of the canvas
nodeCanvas.UpdateSource ("SCENE/" + saveName);
// Preprocess the canvas
NodeCanvas processedCanvas = nodeCanvas;
processedCanvas.OnBeforeSavingCanvas ();
if (createWorkingCopy)
processedCanvas = CreateWorkingCopy(processedCanvas);
// Get the saveHolder and store the canvas
NodeCanvas savedCanvas = processedCanvas;
NodeCanvasSceneSave sceneSave = FindSceneSave(saveName, true);
#if UNITY_EDITOR
if (sceneSave.savedNodeCanvas != null && safeOverwrite && sceneSave.savedNodeCanvas.GetType() == savedCanvas.GetType()) // OVERWRITE
OverwriteCanvas(ref sceneSave.savedNodeCanvas, savedCanvas);
if (!Application.isPlaying)
{ // Set Dirty
UnityEditor.EditorUtility.SetDirty (sceneSave.gameObject);
#if UNITY_5_3_OR_NEWER || UNITY_5_3
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty (sceneSave.gameObject.scene);
#else
UnityEditor.EditorApplication.MarkSceneDirty ();
#endif
}
#endif
sceneSave.savedNodeCanvas = savedCanvas;
NodeEditorCallbacks.IssueOnSaveCanvas (savedCanvas);
}
/// <summary>
/// Loads the nodeCanvas stored in the current scene under the specified name and optionally creates a working copy of it before returning
/// </summary>
public static NodeCanvas LoadSceneNodeCanvas (string saveName, bool createWorkingCopy)
{
if (string.IsNullOrEmpty (saveName))
throw new System.ArgumentNullException ("Cannot load Canvas from scene: No save name specified!");
if (saveName.StartsWith ("SCENE/"))
saveName = saveName.Substring (6);
// Load SceneSave
NodeCanvasSceneSave sceneSave = FindSceneSave (saveName);
if (sceneSave == null || sceneSave.savedNodeCanvas == null)
return null;
// Extract the saved canvas and editorStates
NodeCanvas savedCanvas = sceneSave.savedNodeCanvas;
// Set the saveName as the new source of the canvas
savedCanvas.UpdateSource ("SCENE/" + saveName);
// Postprocess the loaded canvas
savedCanvas.Validate();
if (createWorkingCopy)
savedCanvas = CreateWorkingCopy(savedCanvas);
NodeEditorCallbacks.IssueOnLoadCanvas (savedCanvas);
return savedCanvas;
}
#endregion
#region Asset Saving
/// <summary>
/// Saves the the specified NodeCanvas as a new asset at path, optionally as a working copy and overwriting any existing save at path
/// </summary>
public static void SaveNodeCanvas (string path, ref NodeCanvas nodeCanvas, bool createWorkingCopy, bool safeOverwrite = true)
{
#if !UNITY_EDITOR
throw new System.NotImplementedException ();
#else
if (string.IsNullOrEmpty (path)) throw new System.ArgumentNullException ("Cannot save NodeCanvas: No path specified!");
if (nodeCanvas == null) throw new System.ArgumentNullException ("Cannot save NodeCanvas: The specified NodeCanvas that should be saved to path '" + path + "' is null!");
if (nodeCanvas.GetType () == typeof(NodeCanvas)) throw new System.ArgumentException ("Cannot save NodeCanvas: The NodeCanvas has no explicit type! Please convert it to a valid sub-type of NodeCanvas!");
if (nodeCanvas.allowSceneSaveOnly) throw new System.InvalidOperationException("Cannot save NodeCanvas: NodeCanvas is marked to contain scene data and cannot be saved as an asset!");
nodeCanvas.Validate();
if (nodeCanvas.livesInScene)
{
Debug.LogWarning ("Attempting to save scene canvas '" + nodeCanvas.name + "' to an asset, references to scene object may be broken!" + (!createWorkingCopy? " Forcing creation of working copy!" : ""));
createWorkingCopy = true;
}
if (UnityEditor.AssetDatabase.Contains (nodeCanvas) && UnityEditor.AssetDatabase.GetAssetPath (nodeCanvas) != path)
{
Debug.LogWarning ("Trying to create a duplicate save file for '" + nodeCanvas.name + "'! Forcing creation of working copy!");
nodeCanvas = CreateWorkingCopy(nodeCanvas);
}
// Prepare and update source path of the canvas
path = ResourceManager.PreparePath (path);
nodeCanvas.UpdateSource (path);
// Preprocess the canvas
NodeCanvas processedCanvas = nodeCanvas;
processedCanvas.OnBeforeSavingCanvas ();
if (createWorkingCopy)
processedCanvas = CreateWorkingCopy(processedCanvas);
// Differenciate canvasSave as the canvas asset and nodeCanvas as the source incase an existing save has been overwritten
NodeCanvas canvasSave = processedCanvas;
NodeCanvas prevSave;
if (safeOverwrite && (prevSave = ResourceManager.LoadResource<NodeCanvas> (path)) != null && prevSave.GetType () == canvasSave.GetType ())
{ // OVERWRITE: Delete contents of old save
Object[] subAssets = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(path);
for (int i = 0; i < subAssets.Length; i++)
{ // Delete all subassets except the main canvas to preserve references
if (subAssets[i] != prevSave)
Object.DestroyImmediate(subAssets[i], true);
}
// Overwrite main canvas
OverwriteCanvas (ref prevSave, processedCanvas);
canvasSave = prevSave;
}
else
{ // Write main canvas
UnityEditor.AssetDatabase.DeleteAsset(path);
UnityEditor.AssetDatabase.CreateAsset(processedCanvas, path);
}
// Write editorStates
AddSubAssets (processedCanvas.editorStates, canvasSave);
// Write nodes + contents
foreach (Node node in processedCanvas.nodes)
{ // Write node and additional scriptable objects
AddSubAsset (node, canvasSave);
AddSubAssets (node.GetScriptableObjects (), node);
// Make sure all node ports are included in the representative connectionPorts list
ConnectionPortManager.UpdatePortLists(node);
foreach (ConnectionPort port in node.connectionPorts)
AddSubAsset (port, node);
}
UnityEditor.AssetDatabase.SaveAssets ();
UnityEditor.AssetDatabase.Refresh ();
NodeEditorCallbacks.IssueOnSaveCanvas (canvasSave);
#endif
}
/// <summary>
/// Loads the NodeCanvas from the asset file at path and optionally creates a working copy of it before returning
/// </summary>
public static NodeCanvas LoadNodeCanvas (string path, bool createWorkingCopy)
{
#if !UNITY_EDITOR
throw new System.NotImplementedException ();
#else
if (string.IsNullOrEmpty (path))
throw new System.ArgumentNullException ("Cannot load Canvas: No path specified!");
path = ResourceManager.PreparePath (path);
// Load only the NodeCanvas from the save file
NodeCanvas nodeCanvas = ResourceManager.LoadResource<NodeCanvas> (path);
if (nodeCanvas == null)
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!");
if (!Application.isPlaying && (nodeCanvas.editorStates == null || nodeCanvas.editorStates.Length == 0))
{ // Try to load any contained editorStates, as the canvas did not reference any
nodeCanvas.editorStates = ResourceManager.LoadResources<NodeEditorState> (path);
}
// Set the path as the new source of the canvas
nodeCanvas.UpdateSource (path);
// Postprocess the loaded canvas
nodeCanvas.Validate();
if (createWorkingCopy)
nodeCanvas = CreateWorkingCopy(nodeCanvas);
NodeEditorCallbacks.IssueOnLoadCanvas (nodeCanvas);
return nodeCanvas;
#endif
}
#region Utility
#if UNITY_EDITOR
/// <summary>
/// Adds the specified hidden subAssets to the mainAsset
/// </summary>
public static void AddSubAssets (ScriptableObject[] subAssets, ScriptableObject mainAsset)
{
foreach (ScriptableObject subAsset in subAssets)
AddSubAsset (subAsset, mainAsset);
}
/// <summary>
/// Adds the specified hidden subAsset to the mainAsset
/// </summary>
public static void AddSubAsset (ScriptableObject subAsset, ScriptableObject mainAsset)
{
if (subAsset != null && mainAsset != null)
{
UnityEditor.AssetDatabase.AddObjectToAsset (subAsset, mainAsset);
subAsset.hideFlags = HideFlags.HideInHierarchy;
}
}
/// <summary>
/// Adds the specified hidden subAsset to the mainAsset at path
/// </summary>
public static void AddSubAsset (ScriptableObject subAsset, string path)
{
if (subAsset != null && !string.IsNullOrEmpty (path))
{
UnityEditor.AssetDatabase.AddObjectToAsset (subAsset, path);
subAsset.hideFlags = HideFlags.HideInHierarchy;
}
}
#endif
#endregion
#endregion
#region Working Copy
/// <summary>
/// Creates a working copy of the specified nodeCanvas, and optionally also of it's associated editorStates.
/// This breaks the link of this object to any stored assets and references. That means, that all changes to this object will have to be explicitly saved.
/// </summary>
public static NodeCanvas CreateWorkingCopy (NodeCanvas nodeCanvas, bool editorStates = true)
{
if (nodeCanvas == null)
return null;
// Lists holding initial and cloned version of each ScriptableObject for later replacement of references
List<ScriptableObject> allSOs = new List<ScriptableObject> ();
List<ScriptableObject> clonedSOs = new List<ScriptableObject> ();
System.Func<ScriptableObject, ScriptableObject> copySOs = (ScriptableObject so) => ReplaceSO (allSOs, clonedSOs, so);
// Clone and enter the canvas object and it's referenced SOs
nodeCanvas = AddClonedSO (allSOs, clonedSOs, nodeCanvas);
AddClonedSOs (allSOs, clonedSOs, nodeCanvas.GetScriptableObjects ());
// Iterate over the core ScriptableObjects in the canvas and clone them
for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++)
{
Node node = nodeCanvas.nodes[nodeCnt];
// Clone Node and additional scriptableObjects
Node clonedNode = AddClonedSO (allSOs, clonedSOs, node);
AddClonedSOs (allSOs, clonedSOs, clonedNode.GetScriptableObjects ());
// Update representative port list connectionPorts with all ports and clone them
ConnectionPortManager.UpdatePortLists(clonedNode);
AddClonedSOs(allSOs, clonedSOs, clonedNode.connectionPorts);
}
// Replace every reference to any of the initial SOs of the first list with the respective clones of the second list
nodeCanvas.CopyScriptableObjects (copySOs);
for (int nodeCnt = 0; nodeCnt < nodeCanvas.nodes.Count; nodeCnt++)
{ // Replace SOs in all Nodes
Node node = nodeCanvas.nodes[nodeCnt];
// Replace node and additional ScriptableObjects with their copies
Node clonedNode = nodeCanvas.nodes[nodeCnt] = ReplaceSO (allSOs, clonedSOs, node);
clonedNode.CopyScriptableObjects (copySOs);
// Replace ConnectionPorts
foreach (ConnectionPortDeclaration portDecl in ConnectionPortManager.GetPortDeclarationEnumerator(clonedNode, true))
{ // Iterate over each static port declaration and replace it with it's connections
ConnectionPort port = (ConnectionPort)portDecl.portField.GetValue(clonedNode);
port = ReplaceSO(allSOs, clonedSOs, port);
for (int c = 0; c < port.connections.Count; c++)
port.connections[c] = ReplaceSO(allSOs, clonedSOs, port.connections[c]);
port.body = clonedNode;
portDecl.portField.SetValue(clonedNode, port);
}
for (int i = 0; i < clonedNode.dynamicConnectionPorts.Count; i++)
{ // Iterate over all dynamic connection ports
ConnectionPort port = clonedNode.dynamicConnectionPorts[i];
port = ReplaceSO(allSOs, clonedSOs, port);
for (int c = 0; c < port.connections.Count; c++)
port.connections[c] = ReplaceSO(allSOs, clonedSOs, port.connections[c]);
port.body = clonedNode;
clonedNode.dynamicConnectionPorts[i] = port;
}
}
if (editorStates) // Clone the editorStates
nodeCanvas.editorStates = CreateWorkingCopy (nodeCanvas.editorStates, nodeCanvas);
// Fix references in editorStates to Nodes in the canvas
foreach (NodeEditorState state in nodeCanvas.editorStates)
state.selectedNode = ReplaceSO (allSOs, clonedSOs, state.selectedNode);
return nodeCanvas;
}
/// <summary>
/// Creates a working copy of the specified editorStates. Also remains the link of the canvas to these associated editorStates.
/// This breaks the link of this object to any stored assets and references. That means, that all changes to this object will have to be explicitly saved.
/// </summary>
private static NodeEditorState[] CreateWorkingCopy (NodeEditorState[] editorStates, NodeCanvas associatedNodeCanvas)
{
if (editorStates == null)
return new NodeEditorState[0];
// Clone list
List<NodeEditorState> clonedStates = new List<NodeEditorState> (editorStates.Length);
for (int stateCnt = 0; stateCnt < editorStates.Length; stateCnt++)
{
if (editorStates[stateCnt] == null)
continue;
// Clone editorState
NodeEditorState state = Clone (editorStates[stateCnt]);
if (state != null)
{ // Add it to the clone list
state.canvas = associatedNodeCanvas;
clonedStates.Add (state);
}
}
associatedNodeCanvas.editorStates = clonedStates.ToArray ();
return associatedNodeCanvas.editorStates;
}
#region Utility
/// <summary>
/// Clones the specified SO, preserving its name
/// </summary>
private static T Clone<T> (T SO) where T : ScriptableObject
{
string soName = SO.name;
SO = Object.Instantiate<T> (SO);
SO.name = soName;
return SO;
}
/// <summary>
/// Clones SOs and writes both the initial and cloned versions into the respective list
/// </summary>
private static void AddClonedSOs<T> (List<ScriptableObject> scriptableObjects, List<ScriptableObject> clonedScriptableObjects, IEnumerable<T> initialSOs) where T : ScriptableObject
{
// Filter out all new SOs to add
IEnumerable<T> newSOs = initialSOs.Where ((T so) => !scriptableObjects.Contains (so));
foreach (T SO in newSOs)
{ // Clone and record them
scriptableObjects.Add(SO);
clonedScriptableObjects.Add(Clone(SO));
}
}
/// <summary>
/// Clones SO and writes both the initial and cloned versions into the respective list
/// </summary>
private static T AddClonedSO<T> (List<ScriptableObject> scriptableObjects, List<ScriptableObject> clonedScriptableObjects, T initialSO) where T : ScriptableObject
{
if (initialSO == null)
return null;
// Do not clone again if it already has been
int existing;
if ((existing = scriptableObjects.IndexOf (initialSO)) >= 0)
return clonedScriptableObjects[existing] as T;
// Clone SO and add both versions to the respective list
scriptableObjects.Add (initialSO);
T clonedSO = Clone (initialSO);
clonedScriptableObjects.Add (clonedSO);
return clonedSO;
}
/// <summary>
/// Returns a clone of initialSO and adds both versions to their respective list for later replacement
/// </summary>
private static T ReplaceSO<T> (List<ScriptableObject> scriptableObjects, List<ScriptableObject> clonedScriptableObjects, T initialSO) where T : ScriptableObject
{
if (initialSO == null)
return null;
int soInd = scriptableObjects.IndexOf (initialSO);
if (soInd < 0)
Debug.LogError("GetWorkingCopy: ScriptableObject " + initialSO.name + " was not copied before! It will be null!");
else
{
ScriptableObject SO = clonedScriptableObjects[soInd];
if (!SO)
Debug.LogError("GetWorkingCopy: ScriptableObject " + initialSO.name + " has been recorded with a null copy!");
else if (SO is T)
return clonedScriptableObjects[soInd] as T;
else
Debug.LogError("GetWorkingCopy: ScriptableObject " + initialSO.name + " is not of the same type as copy " + clonedScriptableObjects[soInd].name + "!");
}
return null;
}
#endregion
#endregion
#region Utility
/// <summary>
/// Returns the editorState with the specified name in canvas.
/// If not found but others and forceFind is false, a different one is chosen randomly, else a new one is created.
/// </summary>
public static NodeEditorState ExtractEditorState (NodeCanvas canvas, string stateName, bool forceFind = false)
{
NodeEditorState state = null;
if (canvas.editorStates.Length > 0) // Search for the editorState
state = canvas.editorStates.FirstOrDefault ((NodeEditorState s) => s.name == stateName);
if (state == null && !forceFind) // Take any other if not found
state = canvas.editorStates.FirstOrDefault();
if (state == null)
{ // Create editorState
state = ScriptableObject.CreateInstance<NodeEditorState>();
state.canvas = canvas;
// Insert into list
int index = canvas.editorStates.Length;
System.Array.Resize(ref canvas.editorStates, index + 1);
canvas.editorStates[index] = state;
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(canvas);
#endif
}
state.name = stateName;
return state;
}
/// <summary>
/// Overwrites canvas with the contents of canvasData, so that all references to canvas will be remained, but both canvases are still seperate.
/// Only works in the editor!
/// </summary>
public static void OverwriteCanvas (ref NodeCanvas targetCanvas, NodeCanvas canvasData)
{
#if UNITY_EDITOR
if (canvasData == null)
throw new System.ArgumentNullException ("Cannot overwrite canvas as data is null!");
if (targetCanvas == null)
targetCanvas = NodeCanvas.CreateCanvas (canvasData.GetType ());
UnityEditor.EditorUtility.CopySerialized (canvasData, targetCanvas);
targetCanvas.name = canvasData.name;
#else
throw new System.NotSupportedException ("Cannot overwrite canvas in player!");
#endif
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 536538853a8430243a2f38355c7d4bbd
timeCreated: 1453840956
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,393 @@
#if UNITY_EDITOR
#define CACHE
#endif
using System;
using System.IO;
using UnityEngine;
using NodeEditorFramework.Utilities;
namespace NodeEditorFramework
{
public class NodeEditorUserCache
{
public NodeCanvas nodeCanvas;
public NodeEditorState editorState;
public string openedCanvasPath = "";
public Type defaultNodeCanvasType;
public NodeCanvasTypeData typeData;
private const string MainEditorStateIdentifier = "MainEditorState";
#if CACHE
private const bool cacheWorkingCopy = true;
private const int cacheIntervalSec = 60;
private bool useCache = false;
private double lastCacheTime;
private string cachePath;
private string lastSessionPath { get { return cachePath + "/LastSession.asset"; } }
#endif
#region Setup
public NodeEditorUserCache(NodeCanvas loadedCanvas)
{
SetCanvas(loadedCanvas);
}
public NodeEditorUserCache()
{ }
public NodeEditorUserCache(string CachePath, NodeCanvas loadedCanvas)
{
#if CACHE
useCache = true;
cachePath = CachePath;
SetupCacheEvents();
#endif
SetCanvas(loadedCanvas);
}
public NodeEditorUserCache(string CachePath)
{
#if CACHE
useCache = true;
cachePath = CachePath;
SetupCacheEvents();
#endif
}
/// <summary>
/// Assures a canvas is loaded, either from the cache or new
/// </summary>
public void AssureCanvas()
{
#if CACHE
if (nodeCanvas == null)
LoadCache ();
#endif
if (nodeCanvas == null)
NewNodeCanvas();
if (editorState == null)
NewEditorState();
}
#endregion
#region Cache
/// <summary>
/// Subscribes the cache events needed for the cache to work properly
/// </summary>
public void SetupCacheEvents ()
{
#if UNITY_EDITOR && CACHE
if (!useCache)
return;
UnityEditor.EditorApplication.update -= CheckCacheUpdate;
UnityEditor.EditorApplication.update += CheckCacheUpdate;
lastCacheTime = UnityEditor.EditorApplication.timeSinceStartup;
EditorLoadingControl.beforeEnteringPlayMode -= SaveCache;
EditorLoadingControl.beforeEnteringPlayMode += SaveCache;
EditorLoadingControl.beforeLeavingPlayMode -= SaveCache;
EditorLoadingControl.beforeLeavingPlayMode += SaveCache;
EditorLoadingControl.justEnteredPlayMode -= LoadCache;
EditorLoadingControl.justEnteredPlayMode += LoadCache;
#endif
}
/// <summary>
/// Unsubscribes all cache events
/// </summary>
public void ClearCacheEvents ()
{
#if UNITY_EDITOR && CACHE
SaveCache();
UnityEditor.EditorApplication.update -= CheckCacheUpdate;
EditorLoadingControl.beforeEnteringPlayMode -= SaveCache;
EditorLoadingControl.beforeLeavingPlayMode -= SaveCache;
EditorLoadingControl.justEnteredPlayMode -= LoadCache;
#endif
}
private void CheckCacheUpdate ()
{
#if UNITY_EDITOR && CACHE
if (UnityEditor.EditorApplication.timeSinceStartup-lastCacheTime > cacheIntervalSec)
{
AssureCanvas();
if (editorState.dragUserID == "" && editorState.connectKnob == null && GUIUtility.hotControl <= 0 && !OverlayGUI.HasPopupControl ())
{ // Only save when the user currently does not perform an action that could be interrupted by the save
lastCacheTime = UnityEditor.EditorApplication.timeSinceStartup;
SaveCache ();
}
}
#endif
}
/// <summary>
/// Creates a new cache save file for the currently loaded canvas
/// Only called when a new canvas is created or loaded
/// </summary>
private void RecreateCache ()
{
#if CACHE
if (!useCache)
return;
DeleteCache ();
SaveCache ();
#endif
}
/// <summary>
/// Saves the current canvas to the cache
/// </summary>
public void SaveCache ()
{
#if CACHE
if (!useCache)
return;
if (!nodeCanvas || nodeCanvas.GetType () == typeof(NodeCanvas))
return;
UnityEditor.EditorUtility.SetDirty (nodeCanvas);
if (editorState != null)
UnityEditor.EditorUtility.SetDirty (editorState);
lastCacheTime = UnityEditor.EditorApplication.timeSinceStartup;
nodeCanvas.editorStates = new NodeEditorState[] { editorState };
if (nodeCanvas.livesInScene || nodeCanvas.allowSceneSaveOnly)
NodeEditorSaveManager.SaveSceneNodeCanvas ("lastSession", ref nodeCanvas, cacheWorkingCopy);
else
NodeEditorSaveManager.SaveNodeCanvas (lastSessionPath, ref nodeCanvas, cacheWorkingCopy, true);
#endif
}
/// <summary>
/// Loads the canvas from the cache save file
/// Called whenever a reload was made
/// </summary>
public void LoadCache ()
{
#if CACHE
if (!useCache)
{ // Simply create a ne canvas
NewNodeCanvas ();
return;
}
// Try to load the NodeCanvas
if ((!File.Exists (lastSessionPath) || (nodeCanvas = NodeEditorSaveManager.LoadNodeCanvas (lastSessionPath, cacheWorkingCopy)) == null) && // Check for asset cache
(nodeCanvas = NodeEditorSaveManager.LoadSceneNodeCanvas ("lastSession", cacheWorkingCopy)) == null) // Check for scene cache
{
NewNodeCanvas ();
return;
}
// Fetch the associated MainEditorState
editorState = NodeEditorSaveManager.ExtractEditorState (nodeCanvas, MainEditorStateIdentifier);
UpdateCanvasInfo ();
nodeCanvas.Validate ();
nodeCanvas.TraverseAll ();
NodeEditor.RepaintClients ();
#endif
}
#if CACHE
/// <summary>
/// Makes sure the current canvas is saved to the cache
/// </summary>
private void CheckCurrentCache ()
{
if (!useCache)
return;
if (nodeCanvas.livesInScene)
{
if (!NodeEditorSaveManager.HasSceneSave ("lastSession"))
SaveCache ();
}
else if (UnityEditor.AssetDatabase.LoadAssetAtPath<NodeCanvas> (lastSessionPath) == null)
SaveCache ();
}
/// <summary>
/// Deletes the cache
/// </summary>
private void DeleteCache ()
{
if (!useCache)
return;
UnityEditor.AssetDatabase.DeleteAsset (lastSessionPath);
UnityEditor.AssetDatabase.Refresh ();
NodeEditorSaveManager.DeleteSceneNodeCanvas ("lastSession");
}
/// <summary>
/// Sets the cache dirty and as makes sure it's saved
/// </summary>
private void UpdateCacheFile ()
{
if (!useCache)
return;
UnityEditor.EditorUtility.SetDirty (nodeCanvas);
UnityEditor.AssetDatabase.SaveAssets ();
UnityEditor.AssetDatabase.Refresh ();
}
#endif
#endregion
#region Save/Load
/// <summary>
/// Sets the current canvas, handling all cache operations
/// </summary>
public void SetCanvas (NodeCanvas canvas)
{
if (canvas == null)
NewNodeCanvas();
else if (nodeCanvas != canvas)
{
canvas.Validate ();
nodeCanvas = canvas;
editorState = NodeEditorSaveManager.ExtractEditorState (nodeCanvas, MainEditorStateIdentifier);
RecreateCache ();
UpdateCanvasInfo ();
nodeCanvas.TraverseAll ();
NodeEditor.RepaintClients ();
}
}
/// <summary>
/// Saves the mainNodeCanvas and it's associated mainEditorState as an asset at path
/// </summary>
public void SaveSceneNodeCanvas (string path)
{
nodeCanvas.editorStates = new NodeEditorState[] { editorState };
bool switchedToScene = !nodeCanvas.livesInScene;
NodeEditorSaveManager.SaveSceneNodeCanvas (path, ref nodeCanvas, true);
editorState = NodeEditorSaveManager.ExtractEditorState (nodeCanvas, MainEditorStateIdentifier);
if (switchedToScene)
RecreateCache ();
NodeEditor.RepaintClients ();
}
/// <summary>
/// Loads the mainNodeCanvas and it's associated mainEditorState from an asset at path
/// </summary>
public void LoadSceneNodeCanvas (string path)
{
if (path.StartsWith ("SCENE/"))
path = path.Substring (6);
// Try to load the NodeCanvas
if ((nodeCanvas = NodeEditorSaveManager.LoadSceneNodeCanvas (path, true)) == null)
{
NewNodeCanvas ();
return;
}
editorState = NodeEditorSaveManager.ExtractEditorState (nodeCanvas, MainEditorStateIdentifier);
openedCanvasPath = path;
nodeCanvas.Validate();
RecreateCache ();
UpdateCanvasInfo ();
nodeCanvas.TraverseAll ();
NodeEditor.RepaintClients ();
}
/// <summary>
/// Saves the mainNodeCanvas and it's associated mainEditorState as an asset at path
/// </summary>
public void SaveNodeCanvas (string path)
{
nodeCanvas.editorStates = new NodeEditorState[] { editorState };
bool switchedToFile = nodeCanvas.livesInScene;
NodeEditorSaveManager.SaveNodeCanvas (path, ref nodeCanvas, true);
if (switchedToFile)
RecreateCache ();
NodeEditor.RepaintClients ();
}
/// <summary>
/// Loads the mainNodeCanvas and it's associated mainEditorState from an asset at path
/// </summary>
public void LoadNodeCanvas (string path)
{
// Try to load the NodeCanvas
if (!File.Exists (path) || (nodeCanvas = NodeEditorSaveManager.LoadNodeCanvas (path, true)) == null)
{
NewNodeCanvas ();
return;
}
editorState = NodeEditorSaveManager.ExtractEditorState (nodeCanvas, MainEditorStateIdentifier);
openedCanvasPath = path;
nodeCanvas.Validate();
RecreateCache ();
UpdateCanvasInfo ();
nodeCanvas.TraverseAll ();
NodeEditor.RepaintClients ();
}
/// <summary>
/// Creates and loads a new NodeCanvas
/// </summary>
public void NewNodeCanvas (Type canvasType = null)
{
canvasType = canvasType ?? defaultNodeCanvasType;
nodeCanvas = NodeCanvas.CreateCanvas (canvasType);
NewEditorState ();
openedCanvasPath = "";
RecreateCache ();
UpdateCanvasInfo ();
}
/// <summary>
/// Creates a new EditorState for the current NodeCanvas
/// </summary>
public void NewEditorState ()
{
editorState = ScriptableObject.CreateInstance<NodeEditorState> ();
editorState.canvas = nodeCanvas;
editorState.name = MainEditorStateIdentifier;
nodeCanvas.editorStates = new NodeEditorState[] { editorState };
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty (nodeCanvas);
#endif
}
#endregion
#region Utility
public void ConvertCanvasType(Type newType)
{
NodeCanvas canvas = NodeCanvasManager.ConvertCanvasType(nodeCanvas, newType);
if (canvas != nodeCanvas)
{
nodeCanvas = canvas;
RecreateCache();
UpdateCanvasInfo();
nodeCanvas.TraverseAll();
NodeEditor.RepaintClients();
}
}
private void UpdateCanvasInfo()
{
typeData = NodeCanvasManager.GetCanvasTypeData(nodeCanvas);
}
#endregion
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,431 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 26a89c36c30b2e940804d7efcccaf0bd, type: 3}
m_Name: Calculation Canvas
m_EditorClassIdentifier:
editorStates:
- {fileID: 11482305}
saveName: Calculation Canvas
savePath: Assets/Node_Editor_Framework-develop/Node_Editor/Resources/Saves/Calculation
Canvas.asset
livesInScene: 0
nodes:
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
- {fileID: 0}
groups: []
--- !u!114 &11403174
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Output
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 2
maxConnectionCount: 1
styleID: Float
_connections:
- {fileID: 11439100}
- {fileID: 11419872}
side: 2
sidePosition: 28
sideOffset: 0
--- !u!114 &11403266
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3dd50b389073b148b5ec10e419dc073, type: 3}
m_Name: Input Node
m_EditorClassIdentifier:
position: {x: -50, y: 50}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
outputKnob: {fileID: 11411772}
value: 5
--- !u!114 &11406980
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Value
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 2
maxConnectionCount: 1
styleID: Float
_connections:
- {fileID: 11495465}
side: 2
sidePosition: 28
sideOffset: 0
--- !u!114 &11408467
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Output
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 2
maxConnectionCount: 1
styleID: Float
_connections:
- {fileID: 11419545}
side: 2
sidePosition: 28
sideOffset: 0
--- !u!114 &11409214
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Input 2
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 1
maxConnectionCount: 0
styleID: Float
_connections:
- {fileID: 11411772}
side: 4
sidePosition: 46
sideOffset: 0
--- !u!114 &11411421
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 39f5df92d12f07d45a4fcfb96e1b36d8, type: 3}
m_Name: Display Node
m_EditorClassIdentifier:
position: {x: 550, y: -50}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
inputKnob: {fileID: 11419545}
--- !u!114 &11411772
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Value
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 2
maxConnectionCount: 1
styleID: Float
_connections:
- {fileID: 11409214}
side: 2
sidePosition: 28
sideOffset: 0
--- !u!114 &11411914
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Value
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 2
maxConnectionCount: 1
styleID: Float
_connections:
- {fileID: 11470550}
side: 2
sidePosition: 28
sideOffset: 0
--- !u!114 &11417989
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 39f5df92d12f07d45a4fcfb96e1b36d8, type: 3}
m_Name: Display Node
m_EditorClassIdentifier:
position: {x: 250, y: -150}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
inputKnob: {fileID: 11439100}
--- !u!114 &11419545
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Value
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 1
maxConnectionCount: 0
styleID: Float
_connections:
- {fileID: 11408467}
side: 4
sidePosition: 28
sideOffset: 0
--- !u!114 &11419872
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Input 1
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 1
maxConnectionCount: 0
styleID: Float
_connections:
- {fileID: 11403174}
side: 4
sidePosition: 28
sideOffset: 0
--- !u!114 &11433881
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3dd50b389073b148b5ec10e419dc073, type: 3}
m_Name: Input Node
m_EditorClassIdentifier:
position: {x: -350, y: -50}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
outputKnob: {fileID: 11411914}
value: 3
--- !u!114 &11439100
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Value
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 1
maxConnectionCount: 0
styleID: Float
_connections:
- {fileID: 11403174}
side: 4
sidePosition: 28
sideOffset: 0
--- !u!114 &11462145
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 27a65ad2b155f0d40838ed81292e4df7, type: 3}
m_Name: Calc Node
m_EditorClassIdentifier:
position: {x: -50, y: -100}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
type: 3
input1Knob: {fileID: 11495465}
input2Knob: {fileID: 11470550}
outputKnob: {fileID: 11403174}
Input1Val: 73
Input2Val: 3
--- !u!114 &11462421
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 27a65ad2b155f0d40838ed81292e4df7, type: 3}
m_Name: Calc Node
m_EditorClassIdentifier:
position: {x: 250, y: -50}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
type: 2
input1Knob: {fileID: 11419872}
input2Knob: {fileID: 11409214}
outputKnob: {fileID: 11408467}
Input1Val: 24.333334
Input2Val: 5
--- !u!114 &11470550
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Input 2
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 1
maxConnectionCount: 0
styleID: Float
_connections:
- {fileID: 11411914}
side: 4
sidePosition: 46
sideOffset: 0
--- !u!114 &11482305
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f6ab6487237ff124ea4c2aa5de9ce3fb, type: 3}
m_Name: MainEditorState
m_EditorClassIdentifier:
canvas: {fileID: 0}
parentEditor: {fileID: 0}
selectedNode: {fileID: 0}
panOffset: {x: -146, y: 6}
zoom: 1
--- !u!114 &11495465
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 017e2c7fd038ecb46b317c1e003f31dc, type: 3}
m_Name: Input 1
m_EditorClassIdentifier:
body: {fileID: 0}
direction: 1
maxConnectionCount: 0
styleID: Float
_connections:
- {fileID: 11406980}
side: 4
sidePosition: 28
sideOffset: 0
--- !u!114 &11496963
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f3dd50b389073b148b5ec10e419dc073, type: 3}
m_Name: Input Node
m_EditorClassIdentifier:
position: {x: -350, y: -150}
dynamicConnectionPorts: []
backgroundColor:
r: 1
g: 1
b: 1
a: 1
outputKnob: {fileID: 11406980}
value: 73

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 60480b2d075467a42b5d9e035f6d2a8f
timeCreated: 1499013237
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c2591c50ff1bb714d8398a7b80ec7cb4
timeCreated: 1499013237
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,43 @@
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Hidden/LineShader"
{
SubShader
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
Fog { Mode Off }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
uniform float4 _LineColor;
uniform sampler2D _LineTexture;
struct v2f
{
float2 texcoord : TEXCOORD0;
float4 vertex : POSITION;
};
v2f vert (float2 texcoord : TEXCOORD0, float4 vertex : POSITION)
{
v2f o;
o.vertex = UnityObjectToClipPos(vertex);
o.texcoord = texcoord;
return o;
}
float4 frag (v2f i) : COLOR
{
return _LineColor * tex2D (_LineTexture, i.texcoord);
}
ENDCG
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0af19377f71840343ab4dffff74b8a00
timeCreated: 1482685508
licenseType: Store
ShaderImporter:
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,57 @@
fileFormatVersion: 2
guid: ef3fefb6407298c42bbcbc73ad562830
timeCreated: 1437572302
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 1
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 1
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -3
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
allowsAlphaSplitting: 0
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 3, z: 0, w: 3}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 2
buildTargetSettings: []
spriteSheet:
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
fileFormatVersion: 2
guid: d7e0ecbca30f9b347bb68f235cd9cae4
timeCreated: 1450128686
licenseType: Store
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 1
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 32
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
allowsAlphaSplitting: 0
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 2
buildTargetSettings: []
spriteSheet:
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More