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; private const float raycastOffsetDistance = 5f; [SerializeField] private Transform gridDebugObjectPrefab; [SerializeField] private LayerMask obstaclesLayerMask; public static Pathfinding Instance { get; private set; } private GridSystem GridSystem { get; set; } private int Width { get; set; } private int Height { get; set; } public float CellSize { get; private set; } private void Awake() { if (Instance is not null) { Debug.LogError($"There is more than one Pathfinding! {transform} - {Instance}"); Destroy(gameObject); return; } Instance = this; } 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; } } } } public bool IsWalkableGridPosition(GridPosition gridPosition) => GridSystem.GetGridObject(gridPosition).IsWalkable; public void SetWalkableGridPosition(GridPosition gridPosition, bool isWalkable) => GridSystem.GetGridObject(gridPosition).IsWalkable = isWalkable; public List FindPath(GridPosition startGridPosition, GridPosition endGridPosition, out int pathLength) { 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) { // Reached final node pathLength = endNode.FCost; return CalculatePath(endNode); } 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) continue; neighbourNode.CameFromPathNode = currentNode; neighbourNode.GCost = tentativeGCost; neighbourNode.HCost = CalculateDistance(neighbourNode.GridPosition, endGridPosition); if (!openList.Contains(neighbourNode)) openList.Add(neighbourNode); } } pathLength = 0; //no path found return null; } private static 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 static PathNode GetLowestFCostPathNode(IReadOnlyList pathNodeList) { PathNode lowestFCostPathNode = pathNodeList[0]; foreach (PathNode currentPathNode in pathNodeList.Where(t => t.FCost < lowestFCostPathNode.FCost)) lowestFCostPathNode = currentPathNode; return lowestFCostPathNode; } private IEnumerable 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 static 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(); } public bool HasPath(GridPosition startGridPosition, GridPosition endGridPosition) => FindPath(startGridPosition, endGridPosition, out _) != null; public int GetPathLength(GridPosition startGridPosition, GridPosition endGridPosition) { FindPath(startGridPosition, endGridPosition, out int pathLength); return pathLength; } }