/// <summary> /// Assembles a path from the data stored in the AStarTileGrid, tracing the tiles using each tile's parent location. /// </summary> /// <param name="aStarTileGrid"></param> /// <param name="endTile"></param> /// <param name="startingLocation"></param> /// <returns></returns> private AStarPath BuildPath(ref AStarTileGrid aStarTileGrid, AStarTile endTile, Vector2Int startingLocation) { AStarTile priorTile = endTile; AStarPath path = new AStarPath(); //Trace through each parent, as the path is effectively stored as a linked list, where the endTile is the start of the list. path.AddTile(tiles[endTile.location]); while (priorTile.location != startingLocation) { path.AddTile(tiles[priorTile.location]); priorTile = aStarTileGrid[priorTile.parentLocation]; } //The starting location is not added in the above loop, so we manually add it after path.AddTile(tiles[startingLocation]); return(path); }
/// Loosely based on https://www.redblobgames.com/pathfinding/a-star/introduction.html ///From Red Blob Games. /// And http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties /// ///More detailed breakdown from https://medium.com/@nicholas.w.swift/easy-a-star-pathfinding-7e6689c7f7b2 ///From Nicholas W. Swift /// <summary> /// Uses A* to find a path from the starting location, to the target location. /// Returns true if a path was found, which is stored in the out variable of path. /// </summary> /// <param name="startingLocation"></param> /// <param name="targetLocation"></param> /// <param name="path"></param> /// <returns></returns> public bool FindPath(Vector2Int startingLocation, Vector2Int targetLocation, out AStarPath path) { //The AStarTileGrid stores the state information for each node (each tile in the grid) AStarTileGrid aStarTileGrid = new AStarTileGrid(length, width, ref tiles); //Acts as a priority queue to deterimine which tile to move through next List <AStarTile> openTiles = new List <AStarTile>(); //Add the starting tile to the open list, to start with. openTiles.Add(new AStarTile(0, Vector2Int.Distance(startingLocation, targetLocation))); AStarTile currentTile = openTiles[0]; bool pathFound = false; //Main loop while (openTiles.Count != 0) { //As openTiles is a sorted list based on the F scores, the first item is tile with the lowest cost. currentTile = openTiles[0]; //If the current tile is at the targetLocation, a path is found and we can terminate our search if (currentTile.location == targetLocation) { pathFound = true; break; } //For each adjacent tile that is not an obstacle foreach (AStarTile neighbour in aStarTileGrid.GetAdjacentEmptyTiles(currentTile.location)) { float routeCost = currentTile.G + Vector2Int.Distance(currentTile.location, neighbour.location); //Explore the neighbouring tile, and update it's costs if (!neighbour.hasBeenExplored) { neighbour.G = routeCost; neighbour.H = CalculateH(neighbour.location, targetLocation); neighbour.hasBeenExplored = true; neighbour.parentLocation = currentTile.location; openTiles.Add(neighbour); continue; } //Neighbour has been explored. Have we found a short root to this tile? if (routeCost < neighbour.G) { //A new path was found. neighbour.G = routeCost; neighbour.parentLocation = currentTile.location; //Reopen the tile if necessary if (!openTiles.Contains(neighbour)) { openTiles.Add(neighbour); } } } //Remove the tile from the open list, so that the A* examines the next tile. Required for termination of the main loop. openTiles.Remove(currentTile); //TODO: Replace this with a more optimized method of updating the priority queue. Note that using a lambda here is not efficient. openTiles.Sort((x, y) => x.F.CompareTo(y.F)); } if (pathFound) { path = BuildPath(ref aStarTileGrid, currentTile, startingLocation); return(true); } //Unable to find a path path = null; return(false); }