Advertisement
celinedrules

DialogueGraph

Aug 27th, 2022
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.50 KB | None | 0 0
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEditor;
  4. using UnityEditor.Callbacks;
  5. using UnityEditor.Experimental.GraphView;
  6. using UnityEditor.UIElements;
  7. using UnityEngine;
  8. using UnityEngine.UIElements;
  9. using Object = UnityEngine.Object;
  10.  
  11. namespace ModernDialogues.Editor
  12. {
  13. /// <summary>
  14. /// Custom editor window to display the dialogue graph
  15. /// </summary>
  16. public class DialogueGraph : EditorWindow
  17. {
  18. //private static DialogueGraph instance;
  19.  
  20. //public static DialogueGraph Instance { get { return GetWindow<DialogueGraph>(); } }
  21.  
  22. private bool isFocused;
  23.  
  24. //private InspectorView inspectorView; // Shows details about the selected node
  25. private const string ConversationName = "New Narrative"; // The name of the dialogue conversation
  26. private ToolbarMenu dialogueMenu; // Dropdown of available dialogues
  27. private List<Dialogue> dialogues; // Holds a list of all available dialogues
  28.  
  29. //[SerializeField]
  30. private DialogueGraphView graphView;
  31. // Reference to the dialogue graph view
  32. public DialogueGraphView GraphView { get => graphView; set => graphView = value; }
  33.  
  34. // Check used to see if we want to load the dialogue after it's created
  35. public bool LoadAsset { get; set; }
  36.  
  37. // Auto save dialogue
  38. public bool AutoSaveDialogue { get; private set; }
  39.  
  40. // Reference to the filename of the current dialogue
  41. public string Filename { get; set; }
  42.  
  43. /// <summary>
  44. /// Open the dialogue graph
  45. /// </summary>
  46. [MenuItem("Graph/Dialogue Graph")]
  47. public static void OpenDialogueGraphWindow()
  48. {
  49. DialogueGraph window = GetWindow<DialogueGraph>();
  50. window.titleContent = new GUIContent("Dialogue Graph");
  51. }
  52.  
  53. /// <summary>
  54. /// Open the dialogue graph and load the selected dialogue
  55. /// </summary>
  56. /// <param name="instanceId">The ID of the dialogue object</param>
  57. /// <returns>If we handled the opening of the asset</returns>
  58. [OnOpenAsset(1)]
  59. public static bool OnOpenAsset(int instanceId)
  60. {
  61. // Chek if the object is a DialogueDatatable and assign it to the datatable variable
  62. if (EditorUtility.InstanceIDToObject(instanceId) is not Dialogue dialogue)
  63. return false;
  64.  
  65. // Get the filename of the datatable
  66. //string filename = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(datatable));
  67. string filename = AssetDatabase.GetAssetPath(dialogue);
  68.  
  69. // Open dialogue graph and load the datatable
  70. DialogueGraph window = GetWindow<DialogueGraph>();
  71. window.LoadDialogue(filename);
  72. window.Filename = filename;
  73. window.EnableGraph();
  74.  
  75. // Set the dropdown field to the opened datatable
  76. if (window.GraphView.Dialogue != null)
  77. window.rootVisualElement.Query<ToolbarMenu>().Where(x => x.name == "dialogueMenu").First()
  78. .text = window.GraphView.Dialogue.ConversationName;
  79.  
  80. return true;
  81. }
  82.  
  83. /// <summary>
  84. /// Setup the dialogue graph window
  85. /// </summary>
  86. private void OnEnable()
  87. {
  88. //instance = this;
  89. GenerateToolbar();
  90. ConstructGraphView();
  91. GenerateMiniMap();
  92. }
  93.  
  94. /// <summary>
  95. /// Creates a clickable minimap of the dialogue nodes
  96. /// </summary>
  97. private void GenerateMiniMap()
  98. {
  99. MiniMap miniMap = new() {anchored = true};
  100. miniMap.SetPosition(new Rect(position.width - 210, 30, 200, 140));
  101. GraphView.Add(miniMap);
  102. }
  103.  
  104. /// <summary>
  105. /// By removing the base element in the window, it removes all children too
  106. /// </summary>
  107. private void OnDisable() => rootVisualElement.RemoveAt(0);
  108.  
  109. /// <summary>
  110. /// Constructs the dialogue graph
  111. /// </summary>
  112. private void ConstructGraphView()
  113. {
  114. GraphView = new DialogueGraphView(this)
  115. {
  116. name = "Dialogue Graph"
  117. };
  118.  
  119. TwoPaneSplitView sv = new()
  120. {
  121. name = "splitView",
  122. fixedPaneInitialDimension = 400,
  123. orientation = TwoPaneSplitViewOrientation.Horizontal
  124. };
  125.  
  126. VisualElement leftPanel = new();
  127. VisualElement rightPanel = new();
  128. rightPanel.Add(GraphView);
  129. leftPanel.style.maxWidth = 400;
  130. leftPanel.style.minWidth = 400;
  131.  
  132. // Displays the name of the conversation on the graph
  133. Label lblConversationName = new(ConversationName)
  134. {
  135. name = "conversationName",
  136. style =
  137. {
  138. fontSize = 36,
  139. unityFontStyleAndWeight = new StyleEnum<FontStyle>(FontStyle.Bold),
  140. marginLeft = 5,
  141. marginTop = 5,
  142. color = new StyleColor(new Color(1, 1, 1, .25f))
  143. }
  144. };
  145. rightPanel.Add(lblConversationName);
  146.  
  147. sv.Add(leftPanel);
  148. sv.Add(rightPanel);
  149.  
  150. GraphView.StretchToParentSize();
  151. rootVisualElement.Add(sv);
  152. sv.visible = false;
  153. }
  154.  
  155. /// <summary>
  156. /// Generates the top toolbar for the graph allowing for basic operations such as switching, loading, and
  157. /// saving of dialogues
  158. /// </summary>
  159. private void GenerateToolbar()
  160. {
  161. Toolbar toolbar = new()
  162. {
  163. style =
  164. {
  165. alignItems = new StyleEnum<Align>(Align.Center)
  166. }
  167. };
  168.  
  169. dialogueMenu = new ToolbarMenu
  170. {
  171. name = "dialogueMenu",
  172. text = "Select Dialogue",
  173. style =
  174. {
  175. width = 200,
  176. paddingLeft = 10,
  177. paddingRight = 10
  178. }
  179. };
  180. toolbar.Add(dialogueMenu);
  181. GetDialogues();
  182.  
  183. // Creates a new datatable
  184. ToolbarButton newDialogue = new(CreateDialogue)
  185. {
  186. name = "newDialogue",
  187. text = "New Dialogue",
  188. style =
  189. {
  190. paddingLeft = 10,
  191. paddingRight = 10
  192. }
  193. };
  194. toolbar.Add(newDialogue);
  195.  
  196. ToolbarButton duplicateDialogue = new(DuplicateDialogue)
  197. {
  198. name = "duplicateDialogue",
  199. text = "Duplicate Dialogue",
  200. style =
  201. {
  202. paddingLeft = 10,
  203. paddingRight = 10
  204. }
  205. };
  206. duplicateDialogue.SetEnabled(false);
  207. toolbar.Add(duplicateDialogue);
  208.  
  209. // Deletes the current datatable
  210. ToolbarButton removeDialogue = new(RemoveDialogue)
  211. {
  212. name = "removeDialogue",
  213. text = "Remove Dialogue",
  214. style =
  215. {
  216. paddingLeft = 10,
  217. paddingRight = 10
  218. }
  219. };
  220. removeDialogue.SetEnabled(false);
  221. toolbar.Add(removeDialogue);
  222.  
  223. VisualElement saveContainer = new()
  224. {
  225. style =
  226. {
  227. flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row),
  228. position = new StyleEnum<Position>(Position.Absolute),
  229. right = 0,
  230. top = 0,
  231. bottom = 0,
  232. alignItems = new StyleEnum<Align>(Align.Center)
  233. }
  234. };
  235.  
  236. // Saves the current datatable
  237. ToolbarButton saveDialogue = new(() => RequestDataOperation(true))
  238. {
  239. name = "saveData",
  240. text = "Save Dialogue",
  241. style =
  242. {
  243. paddingLeft = 10,
  244. paddingRight = 10
  245. }
  246. };
  247. saveDialogue.SetEnabled(false);
  248. saveContainer.Add(saveDialogue);
  249.  
  250. ToolbarToggle autoSave = new()
  251. {
  252. name = "autoSave",
  253. text = " Auto Save",
  254. style =
  255. {
  256. paddingLeft = 10,
  257. paddingRight = 10
  258. }
  259. };
  260. autoSave.RegisterValueChangedCallback(evt => { AutoSaveDialogue = evt.newValue; });
  261. saveContainer.Add(autoSave);
  262.  
  263. toolbar.Add(saveContainer);
  264.  
  265. rootVisualElement.Add(toolbar);
  266. }
  267.  
  268. /// <summary>
  269. /// Get all objects in the project of the specified type
  270. /// </summary>
  271. /// <typeparam name="T">The type of object we want to load</typeparam>
  272. /// <returns>List of objects in the projects of the specified type</returns>
  273. private IEnumerable<T> GetAllDialogues<T>() where T : Object
  274. {
  275. string[] guids = AssetDatabase.FindAssets($"t:{typeof(T)}");
  276.  
  277. foreach (string t in guids)
  278. {
  279. string assetPath = AssetDatabase.GUIDToAssetPath(t);
  280. T asset = AssetDatabase.LoadAssetAtPath<T>(assetPath);
  281.  
  282. if (asset != null)
  283. yield return asset;
  284. }
  285. }
  286.  
  287. /// <summary>
  288. /// Save and load the current graph
  289. /// </summary>
  290. /// <param name="save">Do we want to save</param>
  291. private void RequestDataOperation(bool save)
  292. {
  293. if (string.IsNullOrEmpty(Filename))
  294. {
  295. EditorUtility.DisplayDialog("Invalid file name!", "Please enter a valid file name.", "OK");
  296. return;
  297. }
  298.  
  299. if (save)
  300. {
  301. SaveChanges();
  302. GraphView.DialogueNeedsSaving = false;
  303. }
  304. else
  305. {
  306. LoadDialogue(Filename);
  307. EnableGraph();
  308. }
  309. }
  310.  
  311. /// <summary>
  312. /// Creates a new dialogue datatable
  313. /// </summary>
  314. private void CreateDialogue()
  315. {
  316. Dialogue dialogue = CreateInstance<Dialogue>();
  317.  
  318. Utilities.TryGetActiveFolderPath(out string path);
  319. string assetPath = AssetDatabase.GenerateUniqueAssetPath(path + "/" + ConversationName + ".asset");
  320. AssetDatabase.CreateAsset(dialogue, assetPath);
  321. AssetDatabase.SaveAssets();
  322. RefreshDialogueList();
  323.  
  324. dialogue.ConversationName = dialogue.name;
  325. Filename = assetPath;
  326. LoadDialogue(Filename);
  327. EnableGraph();
  328. dialogueMenu.text = dialogue.ConversationName;
  329. }
  330.  
  331. /// <summary>
  332. /// Duplicates the current dialogue
  333. /// </summary>
  334. private void DuplicateDialogue()
  335. {
  336. foreach (string asset in AssetDatabase.FindAssets("t:Dialogue"))
  337. {
  338. string path = AssetDatabase.GUIDToAssetPath(asset);
  339.  
  340. if (!path.Contains(GraphView.Dialogue.ConversationName + ".asset"))
  341. continue;
  342.  
  343. Utilities.TryGetActiveFolderPath(out string outPath);
  344. string newPath =
  345. AssetDatabase.GenerateUniqueAssetPath(outPath + "/" + GraphView.Dialogue.ConversationName +
  346. ".asset");
  347. AssetDatabase.CopyAsset(path, newPath);
  348.  
  349. AssetDatabase.SaveAssets();
  350. AssetDatabase.Refresh();
  351. RefreshDialogueList();
  352.  
  353. Filename = newPath;
  354. LoadAsset = true;
  355. }
  356. }
  357.  
  358. /// <summary>
  359. /// Deletes the current dialogue datatable
  360. /// </summary>
  361. private void RemoveDialogue()
  362. {
  363. foreach (var asset in AssetDatabase.FindAssets("t:Dialogue"))
  364. {
  365. var path = AssetDatabase.GUIDToAssetPath(asset);
  366.  
  367. if (path.Contains(GraphView.Dialogue.ConversationName + ".asset"))
  368. {
  369. AssetDatabase.DeleteAsset(path);
  370. }
  371. }
  372. }
  373.  
  374. /// <summary>
  375. /// Enable the graph basic operations of the graphview window
  376. /// </summary>
  377. private void EnableGraph()
  378. {
  379. rootVisualElement.Q<Button>("saveData").SetEnabled(true);
  380. rootVisualElement.Q<Button>("duplicateDialogue").SetEnabled(true);
  381. rootVisualElement.Q<Button>("removeDialogue").SetEnabled(true);
  382. rootVisualElement.Q<TwoPaneSplitView>("splitView").visible = true;
  383. }
  384.  
  385. /// <summary>
  386. /// Disable the graph basic operations of the graphview window
  387. /// </summary>
  388. private void DisableGraph()
  389. {
  390. rootVisualElement.Q<Button>("saveData").SetEnabled(false);
  391. rootVisualElement.Q<Button>("duplicateDialogue").SetEnabled(false);
  392. rootVisualElement.Q<Button>("removeDialogue").SetEnabled(false);
  393. rootVisualElement.Q<TwoPaneSplitView>("splitView").visible = false;
  394. }
  395.  
  396. private void OnFocus() => isFocused = true;
  397.  
  398.  
  399. /// <summary>
  400. /// When we change the name of the conversation in the default inspector, we want to reflect the
  401. /// changes in the graph
  402. /// </summary>
  403. private void OnInspectorUpdate()
  404. {
  405. if (!isFocused)
  406. return;
  407.  
  408. Selection.activeObject = GraphView.Dialogue;
  409.  
  410. if (GraphView.Dialogue != null)
  411. ((Label) rootVisualElement.Query<Label>().Where(x => x.name == "conversationName")).text =
  412. GraphView.Dialogue.ConversationName;
  413. }
  414.  
  415. /// <summary>
  416. /// Adjust the correct position of the minimap whenever the window is resized
  417. /// </summary>
  418. private void OnGUI()
  419. {
  420. if (Event.current.rawType != EventType.Layout)
  421. return;
  422.  
  423. MiniMap miniMap = GraphView.contentContainer.Q<MiniMap>();
  424. miniMap.SetPosition(new Rect(GraphView.resolvedStyle.width - 210, 30, 200, 140));
  425. }
  426.  
  427. /// <summary>
  428. /// We created, deleted, or renamed a dialogue so we need to refresh the dropdown dialogue list
  429. /// </summary>
  430. public void RefreshDialogueList()
  431. {
  432. GetDialogues();
  433.  
  434. if (GraphView.Dialogue != null)
  435. return;
  436.  
  437. DisableGraph();
  438. dialogueMenu.text = "Select Dialogue";
  439. }
  440.  
  441. /// <summary>
  442. /// Gets all the dialogue datatables in the entire project
  443. /// </summary>
  444. private void GetDialogues()
  445. {
  446. dialogueMenu.menu.MenuItems().Clear();
  447. dialogues = GetAllDialogues<Dialogue>().ToList();
  448.  
  449. foreach (Dialogue dialogue in dialogues)
  450. {
  451. dialogueMenu.menu.AppendAction(dialogue.ConversationName, _ =>
  452. {
  453. if (AutoSaveDialogue)
  454. {
  455. SaveChanges();
  456. }
  457. else
  458. {
  459. if (GraphView.DialogueNeedsSaving)
  460. SaveChangesPopup.Show(this);
  461. }
  462.  
  463. dialogueMenu.text = dialogue.ConversationName;
  464. Dialogue newDialogue =
  465. dialogues.FirstOrDefault(dt => dt.ConversationName == dialogue.ConversationName);
  466.  
  467. if (newDialogue == null)
  468. return;
  469.  
  470. //filename = newDatatable.name;
  471. Filename = AssetDatabase.GetAssetPath(newDialogue);
  472. LoadDialogue(Filename);
  473. EnableGraph();
  474. });
  475. }
  476. }
  477.  
  478. /// <summary>
  479. /// If auto save is on, save when the graph window loses focus
  480. /// </summary>
  481. private void OnLostFocus()
  482. {
  483. if (AutoSaveDialogue)
  484. RequestDataOperation(true);
  485.  
  486. isFocused = false;
  487. }
  488.  
  489. /// <summary>
  490. /// If auto save is on, save when the graph window closes. If auto save is off,
  491. /// prompt to save changes.
  492. /// </summary>
  493. private void OnDestroy()
  494. {
  495. if (AutoSaveDialogue)
  496. RequestDataOperation(true);
  497. else if (GraphView.DialogueNeedsSaving)
  498. SaveChangesPopup.Show(this);
  499. }
  500.  
  501. /// <summary>
  502. /// Save changes to the dialogue
  503. /// </summary>
  504. public override void SaveChanges() => GraphSaveUtility.GetInstance(GraphView).SaveGraph(Filename);
  505.  
  506. /// <summary>
  507. /// Load a dialogue
  508. /// </summary>
  509. /// <param name="graphToLoad">The name of the dialogue to load</param>
  510. public void LoadDialogue(string graphToLoad) =>
  511. GraphSaveUtility.GetInstance(GraphView).LoadDialogue(graphToLoad);
  512. }
  513. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement