//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $// using UnityEngine; using UnityEditor; using System.Collections.Generic; using DungeonArchitect.Builders; using DungeonArchitect.Builders.Grid; using DungeonArchitect.Utils; using DungeonArchitect.Graphs; using DungeonArchitect.SpatialConstraints; using DungeonArchitect.Graphs.SpatialConstraints; using DungeonArchitect.Grammar; using DungeonArchitect.Editors.SnapFlow; using DungeonArchitect.Editors.Flow; using DungeonArchitect.Editors.Flow.Impl; using DungeonArchitect.Flow.Impl.GridFlow; using DungeonArchitect.Flow.Impl.SnapGridFlow; using DungeonArchitect.Landscape; using DungeonArchitect.UI.Widgets.GraphEditors; using DungeonArchitect.UI; using DungeonArchitect.UI.Impl.UnityEditor; namespace DungeonArchitect.Editors { /// /// Utility functions for various editor based features of Dungeon Architect /// public class DungeonEditorHelper { /// /// Creates a new Dungeon Theme in the specified asset folder. Access from the Create context menu in the Project window /// [MenuItem("Assets/Create/Dungeon Architect/Dungeon Theme", false, 100)] public static void CreateThemeAssetInBrowser() { var defaultFileName = "DungeonTheme.asset"; var path = GetAssetBrowserPath(); var graph = CreateAssetInBrowser(path, defaultFileName); CreateDefaultMarkerNodes(graph); HandlePostAssetCreated(graph); } [MenuItem("Assets/Create/Dungeon Architect/Snap Builder/Snap Graph", false, 1000)] public static void CreateSnapAssetInBrowser() { var defaultFileName = "DungeonSnapFlow.asset"; var path = GetAssetBrowserPath(); var dungeonFlow = CreateAssetInBrowser(path, defaultFileName); SnapEditorUtils.InitAsset(dungeonFlow, new UnityEditorUIPlatform()); HandlePostAssetCreated(dungeonFlow); } [MenuItem("Assets/Create/Dungeon Architect/Snap Builder/Snap Connection", false, 1000)] public static void CreateSnapConnectionAssetInBrowser() { CreateSnapConnectionAssetImpl(); } [MenuItem("Assets/Create/Dungeon Architect/Grid Flow Builder/Grid Flow Graph", false, 1000)] public static void CreateGridFlowAssetInBrowser() { var defaultFileName = "DungeonGridFlow.asset"; var path = GetAssetBrowserPath(); var gridFlow = CreateAssetInBrowser(path, defaultFileName); FlowEditorUtils.InitAsset(gridFlow, new UnityEditorUIPlatform()); HandlePostAssetCreated(gridFlow); } [MenuItem("Assets/Create/Dungeon Architect/Snap Grid Flow Builder/Snap Grid Flow Graph", false, 1000)] public static void CreateSnapGridFlowGraphAssetInBrowser() { var defaultFileName = "SnapGridFlow.asset"; var path = GetAssetBrowserPath(); var gridFlow = CreateAssetInBrowser(path, defaultFileName); FlowEditorUtils.InitAsset(gridFlow, new UnityEditorUIPlatform()); HandlePostAssetCreated(gridFlow); } [MenuItem("Assets/Create/Dungeon Architect/Snap Grid Flow Builder/Module Bounds", false, 1000)] public static void CreateSnapGridFlowModuleBoundsAssetInBrowser() { var defaultFileName = "SnapGridFlowModuleBounds.asset"; var path = GetAssetBrowserPath(); var moduleBounds = CreateAssetInBrowser(path, defaultFileName); SnapGridFlowEditorUtils.InitAsset(moduleBounds); HandlePostAssetCreated(moduleBounds); } [MenuItem("Assets/Create/Dungeon Architect/Snap Grid Flow Builder/Module Database", false, 1000)] public static void CreateSnapGridFlowModuleDatabaseAssetInBrowser() { var defaultFileName = "SnapGridFlowModuleDB.asset"; var path = GetAssetBrowserPath(); var moduleDatabase = CreateAssetInBrowser(path, defaultFileName); SnapGridFlowEditorUtils.InitAsset(moduleDatabase); HandlePostAssetCreated(moduleDatabase); } [MenuItem("Assets/Create/Dungeon Architect/Snap Grid Flow Builder/Placeable Marker", false, 1000)] public static void CreateSnapGridFlowPlaceableMarkerAssetInBrowser() { var path = GetAssetBrowserPath(); var fileName = MakeFilenameUnique(path, "PlaceableMarker.prefab"); var fullPath = path + "/" + fileName; AssetBuilder.CreatePlaceableMarkerAsset(fullPath); } [MenuItem("Assets/Create/Dungeon Architect/Snap Grid Flow Builder/Snap Connection", false, 1000)] public static void CreateSnapGridFlowConnectionAssetInBrowser() { CreateSnapConnectionAssetImpl(); } private static void CreateSnapConnectionAssetImpl() { var path = GetAssetBrowserPath(); var fileName = MakeFilenameUnique(path, "SnapConnection.prefab"); var fullPath = path + "/" + fileName; AssetBuilder.CreateSnapConnection(fullPath); } [MenuItem("Assets/Create/Dungeon Architect/Landscape/Landscape Restoration Cache", false, 3000)] public static void CreateDungeonLandscapeRestCacheInBrowser() { var defaultFileName = "DungeonLandscapeRestorationCache.asset"; var path = GetAssetBrowserPath(); var cacheAsset = CreateAssetInBrowser(path, defaultFileName); HandlePostAssetCreated(cacheAsset); } /// /// Handle opening of theme graphs. /// When the user right clicks on the theme graph and selects open, the graph is shown in the theme editor /// /// /// /// true if trying to open a dungeon theme, indicating that it has been handled. false otherwise [UnityEditor.Callbacks.OnOpenAsset(1)] public static bool OnOpenAsset(int instanceID, int line) { Object activeObject = EditorUtility.InstanceIDToObject(instanceID); if (activeObject is Graph) { var graph = Selection.activeObject as Graph; ShowThemeEditor(graph); return true; //catch open file } else if (activeObject is SnapFlowAsset) { var dungeonFlow = Selection.activeObject as SnapFlowAsset; ShowDungeonFlowEditor(dungeonFlow); return true; } else if (activeObject is GridFlowAsset) { var dungeonFlow = Selection.activeObject as GridFlowAsset; ShowDungeonGridFlowEditor(dungeonFlow); return true; } else if (activeObject is SnapGridFlowAsset) { var dungeonFlow = Selection.activeObject as SnapGridFlowAsset; ShowSnapGridFlowEditor(dungeonFlow); return true; } return false; // let unity open the file } public static T CreateAssetInBrowser(string path, string defaultFilename) where T : ScriptableObject { var fileName = MakeFilenameUnique(path, defaultFilename); var fullPath = path + "/" + fileName; var asset = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(asset, fullPath); return asset; } public static void HandlePostAssetCreated(ScriptableObject asset) { AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); ProjectWindowUtil.ShowCreatedAsset(asset); } public static string GetActiveScenePath() { var scenePath = UnityEngine.SceneManagement.SceneManager.GetActiveScene().path; scenePath = scenePath.Replace('\\', '/'); int trimIndex = scenePath.LastIndexOf('/'); scenePath = scenePath.Substring(0, trimIndex); return scenePath; } /// /// Shows the dungeon theme editor window and loads the specified graph into it /// /// The graph to load in the dungeon theme editor window public static void ShowThemeEditor(Graph graph) { if (graph != null) { var window = EditorWindow.GetWindow(); if (window != null) { window.Init(graph); } } else { Debug.LogWarning("Invalid Dungeon theme file"); } } /// /// Shows the dungeon flow editor window and loads the specified graph into it /// /// The graph to load in the dungeon theme editor window public static void ShowDungeonFlowEditor(SnapFlowAsset snapFlow) { if (snapFlow != null) { var window = EditorWindow.GetWindow(); if (window != null) { window.Init(snapFlow); } } else { Debug.LogWarning("Invalid Dungeon flow file"); } } /// /// Shows the dungeon grid flow editor window and loads the specified graph into it /// /// The graph to load in the dungeon theme editor window public static void ShowDungeonGridFlowEditor(GridFlowAsset gridFlow) { if (gridFlow != null) { var window = EditorWindow.GetWindow(); if (window != null) { window.Init(gridFlow); } } else { Debug.LogWarning("Invalid Dungeon grid flow file"); } } /// /// Shows the dungeon grid flow editor window and loads the specified graph into it /// /// The graph to load in the dungeon theme editor window public static void ShowSnapGridFlowEditor(SnapGridFlowAsset snapGridFlow) { if (snapGridFlow != null) { var window = EditorWindow.GetWindow(); if (window != null) { window.Init(snapGridFlow); } } else { Debug.LogWarning("Invalid Dungeon grid flow file"); } } /// /// Creates a unique filename in the specified asset directory /// /// The target directory this file will be placed in. Used for finding non-colliding filenames /// The prefered filename. Will add incremental numbers to it till it finds a free filename /// A filename not currently used in the specified directory public static string MakeFilenameUnique(string dir, string filename) { string fileNamePart = System.IO.Path.GetFileNameWithoutExtension(filename); string fileExt = System.IO.Path.GetExtension(filename); var indexedFileName = fileNamePart + fileExt; string path = System.IO.Path.Combine(dir, indexedFileName); for (int i = 1; ; ++i) { if (!System.IO.File.Exists(path)) return indexedFileName; indexedFileName = fileNamePart + " " + i + fileExt; path = System.IO.Path.Combine(dir, indexedFileName); } } public static T CreateConstraintRule(SpatialConstraintAsset spatialConstraint) where T : ConstraintRule { if (spatialConstraint == null || spatialConstraint.hostThemeNode == null) return null; var rule = ScriptableObject.CreateInstance(); var assetObject = spatialConstraint.hostThemeNode.Graph; AssetDatabase.AddObjectToAsset(rule, assetObject); return rule; } public static void DestroySpatialConstraintAsset(SpatialConstraintAsset spatialConstraint) { if (spatialConstraint == null) { return; } if (spatialConstraint.hostThemeNode == null) { return; } var asset = spatialConstraint.hostThemeNode.Graph; Undo.RegisterCompleteObjectUndo(asset, "Delete Node Spatial Constraint"); var objectsToDestroy = new List(); if (spatialConstraint != null && spatialConstraint.Graph != null) { foreach (var node in spatialConstraint.Graph.Nodes) { if (node is SCRuleNode) { var ruleNode = node as SCRuleNode; foreach (var constraint in ruleNode.constraints) { objectsToDestroy.Add(constraint); } } objectsToDestroy.Add(node); } objectsToDestroy.Add(spatialConstraint.Graph); objectsToDestroy.Add(spatialConstraint); } foreach (var objectToDestroy in objectsToDestroy) { if (objectToDestroy != null) { Undo.DestroyObjectImmediate(objectToDestroy); } } } public static string GetAssetBrowserPath() { string path = AssetDatabase.GetAssetPath(Selection.activeObject); if (path == "") { path = "Assets"; } else if (System.IO.Path.GetExtension(path) != "") { path = path.Replace(System.IO.Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), ""); } return path; } public static T GetWindowIfOpen() where T : Object { T[] existingWindows = Resources.FindObjectsOfTypeAll(); T existingWindow = null; if (existingWindows.Length > 0) { existingWindow = existingWindows[0]; } return existingWindow; } /// /// Marks the graph as dirty so that it is serialized to disk again when saved /// /// public static void MarkAsDirty(Graph graph) { EditorUtility.SetDirty(graph); } public static void CreateDefaultSpatialConstraintNodes(SpatialConstraintAsset constraintAsset, UIUndoSystem undo) { var position = SCBaseDomainNode.TileSize * 0.5f * Vector2.one; CreateSpatialConstraintNode(constraintAsset, position, undo); } public static T CreateSpatialConstraintNode(SpatialConstraintAsset constraintAsset, Vector2 worldPosition, UIUndoSystem undo) where T : SCBaseDomainNode { var graph = constraintAsset.Graph; var node = GraphOperations.CreateNode(graph, undo); node.Position = worldPosition; node.SnapNode(); var hostAsset = constraintAsset.hostThemeNode.Graph; GraphEditorUtils.AddToAsset(new UnityEditorUIPlatform(), hostAsset, node); return node; } /// /// Creates default marker nodes when a new graph is created /// static void CreateDefaultMarkerNodes(Graph graph) { if (graph == null) { Debug.LogWarning("Cannot create default marker nodes. graph is null"); return; } var markerNames = DungeonBuilderDefaultMarkers.GetDefaultMarkers(typeof(GridDungeonBuilder)); // Make sure we don't have any nodes in the graph if (graph.Nodes.Count > 0) { return; } const int INTER_NODE_X = 200; const int INTER_NODE_Y = 300; int itemsPerRow = markerNames.Length / 2; for (int i = 0; i < markerNames.Length; i++) { int ix = i % itemsPerRow; int iy = i / itemsPerRow; int x = ix * INTER_NODE_X; int y = iy * INTER_NODE_Y; var node = GraphOperations.CreateNode(graph, null); GraphEditorUtils.AddToAsset(new UnityEditorUIPlatform(), graph, node); node.Position = new Vector2(x, y); node.Caption = markerNames[i]; } } /// /// Creates an editor tag /// /// public static void CreateEditorTag(string tag) { SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]); SerializedProperty tagsProp = tagManager.FindProperty("tags"); // Check if the tag is already present for (int i = 0; i < tagsProp.arraySize; i++) { SerializedProperty t = tagsProp.GetArrayElementAtIndex(i); if (t.stringValue.Equals(tag)) { // Tag already exists. do not add a duplicate return; } } tagsProp.InsertArrayElementAtIndex(0); SerializedProperty n = tagsProp.GetArrayElementAtIndex(0); n.stringValue = tag; tagManager.ApplyModifiedProperties(); } // Resets the node IDs of the graph. Useful if you have cloned another graph //[MenuItem("Debug DA/Fix Node Ids")] public static void _Advanced_RecreateGraphNodeIds() { var editor = EditorWindow.GetWindow(); if (editor != null && editor.GraphEditor != null && editor.GraphEditor.Graph != null) { var graph = editor.GraphEditor.Graph; foreach (var node in graph.Nodes) { node.Id = System.Guid.NewGuid().ToString(); } } } public static void MarkSceneDirty() { if (!Application.isPlaying) { UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()); } } } }