Kleiner Anfang für Dialog system
This commit is contained in:
9
Assets/RPGTALK/Scripts/Editor.meta
generated
Normal file
9
Assets/RPGTALK/Scripts/Editor.meta
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da8ee1bb313f247c89ff89d20159eaea
|
||||
folderAsset: yes
|
||||
timeCreated: 1476472685
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/RPGTALK/Scripts/Editor/Node Editor.meta
generated
Normal file
8
Assets/RPGTALK/Scripts/Editor/Node Editor.meta
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e5d11f91b8c8414b946af0c721ca498
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop.meta
generated
Normal file
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop.meta
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1489a6ef1472d474fb683e194b4d4c15
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Editor.meta
generated
Normal file
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Editor.meta
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d653b7bfccdc42bea469909a90ddd52
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Editor/Node_Editor.meta
generated
Normal file
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Editor/Node_Editor.meta
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 950a4a0d67693445ba94259715a8a51b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec97af71a9e87434d8c9b0011d371c42
|
||||
timeCreated: 1482685504
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a250de1c3c15f40d185f5d71f31e90ec
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba199a36d34de0b45b5bc689cf5d53c9
|
||||
timeCreated: 1482685504
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f4b4fa804bb6a641b473d0ca1e833b7
|
||||
timeCreated: 1482685504
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a27c7af4ec1f131429372fc89cb3cabc
|
||||
timeCreated: 1482685504
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c9cdcf36237a684bad680baa79ede80
|
||||
timeCreated: 1482685504
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor.meta
generated
Normal file
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor.meta
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dda1d53ab14d7447da34a555b825683b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor/Default.meta
generated
Normal file
8
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor/Default.meta
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edfe4486466684dc9a2ac4d1486eda21
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bac99074a8ab4156beee749b4ac628e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26a89c36c30b2e940804d7efcccaf0bd
|
||||
timeCreated: 1481402892
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82570dd096e3133488c3ad08f4bd59e9
|
||||
timeCreated: 1481043527
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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); } }
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6d37d55559ce304fb864e95081c872e
|
||||
timeCreated: 1484482479
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 564d0ee5bdae641debbcbf419c5cdcf5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b4fed10ebd11f149aca4caaba5a4c9b
|
||||
timeCreated: 1497983217
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f260887e1c2342aab6c92a1afdcdcf3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11b12ccbf5533274f9abf5008f057a67
|
||||
timeCreated: 1472742778
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 058701fb26fe9874f8dffda7e66a0fe9
|
||||
timeCreated: 1450128681
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f914d6007a514dd29e283e7a78b9a2b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 566520f8843ed45ce873494911e4b2ec
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3d338988a4691f43b8c0764bd85cf70
|
||||
timeCreated: 1498150662
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35456fbe6f3a0f3459ecfa7e219c273c
|
||||
timeCreated: 1498244777
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2b66f60866020c4ba95f07b98106379
|
||||
timeCreated: 1498244971
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6b5fe394e83d344c990206132b84a28
|
||||
timeCreated: 1449162341
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e0c2324a9ab1224ebe3edad393e3544
|
||||
timeCreated: 1437395312
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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) {}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fff8d766888b10d4cbd14d6d865a1e72
|
||||
timeCreated: 1481043527
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6ab6487237ff124ea4c2aa5de9ce3fb
|
||||
timeCreated: 1437395312
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a25adcb9d2c0e704a9b47bed372e4362
|
||||
timeCreated: 1482685563
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 017e2c7fd038ecb46b317c1e003f31dc
|
||||
timeCreated: 1498244881
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cc7c93830e9047678b7f63e6d180a88
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4be5c7fe89350d249b9fd20d52cfb700
|
||||
timeCreated: 1449162341
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3ac1ecc7eb0a6a4bae51b6e18010b92
|
||||
timeCreated: 1469444159
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cdfbc99cfb2a2c4db2bb4cbbec242fc
|
||||
timeCreated: 1449162341
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 896a4a4cc1f024d9590edb6f5a1a38b8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f72444d2da0ade14aba9ff3102d17243
|
||||
timeCreated: 1449162341
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d45bec52de54d834e818ca0ccb994c1f
|
||||
timeCreated: 1449162341
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76a77818258cdde43a807b810eb57074
|
||||
timeCreated: 1466001829
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b51ae3192829104296c1fc1bf4f4068
|
||||
timeCreated: 1466001829
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc2d5bd6fc3c2a042b33642dc057790d
|
||||
timeCreated: 1498909738
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b01efe70ff3f8d84da16ee5d254540a9
|
||||
timeCreated: 1449162341
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b634ce96f54a45eda27b520c00cfa79
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 961d85cbdc1829442907cdcfd40aaf01
|
||||
timeCreated: 1497884522
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7761e7c3d61739f4bbb8b023e3e91adc
|
||||
timeCreated: 1497983354
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84af943011bc7e6448cd010e393c6c02
|
||||
timeCreated: 1498754587
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,10 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NodeEditorFramework
|
||||
{
|
||||
public partial class NodeCanvasSceneSave : MonoBehaviour
|
||||
{
|
||||
public string saveName;
|
||||
public NodeCanvas savedNodeCanvas;
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f1cd8579a6c9ec4b85d9d62cd5ba58b
|
||||
timeCreated: 1464014559
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 536538853a8430243a2f38355c7d4bbd
|
||||
timeCreated: 1453840956
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bb1bb38f09a0534daf0b17a3e45693c
|
||||
timeCreated: 1466090292
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61e5800dea181423a91de81da5b255cb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd32b157c6f67492e93454cc845db5be
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60480b2d075467a42b5d9e035f6d2a8f
|
||||
timeCreated: 1499013237
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2591c50ff1bb714d8398a7b80ec7cb4
|
||||
timeCreated: 1499013237
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f86324b3558a04a0ca6f631003cf1e4e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0af19377f71840343ab4dffff74b8a00
|
||||
timeCreated: 1482685508
|
||||
licenseType: Store
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 414ffd4a9064249ab83ceea22b6b50db
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor/Resources/Textures/AALine.png
(Stored with Git LFS)
Normal file
BIN
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor/Resources/Textures/AALine.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -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:
|
BIN
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor/Resources/Textures/Icon_Dark.png
(Stored with Git LFS)
Normal file
BIN
Assets/RPGTALK/Scripts/Editor/Node Editor/Node_Editor_Framework-develop/Node_Editor/Resources/Textures/Icon_Dark.png
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user