using System.Collections.Generic; using System.Linq; using Grid; using UnityEngine; public class Pathfinding : MonoBehaviour { private const int MOVE_STRAIGHT_COST = 10; private const float raycastOffsetDistance = 5f; [SerializeField] private Transform gridDebugObjectPrefab; [SerializeField] private LayerMask obstaclesLayerMask; public static Pathfinding Instance { get; private set; } private GridSystemHex GridSystemHex { 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; GridSystemHex = 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) => GridSystemHex.GetGridObject(gridPosition).IsWalkable; public void SetWalkableGridPosition(GridPosition gridPosition, bool isWalkable) => GridSystemHex.GetGridObject(gridPosition).IsWalkable = isWalkable; public List FindPath(GridPosition startGridPosition, GridPosition endGridPosition, out int pathLength) { List openList = new(); List closedList = new(); PathNode startNode = GridSystemHex.GetGridObject(startGridPosition); PathNode endNode = GridSystemHex.GetGridObject(endGridPosition); openList.Add(startNode); for (int x = 0; x < GridSystemHex.Width; x++) { for (int z = 0; z < GridSystemHex.Height; z++) { PathNode pathNode = GridSystemHex.GetGridObject(new(x, z)); pathNode.GCost = int.MaxValue; pathNode.HCost = 0; pathNode.CameFromPathNode = null; } } startNode.GCost = 0; startNode.HCost = CalculateHeuristicDistance(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 + MOVE_STRAIGHT_COST; if (tentativeGCost >= neighbourNode.GCost) continue; neighbourNode.CameFromPathNode = currentNode; neighbourNode.GCost = tentativeGCost; neighbourNode.HCost = CalculateHeuristicDistance(neighbourNode.GridPosition, endGridPosition); if (!openList.Contains(neighbourNode)) openList.Add(neighbourNode); } } pathLength = 0; //no path found return null; } private int CalculateHeuristicDistance(GridPosition gridPositionA, GridPosition gridPositionB) => Mathf.RoundToInt(MOVE_STRAIGHT_COST * Vector3.Distance(GridSystemHex.GetWorldPosition(gridPositionA), GridSystemHex.GetWorldPosition(gridPositionB))); 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.X + 1 < GridSystemHex.Width) neighbourList.Add(GetNode(gridPosition.X + 1, gridPosition.Z + 0)); //Right if (gridPosition.Z - 1 >= 0) neighbourList.Add(GetNode(gridPosition.X - 0, gridPosition.Z - 1)); //Down left if (gridPosition.Z + 1 < GridSystemHex.Height) neighbourList.Add(GetNode(gridPosition.X - 0, gridPosition.Z + 1)); //Up left bool oddRow = gridPosition.Z % 2 == 1; if (oddRow) { if (gridPosition.X + 1 < GridSystemHex.Width) { if (gridPosition.Z - 1 >= 0) neighbourList.Add(GetNode(gridPosition.X + 1, gridPosition.Z - 1)); //Down right if (gridPosition.Z + 1 < GridSystemHex.Height) neighbourList.Add(GetNode(gridPosition.X + 1, gridPosition.Z + 1)); //Up right } } else { if (gridPosition.X - 1 >= 0) { if (gridPosition.Z - 1 >= 0) neighbourList.Add(GetNode(gridPosition.X - 1, gridPosition.Z - 1)); //Down right if (gridPosition.Z + 1 < GridSystemHex.Height) neighbourList.Add(GetNode(gridPosition.X - 1, gridPosition.Z + 1)); //Up right } } return neighbourList; } private PathNode GetNode(int x, int z) => GridSystemHex.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; } }