using System.Collections.Generic; using System.Linq; using Grid; using UnityEngine; public class Pathfinding : MonoBehaviour { private const int moveStraightCost = 10; private const int moveDiagonalCost = 14; [SerializeField] private Transform gridDebugObjectPrefab; [SerializeField] private LayerMask obstaclesLayerMask; public static Pathfinding Instance { get; private set; } private GridSystem GridSystem { get; set; } public int Width { get; private set;} public int Height { get; private set; } public float CellSize { get; private set;} private const float raycastOffsetDistance = 5f; public void Setup(int width, int height, float cellSize) { Width = width; Height = height; CellSize = cellSize; GridSystem = new(width, height, cellSize, (_, gridPosition) => new(gridPosition)); // GridSystem.CreateDebugObjects(gridDebugObjectPrefab); for (int x = 0; x < Width; x++) { for (int z = 0; z < Height; z++) { GridPosition gridPosition = new(x, z); Vector3 worldPosition = LevelGrid.Instance.GetWorldPosition(gridPosition); if (Physics.Raycast(worldPosition + Vector3.down * raycastOffsetDistance, Vector3.up, raycastOffsetDistance * 2, obstaclesLayerMask)) { GetNode(x, z).IsWalkable = false; } } } } private void Awake() { if (Instance is not null) { Debug.LogError($"There is more than one Pathfinding! {transform} - {Instance}"); Destroy(gameObject); return; } Instance = this; } public List FindPath(GridPosition startGridPosition, GridPosition endGridPosition) { List openList = new(); List closedList = new(); PathNode startNode = GridSystem.GetGridObject(startGridPosition); PathNode endNode = GridSystem.GetGridObject(endGridPosition); openList.Add(startNode); for (int x = 0; x < GridSystem.Width; x++) { for (int z = 0; z < GridSystem.Height; z++) { PathNode pathNode = GridSystem.GetGridObject(new(x, z)); pathNode.GCost = int.MaxValue; pathNode.HCost = 0; pathNode.CameFromPathNode = null; } } startNode.GCost = 0; startNode.HCost = CalculateDistance(startGridPosition, endGridPosition); while (openList.Count > 0) { PathNode currentNode = GetLowestFCostPathNode(openList); if (currentNode == endNode) return CalculatePath(endNode); // Reached final node openList.Remove(currentNode); closedList.Add(currentNode); //currentNode is tested foreach (PathNode neighbourNode in GetNeighbourList(currentNode).Where(neighbourNode => !closedList.Contains(neighbourNode))) { if (!neighbourNode.IsWalkable) { closedList.Add(neighbourNode); // Not walkable continue; } int tentativeGCost = currentNode.GCost + CalculateDistance(currentNode.GridPosition, neighbourNode.GridPosition); if (tentativeGCost < neighbourNode.GCost) { neighbourNode.CameFromPathNode = currentNode; neighbourNode.GCost = tentativeGCost; neighbourNode.HCost = CalculateDistance(neighbourNode.GridPosition, endGridPosition); if (!openList.Contains(neighbourNode)) openList.Add(neighbourNode); } } } return null; //no path found } private int CalculateDistance(GridPosition gridPositionA, GridPosition gridPositionB) { GridPosition gridPositionDistance = gridPositionA - gridPositionB; int xDistance = Mathf.Abs(gridPositionDistance.X); int zDistance = Mathf.Abs(gridPositionDistance.Z); int remaining = Mathf.Abs(xDistance - zDistance); return moveDiagonalCost * Mathf.Min(xDistance, zDistance) + moveStraightCost * remaining; } private PathNode GetLowestFCostPathNode(IReadOnlyList pathNodeList) { PathNode lowestFCostPathNode = pathNodeList[0]; foreach (PathNode currentPathNode in pathNodeList.Where(t => t.FCost < lowestFCostPathNode.FCost)) lowestFCostPathNode = currentPathNode; return lowestFCostPathNode; } private List GetNeighbourList(PathNode currentNode) { List neighbourList = new(); GridPosition gridPosition = currentNode.GridPosition; if (gridPosition.X - 1 >= 0) { neighbourList.Add(GetNode(gridPosition.X - 1, gridPosition.Z + 0)); //Left if (gridPosition.Z - 1 >= 0) neighbourList.Add(GetNode(gridPosition.X - 1, gridPosition.Z - 1)); //LeftDown if (gridPosition.Z < GridSystem.Height) neighbourList.Add(GetNode(gridPosition.X - 1, gridPosition.Z + 1)); //LeftUp } if (gridPosition.X + 1 < GridSystem.Width) { neighbourList.Add(GetNode(gridPosition.X + 1, gridPosition.Z + 0)); //Right if (gridPosition.Z - 1 >= 0) neighbourList.Add(GetNode(gridPosition.X + 1, gridPosition.Z - 1)); //RightDown if (gridPosition.Z + 1 < GridSystem.Height) neighbourList.Add(GetNode(gridPosition.X + 1, gridPosition.Z + 1)); //RightUp } if (gridPosition.Z - 1 >= 0) neighbourList.Add(GetNode(gridPosition.X - 0, gridPosition.Z - 1)); //Down if (gridPosition.Z + 1 < GridSystem.Height) neighbourList.Add(GetNode(gridPosition.X - 0, gridPosition.Z + 1)); //Up return neighbourList; } private PathNode GetNode(int x, int z) => GridSystem.GetGridObject(new(x, z)); private List CalculatePath(PathNode endNode) { List pathNodeList = new() { endNode }; PathNode currentNode = endNode; while (currentNode.CameFromPathNode != null) { pathNodeList.Add(currentNode.CameFromPathNode); currentNode = currentNode.CameFromPathNode; } pathNodeList.Reverse(); return pathNodeList.Select(pathNode => pathNode.GridPosition).ToList(); } }