MazePathfinder.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. /// <summary>
  5. /// Handles pathfinding through the maze using A* algorithm
  6. /// Takes into account terrain movement costs
  7. /// </summary>
  8. public class MazePathfinder
  9. {
  10. private MazeData mazeData;
  11. public class PathNode
  12. {
  13. public Vector2Int Position { get; set; }
  14. public PathNode Parent { get; set; }
  15. public float GCost { get; set; } // Cost from start
  16. public float HCost { get; set; } // Heuristic cost to goal
  17. public float FCost => GCost + HCost;
  18. public PathNode(Vector2Int position, PathNode parent, float gCost, float hCost)
  19. {
  20. Position = position;
  21. Parent = parent;
  22. GCost = gCost;
  23. HCost = hCost;
  24. }
  25. }
  26. public MazePathfinder(MazeData mazeData)
  27. {
  28. this.mazeData = mazeData;
  29. }
  30. /// <summary>
  31. /// Finds a path from start to goal using A* algorithm
  32. /// </summary>
  33. public List<Vector2Int> FindPath(Vector2Int start, Vector2Int goal)
  34. {
  35. var openSet = new List<PathNode>();
  36. var closedSet = new HashSet<Vector2Int>();
  37. var startNode = new PathNode(start, null, 0, Heuristic(start, goal));
  38. openSet.Add(startNode);
  39. while (openSet.Count > 0)
  40. {
  41. // Find node with lowest F cost
  42. int current = 0;
  43. for (int i = 1; i < openSet.Count; i++)
  44. {
  45. if (openSet[i].FCost < openSet[current].FCost)
  46. {
  47. current = i;
  48. }
  49. }
  50. var currentNode = openSet[current];
  51. if (currentNode.Position == goal)
  52. {
  53. return ReconstructPath(currentNode);
  54. }
  55. openSet.RemoveAt(current);
  56. closedSet.Add(currentNode.Position);
  57. // Check neighbors
  58. var neighbors = GetNeighbors(currentNode.Position);
  59. foreach (var neighbor in neighbors)
  60. {
  61. if (closedSet.Contains(neighbor))
  62. continue;
  63. float movementCost = GetMovementCost(neighbor);
  64. float tentativeGCost = currentNode.GCost + movementCost;
  65. var existingNode = openSet.FirstOrDefault(n => n.Position == neighbor);
  66. if (existingNode != null)
  67. {
  68. if (tentativeGCost < existingNode.GCost)
  69. {
  70. existingNode.Parent = currentNode;
  71. existingNode.GCost = tentativeGCost;
  72. }
  73. }
  74. else
  75. {
  76. var hCost = Heuristic(neighbor, goal);
  77. openSet.Add(new PathNode(neighbor, currentNode, tentativeGCost, hCost));
  78. }
  79. }
  80. }
  81. // No path found
  82. return new List<Vector2Int>();
  83. }
  84. /// <summary>
  85. /// Gets the movable neighbors of a position
  86. /// </summary>
  87. private List<Vector2Int> GetNeighbors(Vector2Int position)
  88. {
  89. var neighbors = new List<Vector2Int>();
  90. Vector2Int[] directions = new[]
  91. {
  92. new Vector2Int(position.x + 1, position.y),
  93. new Vector2Int(position.x - 1, position.y),
  94. new Vector2Int(position.x, position.y + 1),
  95. new Vector2Int(position.x, position.y - 1),
  96. // Optionally add diagonals:
  97. // new Vector2Int(position.x + 1, position.y + 1),
  98. // new Vector2Int(position.x - 1, position.y + 1),
  99. // new Vector2Int(position.x + 1, position.y - 1),
  100. // new Vector2Int(position.x - 1, position.y - 1),
  101. };
  102. foreach (var dir in directions)
  103. {
  104. if (mazeData.IsWalkable(dir.x, dir.y))
  105. {
  106. neighbors.Add(dir);
  107. }
  108. }
  109. return neighbors;
  110. }
  111. /// <summary>
  112. /// Gets the movement cost for a tile based on terrain
  113. /// </summary>
  114. private float GetMovementCost(Vector2Int position)
  115. {
  116. var tile = mazeData.GetTile(position.x, position.y);
  117. if (tile == null) return float.MaxValue;
  118. return tile.GetMovementCost();
  119. }
  120. /// <summary>
  121. /// Heuristic function (Manhattan distance)
  122. /// </summary>
  123. private float Heuristic(Vector2Int from, Vector2Int to)
  124. {
  125. return Mathf.Abs(from.x - to.x) + Mathf.Abs(from.y - to.y);
  126. }
  127. /// <summary>
  128. /// Reconstructs the path from goal to start
  129. /// </summary>
  130. private List<Vector2Int> ReconstructPath(PathNode node)
  131. {
  132. var path = new List<Vector2Int>();
  133. while (node != null)
  134. {
  135. path.Add(node.Position);
  136. node = node.Parent;
  137. }
  138. path.Reverse();
  139. return path;
  140. }
  141. /// <summary>
  142. /// Finds all reachable tiles from a starting position within a certain distance
  143. /// </summary>
  144. public HashSet<Vector2Int> FindReachableTiles(Vector2Int start, float maxDistance)
  145. {
  146. var reachable = new HashSet<Vector2Int>();
  147. var queue = new Queue<(Vector2Int pos, float distance)>();
  148. queue.Enqueue((start, 0));
  149. reachable.Add(start);
  150. while (queue.Count > 0)
  151. {
  152. var (current, distance) = queue.Dequeue();
  153. var neighbors = GetNeighbors(current);
  154. foreach (var neighbor in neighbors)
  155. {
  156. if (!reachable.Contains(neighbor))
  157. {
  158. float cost = GetMovementCost(neighbor);
  159. float newDistance = distance + cost;
  160. if (newDistance <= maxDistance)
  161. {
  162. reachable.Add(neighbor);
  163. queue.Enqueue((neighbor, newDistance));
  164. }
  165. }
  166. }
  167. }
  168. return reachable;
  169. }
  170. }