Kleiner Anfang für Dialog system
This commit is contained in:
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.
@@ -0,0 +1,57 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7e0ecbca30f9b347bb68f235cd9cae4
|
||||
timeCreated: 1450128686
|
||||
licenseType: Store
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -2
|
||||
maxTextureSize: 32
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
allowsAlphaSplitting: 0
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user