550 lines
16 KiB
C#
550 lines
16 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using UnityEditor;
|
||
|
using UnityEditor.SceneManagement;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.SceneManagement;
|
||
|
using UnityObject = UnityEngine.Object;
|
||
|
|
||
|
namespace Unity.VisualScripting
|
||
|
{
|
||
|
internal class DropdownOptions
|
||
|
{
|
||
|
internal bool interactable = true;
|
||
|
internal string label;
|
||
|
internal string tooltip;
|
||
|
internal Action callback;
|
||
|
}
|
||
|
|
||
|
internal class SplitDropdown
|
||
|
{
|
||
|
private bool reset;
|
||
|
private List<DropdownOptions> listOfOptions;
|
||
|
|
||
|
internal SplitDropdown(List<DropdownOptions> listOfOptions)
|
||
|
{
|
||
|
this.listOfOptions = listOfOptions;
|
||
|
}
|
||
|
|
||
|
internal DropdownOptions GetOption(int index)
|
||
|
{
|
||
|
return listOfOptions[index];
|
||
|
}
|
||
|
|
||
|
internal void Reset()
|
||
|
{
|
||
|
reset = true;
|
||
|
}
|
||
|
|
||
|
internal bool Draw(Rect area, string label, ref bool toggleState)
|
||
|
{
|
||
|
GUI.Box(area, string.Empty, EmptyGraphWindow.buttonStyle);
|
||
|
|
||
|
if (GUI.Button(new Rect(area.x, area.y, area.width - area.height, area.height), label,
|
||
|
EmptyGraphWindow.labelStyleButton))
|
||
|
{
|
||
|
toggleState = false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Rect toggleRect = new Rect(area.x + area.width - area.height - 2, area.y - 1, area.height + 3,
|
||
|
area.height + 3);
|
||
|
|
||
|
toggleState = GUI.Toggle(toggleRect, toggleState, "", EmptyGraphWindow.labelStyleButton);
|
||
|
|
||
|
GUI.Label(toggleRect, EmptyGraphWindow.dropdownIcon, EmptyGraphWindow.titleStyle);
|
||
|
|
||
|
if (toggleState)
|
||
|
{
|
||
|
GUI.BeginGroup(new Rect(area.x, area.y + area.height - 1, area.width, area.height * 2),
|
||
|
EmptyGraphWindow.buttonStyle);
|
||
|
Rect areaChild = new Rect(0, 0, EmptyGraphWindow.buttonWidth,
|
||
|
EmptyGraphWindow.buttonHeight);
|
||
|
|
||
|
GUIContent contentOption;
|
||
|
|
||
|
foreach (DropdownOptions dropdownOption in listOfOptions)
|
||
|
{
|
||
|
EditorGUI.BeginDisabledGroup(!dropdownOption.interactable);
|
||
|
|
||
|
if (dropdownOption.interactable)
|
||
|
{
|
||
|
contentOption = new GUIContent(dropdownOption.label);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
contentOption = new GUIContent(dropdownOption.label, dropdownOption.tooltip);
|
||
|
}
|
||
|
|
||
|
if (GUI.Button(areaChild, contentOption, EmptyGraphWindow.labelStyleDropdownOptions))
|
||
|
{
|
||
|
toggleState = false;
|
||
|
|
||
|
dropdownOption.callback();
|
||
|
}
|
||
|
|
||
|
EditorGUI.EndDisabledGroup();
|
||
|
|
||
|
areaChild.y += areaChild.height;
|
||
|
}
|
||
|
|
||
|
GUI.EndGroup();
|
||
|
}
|
||
|
|
||
|
if (reset)
|
||
|
{
|
||
|
toggleState = false;
|
||
|
reset = false;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal class EmptyGraphWindow : EditorWindow
|
||
|
{
|
||
|
internal static GUIContent dropdownIcon;
|
||
|
|
||
|
private bool toggleOnScript = false;
|
||
|
private bool toggleOnState = false;
|
||
|
|
||
|
internal static GUIStyle buttonStyle;
|
||
|
internal static GUIStyle labelStyleButton;
|
||
|
internal static GUIStyle labelStyleDropdownOptions;
|
||
|
internal static GUIStyle titleStyle;
|
||
|
|
||
|
internal static int titleHeight = 120;
|
||
|
internal static int buttonWidth = 200;
|
||
|
internal static int buttonHeight = 22;
|
||
|
|
||
|
private bool shouldCloseWindow;
|
||
|
private Vector2 scrollPosition;
|
||
|
private Rect scrollArea;
|
||
|
|
||
|
private SplitDropdown splitDropdownScriptGraph;
|
||
|
private SplitDropdown splitDropdownStateGraph;
|
||
|
|
||
|
const string k_OnSelectedGameObject = "...on selected GameObject";
|
||
|
const string k_OnNewGameObject = "...on new GameObject";
|
||
|
const string k_SelectedGameObject = "Please, select a GameObject";
|
||
|
|
||
|
[MenuItem("Window/Visual Scripting/Visual Scripting Graph", false, 3010)]
|
||
|
private static void ShowWindow()
|
||
|
{
|
||
|
EmptyGraphWindow window = GetWindow<EmptyGraphWindow>();
|
||
|
|
||
|
window.titleContent = new GUIContent("Visual Scripting");
|
||
|
}
|
||
|
|
||
|
private void OnEnable()
|
||
|
{
|
||
|
string pathRoot = PathUtility.GetPackageRootPath();
|
||
|
|
||
|
UnityObject icon = EditorGUIUtility.Load(Path.Combine(pathRoot,
|
||
|
"Editor/VisualScripting.Shared/EditorAssetResources/SplitButtonArrow.png"));
|
||
|
|
||
|
dropdownIcon = new GUIContent(icon as Texture2D);
|
||
|
|
||
|
scrollArea = new Rect(0, 0, 630, 300);
|
||
|
|
||
|
toggleOnScript = false;
|
||
|
toggleOnState = false;
|
||
|
shouldCloseWindow = false;
|
||
|
|
||
|
var listOfOptions = new List<DropdownOptions>
|
||
|
{
|
||
|
new DropdownOptions
|
||
|
{
|
||
|
label = k_OnSelectedGameObject,
|
||
|
tooltip = k_SelectedGameObject,
|
||
|
callback = CreateScriptGraphOnSelectedGameObject
|
||
|
},
|
||
|
new DropdownOptions
|
||
|
{
|
||
|
label = k_OnNewGameObject,
|
||
|
callback = CreateScriptGraphOnNewGameObject
|
||
|
}
|
||
|
};
|
||
|
|
||
|
splitDropdownScriptGraph = new SplitDropdown(listOfOptions);
|
||
|
|
||
|
listOfOptions = new List<DropdownOptions>
|
||
|
{
|
||
|
new DropdownOptions
|
||
|
{
|
||
|
label = k_OnSelectedGameObject,
|
||
|
tooltip = k_SelectedGameObject,
|
||
|
callback = CreateStateGraphOnSelectedGameObject
|
||
|
},
|
||
|
new DropdownOptions
|
||
|
{
|
||
|
label = k_OnNewGameObject,
|
||
|
callback = CreateStateGraphOnNewGameObject
|
||
|
}
|
||
|
};
|
||
|
|
||
|
splitDropdownStateGraph = new SplitDropdown(listOfOptions);
|
||
|
}
|
||
|
|
||
|
private void OpenGraphAsset(UnityObject unityObject, bool shouldSetSceneAsDirty)
|
||
|
{
|
||
|
shouldCloseWindow = true;
|
||
|
|
||
|
ScriptGraphAsset scriptGraphAsset = unityObject as ScriptGraphAsset;
|
||
|
|
||
|
GraphReference graphReference = null;
|
||
|
|
||
|
if (scriptGraphAsset != null)
|
||
|
{
|
||
|
graphReference = GraphReference.New(scriptGraphAsset, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StateGraphAsset stateGraphAsset = unityObject as StateGraphAsset;
|
||
|
|
||
|
if (stateGraphAsset != null)
|
||
|
{
|
||
|
graphReference = GraphReference.New(stateGraphAsset, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (shouldSetSceneAsDirty)
|
||
|
{
|
||
|
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||
|
}
|
||
|
|
||
|
GraphWindow.OpenActive(graphReference);
|
||
|
}
|
||
|
|
||
|
private void OpenGraphFromPath(string path, bool shouldSetSceneAsDirty)
|
||
|
{
|
||
|
path = path.Replace(Application.dataPath, "Assets");
|
||
|
|
||
|
UnityObject unityObject = AssetDatabase.LoadAssetAtPath(path, typeof(UnityObject));
|
||
|
|
||
|
OpenGraphAsset(unityObject, shouldSetSceneAsDirty);
|
||
|
}
|
||
|
|
||
|
private void OpenGraph()
|
||
|
{
|
||
|
EditorGUIUtility.ShowObjectPicker<MacroScriptableObject>(null, false, String.Empty,
|
||
|
EditorGUIUtility.GetControlID(FocusType.Passive));
|
||
|
}
|
||
|
|
||
|
private bool CreateScriptGraphAsset(GameObject gameObject = null, bool updateName = false)
|
||
|
{
|
||
|
var path = EditorUtility.SaveFilePanelInProject("Save Graph", "New Script Graph", "asset", null);
|
||
|
|
||
|
if (string.IsNullOrEmpty(path))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
VSUsageUtility.isVisualScriptingUsed = true;
|
||
|
|
||
|
var macro = (IMacro)CreateInstance(typeof(ScriptGraphAsset));
|
||
|
var macroObject = (UnityObject)macro;
|
||
|
macro.graph = FlowGraph.WithStartUpdate();
|
||
|
|
||
|
if (gameObject != null)
|
||
|
{
|
||
|
ScriptMachine flowMachine = gameObject.AddComponent<ScriptMachine>();
|
||
|
|
||
|
flowMachine.nest.macro = (ScriptGraphAsset)macro;
|
||
|
}
|
||
|
|
||
|
string filename = Path.GetFileNameWithoutExtension(path);
|
||
|
|
||
|
if (updateName)
|
||
|
{
|
||
|
gameObject.name = filename;
|
||
|
}
|
||
|
|
||
|
macroObject.name = filename;
|
||
|
|
||
|
AssetDatabase.CreateAsset(macroObject, path);
|
||
|
|
||
|
bool shouldSetSceneAsDirty = gameObject != null;
|
||
|
|
||
|
OpenGraphFromPath(path, shouldSetSceneAsDirty);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void CreateScriptGraph()
|
||
|
{
|
||
|
Selection.activeGameObject = null;
|
||
|
|
||
|
if (CreateScriptGraphAsset())
|
||
|
{
|
||
|
shouldCloseWindow = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateScriptGraphOnNewGameObject()
|
||
|
{
|
||
|
Selection.activeGameObject = null;
|
||
|
|
||
|
GameObject newGameObject = new GameObject();
|
||
|
|
||
|
if (!CreateScriptGraphAsset(newGameObject, true))
|
||
|
{
|
||
|
DestroyImmediate(newGameObject);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateScriptGraphOnSelectedGameObject()
|
||
|
{
|
||
|
if (Selection.activeGameObject != null)
|
||
|
{
|
||
|
if (CreateScriptGraphAsset(Selection.activeGameObject))
|
||
|
{
|
||
|
shouldCloseWindow = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool CreateStateGraphAsset(GameObject gameObject = null, bool updateName = false)
|
||
|
{
|
||
|
var path = EditorUtility.SaveFilePanelInProject("Save Graph", "New State Graph", "asset", null);
|
||
|
|
||
|
if (string.IsNullOrEmpty(path))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
VSUsageUtility.isVisualScriptingUsed = true;
|
||
|
|
||
|
var macro = (IMacro)CreateInstance(typeof(StateGraphAsset));
|
||
|
var macroObject = (UnityObject)macro;
|
||
|
macro.graph = StateGraph.WithStart();
|
||
|
|
||
|
if (gameObject != null)
|
||
|
{
|
||
|
StateMachine stateMachine = gameObject.AddComponent<StateMachine>();
|
||
|
|
||
|
stateMachine.nest.macro = (StateGraphAsset)macro;
|
||
|
}
|
||
|
|
||
|
string filename = Path.GetFileNameWithoutExtension(path);
|
||
|
|
||
|
if (updateName)
|
||
|
{
|
||
|
gameObject.name = filename;
|
||
|
}
|
||
|
|
||
|
macroObject.name = filename;
|
||
|
|
||
|
AssetDatabase.CreateAsset(macroObject, path);
|
||
|
|
||
|
bool shouldSetSceneAsDirty = gameObject != null;
|
||
|
|
||
|
OpenGraphFromPath(path, shouldSetSceneAsDirty);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void CreateStateGraph()
|
||
|
{
|
||
|
Selection.activeGameObject = null;
|
||
|
|
||
|
if (CreateStateGraphAsset())
|
||
|
{
|
||
|
shouldCloseWindow = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateStateGraphOnNewGameObject()
|
||
|
{
|
||
|
Selection.activeGameObject = null;
|
||
|
|
||
|
GameObject newGameObject = new GameObject();
|
||
|
|
||
|
if (!CreateStateGraphAsset(newGameObject, true))
|
||
|
{
|
||
|
DestroyImmediate(newGameObject);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateStateGraphOnSelectedGameObject()
|
||
|
{
|
||
|
if (Selection.activeGameObject != null)
|
||
|
{
|
||
|
if (CreateStateGraphAsset(Selection.activeGameObject))
|
||
|
{
|
||
|
shouldCloseWindow = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CreateStyles()
|
||
|
{
|
||
|
if (buttonStyle == null)
|
||
|
{
|
||
|
buttonStyle = new GUIStyle("Button");
|
||
|
}
|
||
|
|
||
|
if (labelStyleButton == null)
|
||
|
{
|
||
|
labelStyleButton = new GUIStyle("Label") { alignment = TextAnchor.MiddleCenter };
|
||
|
}
|
||
|
|
||
|
if (labelStyleDropdownOptions == null)
|
||
|
{
|
||
|
labelStyleDropdownOptions = new GUIStyle("ToolbarButton")
|
||
|
{
|
||
|
alignment = TextAnchor.MiddleLeft,
|
||
|
padding = new RectOffset(20, 0, 0, 0)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (titleStyle == null)
|
||
|
{
|
||
|
titleStyle = new GUIStyle("Label") { alignment = TextAnchor.MiddleCenter, fontSize = 20 };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void ResetToggle()
|
||
|
{
|
||
|
if (Event.current.rawType == EventType.MouseUp)
|
||
|
{
|
||
|
if (toggleOnScript)
|
||
|
{
|
||
|
splitDropdownScriptGraph.Reset();
|
||
|
|
||
|
Repaint();
|
||
|
}
|
||
|
|
||
|
if (toggleOnState)
|
||
|
{
|
||
|
splitDropdownStateGraph.Reset();
|
||
|
|
||
|
Repaint();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OpenGraphFromPicker()
|
||
|
{
|
||
|
if (Event.current.commandName == "ObjectSelectorUpdated")
|
||
|
{
|
||
|
UnityObject selectedObject = EditorGUIUtility.GetObjectPickerObject();
|
||
|
|
||
|
OpenGraphAsset(selectedObject, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnGUI()
|
||
|
{
|
||
|
CreateStyles();
|
||
|
|
||
|
ResetToggle();
|
||
|
|
||
|
DropAreaGUI();
|
||
|
|
||
|
OpenGraphFromPicker();
|
||
|
|
||
|
scrollPosition = GUI.BeginScrollView(new Rect(0, 0, position.width, position.height), scrollPosition,
|
||
|
scrollArea);
|
||
|
GUILayout.BeginVertical(GUILayout.ExpandHeight(true));
|
||
|
|
||
|
Vector2 groupSize = new Vector2(scrollArea.width, 280);
|
||
|
|
||
|
GUI.BeginGroup(new Rect((position.width / 2) - (groupSize.x / 2), (position.height / 2) - (groupSize.y / 2),
|
||
|
groupSize.x, groupSize.y));
|
||
|
|
||
|
GUI.Label(new Rect(0, 0, groupSize.x, titleHeight), "Drag and drop a Visual Scripting Graph asset here\nor",
|
||
|
titleStyle);
|
||
|
|
||
|
int buttonX = 10;
|
||
|
|
||
|
if (GUI.Button(new Rect(buttonX, titleHeight, buttonWidth, buttonHeight), "Browse to open a Graph"))
|
||
|
{
|
||
|
OpenGraph();
|
||
|
}
|
||
|
|
||
|
buttonX += (buttonWidth + 10);
|
||
|
|
||
|
Rect area = new Rect(buttonX, titleHeight, buttonWidth, buttonHeight);
|
||
|
|
||
|
splitDropdownScriptGraph.GetOption(0).interactable = Selection.activeGameObject != null;
|
||
|
|
||
|
if (splitDropdownScriptGraph.Draw(area, "Create new Script Graph", ref toggleOnScript))
|
||
|
{
|
||
|
CreateScriptGraph();
|
||
|
}
|
||
|
|
||
|
if (toggleOnScript)
|
||
|
{
|
||
|
toggleOnState = false;
|
||
|
}
|
||
|
|
||
|
buttonX += (buttonWidth + 10);
|
||
|
|
||
|
area = new Rect(buttonX, titleHeight, buttonWidth, buttonHeight);
|
||
|
|
||
|
splitDropdownStateGraph.GetOption(0).interactable = Selection.activeGameObject != null;
|
||
|
|
||
|
if (splitDropdownStateGraph.Draw(area, "Create new State Graph", ref toggleOnState))
|
||
|
{
|
||
|
CreateStateGraph();
|
||
|
}
|
||
|
|
||
|
if (toggleOnState)
|
||
|
{
|
||
|
toggleOnScript = false;
|
||
|
}
|
||
|
|
||
|
GUI.EndGroup();
|
||
|
GUILayout.EndVertical();
|
||
|
GUI.EndScrollView();
|
||
|
|
||
|
if (shouldCloseWindow)
|
||
|
{
|
||
|
Close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void DropAreaGUI()
|
||
|
{
|
||
|
Event currentEvent = Event.current;
|
||
|
|
||
|
Rect activeArea = GUILayoutUtility.GetRect(0, 0, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||
|
|
||
|
switch (currentEvent.type)
|
||
|
{
|
||
|
case EventType.DragUpdated:
|
||
|
case EventType.DragPerform:
|
||
|
if (!activeArea.Contains(currentEvent.mousePosition))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||
|
|
||
|
if (currentEvent.type == EventType.DragPerform)
|
||
|
{
|
||
|
DragAndDrop.AcceptDrag();
|
||
|
|
||
|
foreach (UnityObject draggedObject in DragAndDrop.objectReferences)
|
||
|
{
|
||
|
ScriptGraphAsset scriptGraphAsset = draggedObject as ScriptGraphAsset;
|
||
|
|
||
|
if (scriptGraphAsset != null)
|
||
|
{
|
||
|
shouldCloseWindow = true;
|
||
|
|
||
|
GraphWindow.OpenActive(GraphReference.New(scriptGraphAsset, true));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|