DungeonFlowInspector.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. using DunGen.Editor.Validation;
  2. using DunGen.Graph;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEditorInternal;
  7. using UnityEngine;
  8. namespace DunGen.Editor
  9. {
  10. [CustomEditor(typeof(DungeonFlow))]
  11. public sealed class DungeonFlowInspector : UnityEditor.Editor
  12. {
  13. #region Helpers
  14. private sealed class Properties
  15. {
  16. public SerializedProperty Length;
  17. public SerializedProperty BranchMode;
  18. public SerializedProperty BranchCount;
  19. public SerializedProperty KeyManager;
  20. public SerializedProperty DoorwayConnectionChance;
  21. public SerializedProperty TileInjectionRules;
  22. public SerializedProperty RestrictConnectionToSameSection;
  23. public SerializedProperty TileTagConnectionMode;
  24. public SerializedProperty TileConnectionTags;
  25. public SerializedProperty BranchTagPruneMode;
  26. public SerializedProperty BranchPruneTags;
  27. public SerializedProperty StraighteningSettings;
  28. public ReorderableList GlobalProps;
  29. public ReorderableList TileConnectionTagsList;
  30. public ReorderableList BranchPruneTagsList;
  31. public ReorderableList TileInjectionRulesList;
  32. }
  33. private static class Labels
  34. {
  35. public static readonly GUIContent Validate = new GUIContent("Validate Dungeon", "Runs a set of automated checks on the integrity of the dungeon, reporting any errors that are found");
  36. public static readonly GUIContent Length = new GUIContent("Length", "Min and max length of the main path. This will determine how long the dungeon is");
  37. public static readonly GUIContent BranchMode = new GUIContent("Branch Mode", "Determines how the number of branches is computed");
  38. public static readonly GUIContent BranchCount = new GUIContent("Branch Count", "The total number of branches to appear accross the entire dungeon. Only used when Branch Mode is set to Global");
  39. public static readonly GUIContent GlobalProps = new GUIContent("Global Props");
  40. public static readonly GUIContent KeyManager = new GUIContent("Key Manager", "Defines which keys are available to be placed throughout the dungeon. This can be left blank if you don't want to make use of the lock & key system");
  41. public static readonly GUIContent DoorwayConnectionHeader = new GUIContent("Doorway Connection");
  42. public static readonly GUIContent DoorwayConnectionChance = new GUIContent("Connection Chance", "The percentage chance that an unconnected but overlapping set of doorways will be connected. This can be overriden on a per-tile basis");
  43. public static readonly GUIContent RestrictConnectionToSameSection = new GUIContent("Restrict to Same Section", "If checked, doorways will only be connected if they lie on the same line segment in the dungeon flow graph");
  44. public static readonly GUIContent TileInjection = new GUIContent("Special Tile Injection", "Used to inject specific tiles into the dungeon layout based on a set of rules");
  45. public static readonly GUIContent OpenFlowEditor = new GUIContent("Open Flow Editor", "The node graph lets you design how the dungeon should be laid out");
  46. public static readonly GUIContent GlobalPropGroupID = new GUIContent("Group ID", "The prop ID. This should match the ID on the GlobalProp component placed inside Tiles");
  47. public static readonly GUIContent GlobalPropGroupCount = new GUIContent("Count", "The number of times this prop should appear across the entire dungeon");
  48. public static readonly GUIContent TileConnectionTagMode = new GUIContent("Mode", "How to apply the tag rules below. NOTE: This section is ignored if the tag pair list is empty.\n Accept: Tiles are only connected if their tags match one of the pairs in the list below.\n Reject: Tiles will always connect unless their tags match one of the pairs in the list below.");
  49. public static readonly GUIContent TileConnectionTags = new GUIContent("Tag Pairs");
  50. public static readonly GUIContent TileConnectionRules = new GUIContent("Tile Connection Rules", "Allows us to accept or reject a connection between two tiles based on the tags each of them have.");
  51. public static readonly GUIContent BranchPruneMode = new GUIContent("Branch Prune Mode", "The method by which tiles at the end of a branch are pruned based on the tags below.");
  52. public static readonly GUIContent BranchPruneTags = new GUIContent("Branch Prune Tags", "Tiles on the end of branches will be deleted depending on which tags they have. Based on the branch prune mode");
  53. public static readonly GUIContent PathStraighteningHeader = new GUIContent("Path Straightening", "Determines if and how the path should be straightened. These settings can be overridden in the Archetype asset and on Nodes in the flow graph");
  54. public static readonly GUIContent BranchingHeader = new GUIContent("Branching");
  55. public static readonly string LocalBranchMode = "In Local mode, the number of branches is calculated per-tile using the Archetype's 'Branch Count' property";
  56. public static readonly string GlobalBranchMode = "In Global mode, the number of branches is calculated across the entire dungeon. NOTE: The number of branches might be less than the specified minimum value, but will never be more than the maximum";
  57. public static readonly string SectionBranchMode = "In Section mode, the number of branches is calculated for each section using the 'Branch Count' property in that section's Archetype settings";
  58. }
  59. #endregion
  60. private Properties properties;
  61. private void OnEnable()
  62. {
  63. properties = new Properties()
  64. {
  65. Length = serializedObject.FindProperty(nameof(DungeonFlow.Length)),
  66. BranchMode = serializedObject.FindProperty(nameof(DungeonFlow.BranchMode)),
  67. BranchCount = serializedObject.FindProperty(nameof(DungeonFlow.BranchCount)),
  68. KeyManager = serializedObject.FindProperty(nameof(DungeonFlow.KeyManager)),
  69. DoorwayConnectionChance = serializedObject.FindProperty(nameof(DungeonFlow.DoorwayConnectionChance)),
  70. RestrictConnectionToSameSection = serializedObject.FindProperty(nameof(DungeonFlow.RestrictConnectionToSameSection)),
  71. TileInjectionRules = serializedObject.FindProperty(nameof(DungeonFlow.TileInjectionRules)),
  72. TileTagConnectionMode = serializedObject.FindProperty(nameof(DungeonFlow.TileTagConnectionMode)),
  73. TileConnectionTags = serializedObject.FindProperty(nameof(DungeonFlow.TileConnectionTags)),
  74. BranchTagPruneMode = serializedObject.FindProperty(nameof(DungeonFlow.BranchTagPruneMode)),
  75. BranchPruneTags = serializedObject.FindProperty(nameof(DungeonFlow.BranchPruneTags)),
  76. StraighteningSettings = serializedObject.FindProperty(nameof(DungeonFlow.GlobalStraighteningSettings)),
  77. GlobalProps = new ReorderableList(serializedObject, serializedObject.FindProperty("GlobalProps"), true, false, true, true)
  78. {
  79. drawElementCallback = (rect, index, isActive, isFocused) => DrawGlobalProp(rect, index),
  80. elementHeightCallback = GetGlobalPropHeight,
  81. },
  82. };
  83. properties.TileConnectionTagsList = new ReorderableList(serializedObject, properties.TileConnectionTags)
  84. {
  85. drawHeaderCallback = DrawTileConnectionTagsHeader,
  86. drawElementCallback = DrawTileConnectionTagsElement,
  87. };
  88. properties.BranchPruneTagsList = new ReorderableList(serializedObject, properties.BranchPruneTags)
  89. {
  90. drawHeaderCallback = (Rect rect) =>
  91. {
  92. EditorGUI.LabelField(rect, Labels.BranchPruneTags);
  93. },
  94. drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
  95. {
  96. EditorGUI.PropertyField(rect, properties.BranchPruneTags.GetArrayElementAtIndex(index), GUIContent.none);
  97. },
  98. };
  99. properties.TileInjectionRulesList = new ReorderableList(serializedObject, properties.TileInjectionRules, true, true, true, true)
  100. {
  101. drawHeaderCallback = rect =>
  102. {
  103. EditorGUI.LabelField(rect, Labels.TileInjection);
  104. },
  105. drawElementCallback = DrawTileInjectionRule,
  106. elementHeightCallback = index =>
  107. {
  108. var element = properties.TileInjectionRules.GetArrayElementAtIndex(index);
  109. // If collapsed, just one line
  110. if (!element.isExpanded)
  111. return EditorGUIUtility.singleLineHeight + 6;
  112. // If expanded, enough lines for all fields
  113. int lines = 8;
  114. return (lines + 1) * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) + 4;
  115. }
  116. };
  117. var flow = target as DungeonFlow;
  118. if (flow != null)
  119. {
  120. foreach (var line in flow.Lines)
  121. line.Graph = flow;
  122. foreach (var node in flow.Nodes)
  123. node.Graph = flow;
  124. }
  125. }
  126. private void DrawTileConnectionTagsHeader(Rect rect)
  127. {
  128. EditorGUI.LabelField(rect, Labels.TileConnectionTags);
  129. }
  130. private void DrawTileConnectionTagsElement(Rect rect, int index, bool isActive, bool isFocused)
  131. {
  132. EditorGUI.PropertyField(rect, properties.TileConnectionTags.GetArrayElementAtIndex(index), GUIContent.none);
  133. }
  134. private void DrawTileInjectionRule(Rect rect, int index, bool isActive, bool isFocused)
  135. {
  136. var element = properties.TileInjectionRules.GetArrayElementAtIndex(index);
  137. rect.y += 2;
  138. float lineHeight = EditorGUIUtility.singleLineHeight;
  139. float spacing = EditorGUIUtility.standardVerticalSpacing;
  140. var tileSetProp = element.FindPropertyRelative(nameof(TileInjectionRule.TileSet));
  141. string tileSetName = "None";
  142. if (tileSetProp != null && tileSetProp.objectReferenceValue != null)
  143. tileSetName = tileSetProp.objectReferenceValue.name;
  144. // Foldout
  145. element.isExpanded = EditorGUI.Foldout(
  146. new Rect(rect.x + 10, rect.y, rect.width, lineHeight),
  147. element.isExpanded,
  148. new GUIContent(tileSetName),
  149. true);
  150. if (!element.isExpanded)
  151. return;
  152. float y = rect.y + lineHeight + spacing;
  153. // TileSet
  154. EditorGUI.PropertyField(
  155. new Rect(rect.x, y, rect.width, lineHeight),
  156. tileSetProp, new GUIContent("Tile Set"));
  157. y += lineHeight + spacing;
  158. // IsRequired
  159. var isRequiredProp = element.FindPropertyRelative("IsRequired");
  160. EditorGUI.PropertyField(
  161. new Rect(rect.x, y, rect.width, lineHeight),
  162. isRequiredProp, new GUIContent("Is Required?"));
  163. y += lineHeight + spacing;
  164. // CanAppearOnMainPath
  165. var canAppearOnMainPathProp = element.FindPropertyRelative("CanAppearOnMainPath");
  166. EditorGUI.PropertyField(
  167. new Rect(rect.x, y, rect.width, lineHeight),
  168. canAppearOnMainPathProp, new GUIContent("Can appear on Main Path?"));
  169. y += lineHeight + spacing;
  170. // CanAppearOnBranchPath
  171. var canAppearOnBranchPathProp = element.FindPropertyRelative("CanAppearOnBranchPath");
  172. EditorGUI.PropertyField(
  173. new Rect(rect.x, y, rect.width, lineHeight),
  174. canAppearOnBranchPathProp, new GUIContent("Can appear on Branch Path?"));
  175. y += lineHeight + spacing;
  176. // IsLocked
  177. var isLockedProp = element.FindPropertyRelative("IsLocked");
  178. EditorGUI.PropertyField(
  179. new Rect(rect.x, y, rect.width, lineHeight),
  180. isLockedProp, new GUIContent("Locked"));
  181. y += lineHeight + spacing;
  182. // LockID
  183. EditorGUI.BeginDisabledGroup(isLockedProp == null || !isLockedProp.boolValue);
  184. {
  185. var lockIDProp = element.FindPropertyRelative("LockID");
  186. var dungeonFlow = target as DungeonFlow;
  187. int keyID = lockIDProp.intValue;
  188. EditorGUI.BeginChangeCheck();
  189. EditorUtil.DrawKey(
  190. new Rect(rect.x, y, rect.width, lineHeight),
  191. new GUIContent("Lock Type"), dungeonFlow.KeyManager, ref keyID);
  192. if (EditorGUI.EndChangeCheck())
  193. lockIDProp.intValue = keyID;
  194. }
  195. EditorGUI.EndDisabledGroup();
  196. y += lineHeight + spacing;
  197. // NormalizedPathDepth
  198. var pathDepthProp = element.FindPropertyRelative("NormalizedPathDepth");
  199. EditorGUI.PropertyField(
  200. new Rect(rect.x, y, rect.width, lineHeight),
  201. pathDepthProp, new GUIContent("Path Depth"));
  202. y += lineHeight + spacing;
  203. // NormalizedBranchDepth
  204. var branchDepthProp = element.FindPropertyRelative("NormalizedBranchDepth");
  205. EditorGUI.PropertyField(
  206. new Rect(rect.x, y, rect.width, lineHeight),
  207. branchDepthProp, new GUIContent("Branch Depth"));
  208. }
  209. private string GetCurrentBranchModeLabel()
  210. {
  211. var dungeonFlow = target as DungeonFlow;
  212. switch (dungeonFlow.BranchMode)
  213. {
  214. case BranchMode.Local:
  215. return Labels.LocalBranchMode;
  216. case BranchMode.Global:
  217. return Labels.GlobalBranchMode;
  218. case BranchMode.Section:
  219. return Labels.SectionBranchMode;
  220. default:
  221. throw new NotImplementedException(string.Format("{0}.{1} is not implemented", typeof(BranchMode).Name, dungeonFlow.BranchMode));
  222. }
  223. }
  224. public override void OnInspectorGUI()
  225. {
  226. var data = target as DungeonFlow;
  227. if (data == null)
  228. return;
  229. serializedObject.Update();
  230. if (GUILayout.Button(Labels.Validate))
  231. DungeonValidator.Instance.Validate(data);
  232. EditorGUILayout.Space();
  233. EditorGUILayout.Space();
  234. EditorGUILayout.Space();
  235. EditorGUILayout.PropertyField(properties.KeyManager, Labels.KeyManager);
  236. EditorGUILayout.PropertyField(properties.Length, Labels.Length);
  237. // Doorway Connections
  238. using (new EditorGUILayout.VerticalScope("box"))
  239. {
  240. EditorGUILayout.LabelField(Labels.DoorwayConnectionHeader, EditorStyles.boldLabel);
  241. EditorGUILayout.PropertyField(properties.DoorwayConnectionChance, Labels.DoorwayConnectionChance);
  242. EditorGUILayout.PropertyField(properties.RestrictConnectionToSameSection, Labels.RestrictConnectionToSameSection);
  243. }
  244. // Straightening Section
  245. using (new EditorGUILayout.VerticalScope("box"))
  246. {
  247. EditorGUILayout.LabelField(Labels.PathStraighteningHeader, EditorStyles.boldLabel);
  248. EditorUtil.DrawStraightenSettings(properties.StraighteningSettings, true);
  249. }
  250. // Branches Section
  251. using (new EditorGUILayout.VerticalScope("box"))
  252. {
  253. EditorGUILayout.LabelField(Labels.BranchingHeader, EditorStyles.boldLabel);
  254. // Branch Mode
  255. EditorGUILayout.HelpBox(GetCurrentBranchModeLabel(), MessageType.Info);
  256. EditorGUILayout.PropertyField(properties.BranchMode, Labels.BranchMode);
  257. EditorGUI.BeginDisabledGroup(data.BranchMode != BranchMode.Global);
  258. EditorGUILayout.PropertyField(properties.BranchCount, Labels.BranchCount);
  259. EditorGUI.EndDisabledGroup();
  260. EditorGUILayout.Space();
  261. EditorGUILayout.Space();
  262. // Branch Prune Tags
  263. EditorGUILayout.PropertyField(properties.BranchTagPruneMode, Labels.BranchPruneMode);
  264. EditorGUILayout.Space();
  265. properties.BranchPruneTagsList.DoLayoutList();
  266. }
  267. EditorGUILayout.Space();
  268. EditorGUILayout.Space();
  269. EditorGUILayout.Space();
  270. // Open Flow Editor
  271. if (GUILayout.Button(Labels.OpenFlowEditor))
  272. DungeonFlowEditorWindow.Open(data);
  273. EditorGUILayout.Space();
  274. // Tile Injection Rules (ReorderableList)
  275. properties.TileInjectionRulesList.DoLayoutList();
  276. EditorGUILayout.Space();
  277. // Global Props
  278. var globalProps = properties.GlobalProps.serializedProperty;
  279. globalProps.isExpanded = EditorGUILayout.Foldout(globalProps.isExpanded, Labels.GlobalProps, true);
  280. if (globalProps.isExpanded)
  281. {
  282. EditorGUI.indentLevel++;
  283. properties.GlobalProps.DoLayoutList();
  284. EditorGUI.indentLevel--;
  285. }
  286. EditorGUILayout.Space();
  287. // Tile Connection Rules
  288. properties.TileConnectionTags.isExpanded = EditorGUILayout.Foldout(properties.TileConnectionTags.isExpanded, Labels.TileConnectionRules);
  289. if (properties.TileConnectionTags.isExpanded)
  290. {
  291. EditorGUI.indentLevel++;
  292. EditorGUILayout.Space();
  293. EditorGUILayout.PropertyField(properties.TileTagConnectionMode, Labels.TileConnectionTagMode);
  294. EditorGUILayout.Space();
  295. properties.TileConnectionTagsList.DoLayoutList();
  296. EditorGUI.indentLevel--;
  297. }
  298. if (GUI.changed)
  299. EditorUtility.SetDirty(data);
  300. serializedObject.ApplyModifiedProperties();
  301. }
  302. private float GetGlobalPropHeight(int index)
  303. {
  304. return EditorGUI.GetPropertyHeight(properties.GlobalProps.serializedProperty.GetArrayElementAtIndex(index));
  305. }
  306. private void DrawGlobalProp(Rect rect, int index)
  307. {
  308. var propProperty = properties.GlobalProps.serializedProperty.GetArrayElementAtIndex(index);
  309. EditorGUI.PropertyField(rect, propProperty);
  310. }
  311. }
  312. }