using MathUnity; using Pathfinding; using System.Collections; using UnityEngine; public class Map : MonoBehaviour { // Store instance to make sure we don't double spawn public static Map instance; [System.Serializable] public struct NavMeshes { public string name; public float size; public NavMeshes(string name, float size) { this.name = name; this.size = size; } } public NavMeshes[] navMeshs = new NavMeshes[] { new NavMeshes("Graph AI", maxAiColliderSize) }; public static float maxAiColliderSize = 1.6f; // Map building variables public bool built = false; [SerializeField] private bool buildRock = true; [SerializeField] private bool forceEntrance = false; [SerializeField] private bool masks = false; [SerializeField] private bool locks = true; [SerializeField] private bool braid = false; [SerializeField] private bool deepBrain = false; [SerializeField] private bool debugShop = false; [SerializeField] private bool debugRecycleCentre = false; [SerializeField] private int levels = 3; [SerializeField] private int rows = 3; [SerializeField] private int columns = 3; [SerializeField] private int border = 5; [SerializeField] private int minimumDeadends = 4; [SerializeField] private int pathfindingSubsections = 3; [SerializeField] private int rockFillOdds = 48; [SerializeField] private int rockSmooths = 5; [SerializeField] private int braidOdds = 48; [SerializeField] private float corridorOdds = 0.75f; [SerializeField] private float stairOdds = 0.1f; [SerializeField] private float lockOdds = 0.5f; [SerializeField] private bool rotateChunks = true; // Static recycle centre and shop odds [SerializeField] private float shopOddsMult = 0.8f; [SerializeField] private float recycleOddsMult = 0.5f; public static float floorHeight = 50.0f; public static float floorRatio = 1 / floorHeight; // Progress of generation for loading screen public float progress = 0; // Yeild counter to help stop blocking the main thread private int yieldCounterLimit = 6; private void Awake() { instance = this; } public IEnumerator build(TileSetPrefabs prefabs, RandomNumber random) { // Set built flag as false built = false; // Create a map silhouette from the generated maze StartCoroutine(SilhouetteGrid.instance.Generate(levels, rows, columns, forceEntrance, masks, locks, minimumDeadends, braid, braidOdds, corridorOdds, stairOdds, lockOdds, shopOddsMult, recycleOddsMult, debugShop, debugRecycleCentre, deepBrain, random)); // Wait until map is built before spawning objects while (!SilhouetteGrid.instance.built) yield return Yields.WaitForEndOfFrame; // Have at least one row of border tiles to help tile prop searching if (border < 1) border = 1; int borderRows = (rows * prefabs.size) + (border * 2); int borderColumns = (columns * prefabs.size) + (border * 2); Tile[,,] tiles = TileMake.InitaliseTiles(levels, borderRows, borderColumns, prefabs.size); // if we are using cellular automaton to build the rock formations if (buildRock) { // Create surrounding asteroid rock using cellular automata Cellular cellular = new Cellular(); tiles = cellular.Generate(tiles, levels, borderRows, borderColumns, prefabs.size, rockFillOdds, rockSmooths, random); } // Progress, generated cellular tiles progress = 0.1f; yield return Yields.WaitForEndOfFrame; // Create new tilset from maze shape TileSet[,,] tileset = TileSetGenerator.InitaliseTileSet(prefabs, SilhouetteGrid.instance.layout, levels, rows, columns); yield return StartCoroutine(TileSetGenerator.ProcessTileSetArray(prefabs, tileset, SilhouetteGrid.instance.layout, levels, rows, columns, random, rotateChunks)); // Create individual tile details from the created sets yield return StartCoroutine(TileMake.MakeTilesSets(tileset, tiles, prefabs, levels, rows, columns, border)); // Progress, generated all tiles progress = 0.2f; yield return Yields.WaitForEndOfFrame; // Removes single wall tiles that are jutting out and look fugly tiles = TileCheck.RemoveStubs(tiles); progress = 0.25f; yield return Yields.WaitForEndOfFrame; // Create invidual tile GameObjects from array yield return StartCoroutine(LayTiles(tiles)); progress = 0.6f; yield return Yields.WaitForEndOfFrame; // Loops through the entire Map.instanceand sets floor size scores for later detail tile use yield return StartCoroutine(CreateFloorSizeScores(Find.instance.tiles)); progress = 0.65f; yield return Yields.WaitForEndOfFrame; // Create navigation grid for each nav mesh entry for (int i = 0; i < navMeshs.Length; i++) CreatePathfinding(Find.instance.tiles, pathfindingSubsections, navMeshs[i].size, navMeshs[i].name); progress = 0.9f; yield return Yields.WaitForEndOfFrame; // Set progress to complete progress = 1.0f; yield return Yields.WaitForEndOfFrame; // Set built flag built = true; } // Loops though tile array and creates tile objects, enemies and all sorts private IEnumerator LayTiles(Tile[,,] tiles) { // Cache tile spawn component TileSpawn tileSpawn = transform.parent.GetComponentInChildren(); // Intitalise find Find.instance.Initialise(tiles.GetLength(0), tiles.GetLength(1), tiles.GetLength(2)); // Tile spawn position Vector3 position = new Vector3(0, 0, 0); // Yield counter used to stop the process from blocking the main thread too long int yieldCounter = 0; for (int level = 0; level < tiles.GetLength(0); level++) { for (int row = 0; row < tiles.GetLength(1); row++) { for (int column = 0; column < tiles.GetLength(2); column++) { // Spawn all tile objects in the correct calculated position position = new Vector3((column * Tile.size) + Tile.halfSize, (row * Tile.size) + Tile.halfSize, level * floorHeight); Find.instance.AddTileToFloor(tileSpawn.LayTile(tiles[level, row, column], position, tiles[level, row, column].systems), level, row, column); // Update yield counter yieldCounter++; // And check if we need to yield if (yieldCounter > yieldCounterLimit) { // Reset counter yieldCounter = 0; // Place a delay here to avoid consoles stopping the game from running yield return Yields.WaitForEndOfFrame; } } } } } // Create an a-star grid based path for the AI to use private void CreatePathfinding (GameObject[,,] tileObjects, int divide, float colliderSize, string gridName) { // Grid variables float nodeSize = Tile.size / divide; int depth = tileObjects.GetLength(1) * divide; int width = tileObjects.GetLength(2) * divide; // Create paths for AI Initialise(width, depth, nodeSize, colliderSize, LayerMasks.gridpath, gridName); // Set find cache Find.instance.activePath = AstarPath.active; // Have an initial scan, so pathfinding is ready immedietely AstarPath.active.Scan(); } /* Creates the A* Pathfinding Project Grid For The Path System To Use */ public void Initialise(int width, int depth, float nodeSize, float diameter, LayerMask layerMask, string name) { // This holds all graph data AstarData data = AstarPath.active.astarData; data.cacheStartup = true; // Disable path logs. // AstarPath.active.logPathResults = PathLog.None; // This creates a Grid Graph GridGraph gridGraph = data.AddGraph(typeof(GridGraph)) as GridGraph; // Name both grid graphs so we can reference them gridGraph.name = name; // Setting up the default parameters. gridGraph.width = width; gridGraph.depth = depth; gridGraph.nodeSize = nodeSize; // Because it's 2d we are rotating to face the camera, which looks down the z - axis gridGraph.rotation.x = 90.0f; // Calculating the centre based on node size and number of nodes gridGraph.center.x = (width * nodeSize) / 2; gridGraph.center.y = (depth * nodeSize) / 2; gridGraph.center = new Vector3(gridGraph.center.x, gridGraph.center.y, gridGraph.center.z); // Enabled corner cutting, disable slop detection and change slop axis to the Z gridGraph.cutCorners = false; gridGraph.neighbours = NumNeighbours.Eight; gridGraph.maxClimb = 0; gridGraph.maxClimbAxis = 2; // Setting to use 2d grid collision detection gridGraph.collision.use2D = true; gridGraph.collision.type = ColliderType.Sphere; gridGraph.collision.diameter = diameter; gridGraph.collision.mask = layerMask; // Updates internal size from the above values gridGraph.UpdateSizeFromWidthDepth(); } // Finds the maximum square floor size and position and stores it for use when setting the detail tiles public IEnumerator CreateFloorSizeScores(GameObject[,,] tileObjects) { // Yield counter used to stop the process from blocking the main thread too long int yieldCounter = 0; for (int level = 0; level < tileObjects.GetLength(0); level++) { for (int row = tileObjects.GetLength(1) - 1; row >= 0; row--) { for (int column = tileObjects.GetLength(2) - 1; column >= 0; column--) { TileProp tile = tileObjects[level, row, column].GetComponent(); if (tile.IsFloorSize()) { // We assume that floor tiles are surrounded by other tiles TileProp northTile = tileObjects[level, row + 1, column].GetComponent(); TileProp eastTile = tileObjects[level, row, column + 1].GetComponent(); TileProp northEastTile = tileObjects[level, row + 1, column + 1].GetComponent(); // If any tiles to the top and right aren't floor size tiles the score is 1 if (!northTile.IsFloorSize() || !eastTile.IsFloorSize()|| !northEastTile.IsFloorSize()) tile.floorSize = 1; // Else the score is the minimum score from the three tiles, plus 1. else tile.floorSize = Maths.Min(northTile.floorSize, eastTile.floorSize, northEastTile.floorSize) + 1; // Update yield counter yieldCounter++; // And check if we need to yield if (yieldCounter > yieldCounterLimit) { // Reset counter yieldCounter = 0; // Place a delay here to avoid consoles stopping the game from running yield return Yields.WaitForEndOfFrame; } } } } } } }