Untitled
unknown
csharp
5 months ago
8.4 kB
1
Indexable
public class Pathfinding { public static Pathfinding ArenaPathfinding; // Called at start to create an instance for the arena's pathfinding public static void CreateArenaPathfinding(Tilemap arenaTilemap) { ArenaPathfinding = new Pathfinding(arenaTilemap); } // Diagonal cost is roughly sqrt(2) times more expensive, mathematically private const int MoveStraightCost = 10; private const int MoveDiagonalCost = 14; private PathNode[,] _grid; private Tilemap _tilemap; private int tilemapWidth; private int tilemapHeight; // We use a hashset due to it having the quickest lookup time. private HashSet<PathNode> _closedNodes = new(); public Pathfinding(Tilemap obstacleTilemap) { _tilemap = obstacleTilemap; // Cut off edges with null tiles to save compute _tilemap.CompressBounds(); tilemapWidth = _tilemap.cellBounds.size.x; tilemapHeight = _tilemap.cellBounds.size.y; _grid = new PathNode[tilemapWidth, tilemapHeight]; // Loop through tilemap to convert to PathNode objects for (var x = 0; x < tilemapWidth; x++) { for (var y = 0; y < tilemapHeight; y++) { _grid[x, y] = new PathNode(_grid, x, y); if (_tilemap.GetTile(new Vector3Int(x, y) + _tilemap.cellBounds.min) != null) { _closedNodes.Add(_grid[x, y]); } } } } public List<Vector2> GetPath(Vector2 startPos, Vector2 endPos) { // Convert input coordinates into tile coordinates. var startTile = _tilemap.WorldToCell(startPos) - _tilemap.cellBounds.min; var endTile = _tilemap.WorldToCell(endPos) - _tilemap.cellBounds.min; // - _tilemap.cellBounds.min; var startNode = _grid[Mathf.Clamp(startTile.x, 0, _grid.GetLength(0)), Mathf.Clamp(startTile.y, 0, _grid.GetLength(1))]; var endNode = _grid[Mathf.Clamp(endTile.x, 0, _grid.GetLength(0)), Mathf.Clamp(endTile.y, 0, _grid.GetLength(1))]; var openList = new HashSet<PathNode>() { startNode };//(new ByFCost()); var closedList = new HashSet<PathNode>(); foreach (var node in _closedNodes) { closedList.Add(node); } for (var x = 0; x < tilemapWidth; x++) { for (var y = 0; y < tilemapHeight; y++) { var pathNode = _grid[x, y]; pathNode.gCost = int.MaxValue; pathNode.CalculateFCost(); pathNode.cameFromNode = null; // My code if (_closedNodes.Contains(pathNode)) { if (endNode == pathNode) { endNode = GetNearestNeighbor(endNode, endPos); } if (startNode == pathNode) { startNode = GetNearestNeighbor(startNode, startPos); } } // End my code } } startNode.gCost = 0; startNode.hCost = CalculateDistanceCost(startNode, endNode); startNode.CalculateFCost(); while (openList.Count > 0) { var currentNode = LowestFCostNode(openList); if (currentNode == endNode) { return CalculatePath(endNode).Select(pn => (Vector2) _tilemap.CellToWorld(new Vector3Int(pn.X, pn.Y) + _tilemap.cellBounds.min) + new Vector2(0.5f, 0.5f)).ToList(); } openList.Remove(currentNode); closedList.Add(currentNode); foreach (var neighborNode in GetNeighborList(currentNode)) { if (closedList.Contains(neighborNode)) continue; var tentativeGCost = currentNode.gCost + CalculateDistanceCost(currentNode, neighborNode); if (tentativeGCost < neighborNode.gCost) { neighborNode.cameFromNode = currentNode; neighborNode.gCost = tentativeGCost; neighborNode.hCost = CalculateDistanceCost(neighborNode, endNode); neighborNode.CalculateFCost(); openList.Add(neighborNode); } } } // Out of nodes on the open list return new List<Vector2>(); // No path } private PathNode GetNearestNeighbor(PathNode current, Vector2 worldPos) { return GetNeighborList(current) .Where(pn => _tilemap.GetTile(new Vector3Int(pn.X, pn.Y) + _tilemap.cellBounds.min) == null) .OrderBy(pn => Vector2.Distance(worldPos - new Vector2(0f, 0.3f), (Vector2) _tilemap.CellToWorld(new Vector3Int(pn.X, pn.Y) + _tilemap.cellBounds.min) + new Vector2(0.5f, 0.5f))) .FirstOrDefault(); } private HashSet<PathNode> GetNeighborList(PathNode currentNode) { var neighborList = new HashSet<PathNode>(); if (currentNode.X - 1 >= 0) { neighborList.Add(_grid[currentNode.X - 1, currentNode.Y]); // Left if (currentNode.Y - 1 >= 0) neighborList.Add(_grid[currentNode.X - 1, currentNode.Y - 1]); // Left down if (currentNode.Y + 1 < _grid.GetLength(1)) neighborList.Add(_grid[currentNode.X - 1, currentNode.Y + 1]); // Left up } if (currentNode.X + 1 < _grid.GetLength(0)) { neighborList.Add(_grid[currentNode.X + 1, currentNode.Y]); // Right if (currentNode.Y - 1 >= 0) neighborList.Add(_grid[currentNode.X + 1, currentNode.Y - 1]); // Right down if (currentNode.Y + 1 < _grid.GetLength(1)) neighborList.Add(_grid[currentNode.X + 1, currentNode.Y + 1]); // Right up } if (currentNode.Y - 1 >= 0) neighborList.Add(_grid[currentNode.X, currentNode.Y - 1]); // Down if (currentNode.Y + 1 < _grid.GetLength(1)) neighborList.Add(_grid[currentNode.X, currentNode.Y + 1]); // Up return neighborList; } private List<PathNode> CalculatePath(PathNode endNode) { var path = new List<PathNode> {endNode}; var currentNode = endNode; while (currentNode.cameFromNode != null) { path.Add(currentNode.cameFromNode); currentNode = currentNode.cameFromNode; } path.Reverse(); return path; } private int CalculateDistanceCost(PathNode a, PathNode b) { var xDistance = Mathf.Abs(a.X - b.X); var yDistance = Mathf.Abs(a.Y - b.Y); var remaining = Mathf.Abs(xDistance - yDistance); return MoveDiagonalCost * Mathf.Min(xDistance, yDistance) + MoveStraightCost * remaining; } private PathNode LowestFCostNode(HashSet<PathNode> pathNodeList) { return pathNodeList.OrderBy(pn => pn.fCost).FirstOrDefault(); } } public class ByFCost : IComparer<PathNode> { public int Compare(PathNode x, PathNode y) { return x.fCost == y.fCost ? 0 : x.fCost > y.fCost ? 1 : -1; } } public class PathNode { private PathNode[,] _grid; public PathNode(PathNode[,] grid, int x, int y) { _grid = grid; X = x; Y = y; } public int X; public int Y; public int gCost; public int hCost; public int fCost; public void CalculateFCost() { fCost = gCost + hCost; } public PathNode cameFromNode; } }
Editor is loading...
Leave a Comment