using UnityEngine;
using System.Collections.Generic;
///
/// Simple AI entity that navigates the maze using fog of war
/// Only knows about areas it has explored or can currently see
///
[RequireComponent(typeof(Rigidbody2D))]
public class MazeAIEntity : MonoBehaviour
{
[Header("AI Settings")]
[SerializeField] private MazeController mazeController;
[SerializeField] private MazeFogOfWar fogOfWar;
[SerializeField] private float visionRange = 5f;
[SerializeField] private float movementSpeed = 2f;
[SerializeField] private float pathUpdateInterval = 1f;
[Header("Pathfinding")]
[SerializeField] private bool showPath = true;
[SerializeField] private LineRenderer pathRenderer;
private MazeData maze;
private MazePathfinder pathfinder;
private List currentPath = new();
private int currentPathIndex = 0;
private Vector2Int currentTarget;
private float lastPathUpdate = 0f;
void Start()
{
maze = mazeController.GetCurrentMaze();
pathfinder = new MazePathfinder(maze);
// Start at a random start point
if (maze.StartPoints.Count > 0)
{
int startIndex = Random.Range(0, maze.StartPoints.Count);
Vector2Int startPos = maze.StartPoints[startIndex];
transform.position = new Vector3(startPos.x + 0.5f, startPos.y + 0.5f, 0);
// Mark starting area as explored
ExploreCurrentArea();
}
if (pathRenderer == null)
{
pathRenderer = gameObject.AddComponent();
pathRenderer.startWidth = 0.1f;
pathRenderer.endWidth = 0.1f;
pathRenderer.material = new Material(Shader.Find("Sprites/Default"));
pathRenderer.startColor = Color.yellow;
pathRenderer.endColor = Color.yellow;
}
}
void Update()
{
if (maze == null) return;
// Update vision and exploration
UpdateVision();
// Update pathfinding
if (Time.time - lastPathUpdate > pathUpdateInterval)
{
UpdatePathfinding();
lastPathUpdate = Time.time;
}
// Move along path
FollowPath();
}
///
/// Updates the entity's vision and explores new areas
///
private void UpdateVision()
{
Vector2Int currentTile = WorldToTile(transform.position);
if (fogOfWar != null)
{
fogOfWar.UpdateEntityVision(gameObject, currentTile, visionRange);
}
else
{
// If no fog of war, explore current area
ExploreCurrentArea();
}
}
///
/// Explores the area around the current position
///
private void ExploreCurrentArea()
{
Vector2Int center = WorldToTile(transform.position);
HashSet explored = new();
int range = Mathf.CeilToInt(visionRange);
for (int x = center.x - range; x <= center.x + range; x++)
{
for (int y = center.y - range; y <= center.y + range; y++)
{
Vector2Int tile = new Vector2Int(x, y);
if (Vector2Int.Distance(center, tile) <= visionRange && maze.IsInBounds(x, y))
{
explored.Add(tile);
}
}
}
if (fogOfWar != null)
{
fogOfWar.ExploreTiles(explored);
}
}
///
/// Updates pathfinding towards an exit
///
private void UpdatePathfinding()
{
if (maze.ExitPoints.Count == 0) return;
Vector2Int currentPos = WorldToTile(transform.position);
// Find closest unexplored exit
Vector2Int targetExit = FindBestExit(currentPos);
if (targetExit != currentPos)
{
// Only pathfind to areas we know about
HashSet knownTiles = fogOfWar != null ?
fogOfWar.GetExploredTiles() : GetAllTiles();
// Find path within known areas
currentPath = FindPathInKnownArea(currentPos, targetExit, knownTiles);
if (currentPath.Count > 0)
{
currentPathIndex = 0;
UpdatePathVisualization();
}
}
}
///
/// Finds the best exit to path towards
///
private Vector2Int FindBestExit(Vector2Int from)
{
Vector2Int bestExit = from;
float bestDistance = float.MaxValue;
foreach (var exit in maze.ExitPoints)
{
// Only consider exits we know about
if (fogOfWar == null || fogOfWar.IsTileExplored(exit))
{
float distance = Vector2Int.Distance(from, exit);
if (distance < bestDistance)
{
bestDistance = distance;
bestExit = exit;
}
}
}
return bestExit;
}
///
/// Finds a path within explored areas
///
private List FindPathInKnownArea(Vector2Int start, Vector2Int goal, HashSet knownTiles)
{
// Simple A* within known tiles
var openSet = new List { start };
var cameFrom = new Dictionary();
var gScore = new Dictionary { [start] = 0 };
var fScore = new Dictionary { [start] = Vector2Int.Distance(start, goal) };
while (openSet.Count > 0)
{
// Find node with lowest fScore
Vector2Int current = openSet[0];
float lowestF = fScore[current];
for (int i = 1; i < openSet.Count; i++)
{
if (fScore[openSet[i]] < lowestF)
{
current = openSet[i];
lowestF = fScore[current];
}
}
if (current == goal)
{
return ReconstructPath(cameFrom, current);
}
openSet.Remove(current);
// Check neighbors
foreach (var neighbor in maze.GetAdjacentWalkable(current.x, current.y))
{
if (!knownTiles.Contains(neighbor)) continue; // Only path through known tiles
float tentativeG = gScore[current] + 1; // Assume cost of 1
if (!gScore.ContainsKey(neighbor) || tentativeG < gScore[neighbor])
{
cameFrom[neighbor] = current;
gScore[neighbor] = tentativeG;
fScore[neighbor] = tentativeG + Vector2Int.Distance(neighbor, goal);
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
}
return new List(); // No path found
}
///
/// Reconstructs the path from A* results
///
private List ReconstructPath(Dictionary cameFrom, Vector2Int current)
{
var path = new List { current };
while (cameFrom.ContainsKey(current))
{
current = cameFrom[current];
path.Insert(0, current);
}
return path;
}
///
/// Gets all tiles (fallback when no fog of war)
///
private HashSet GetAllTiles()
{
HashSet allTiles = new();
for (int x = 0; x < maze.Width; x++)
{
for (int y = 0; y < maze.Height; y++)
{
allTiles.Add(new Vector2Int(x, y));
}
}
return allTiles;
}
///
/// Moves along the current path
///
private void FollowPath()
{
if (currentPath.Count == 0 || currentPathIndex >= currentPath.Count) return;
Vector2Int targetTile = currentPath[currentPathIndex];
Vector3 targetPos = new Vector3(targetTile.x + 0.5f, targetTile.y + 0.5f, 0);
Vector3 currentPos = transform.position;
// Move towards target
Vector3 direction = (targetPos - currentPos).normalized;
transform.position += direction * movementSpeed * Time.deltaTime;
// Check if reached target
if (Vector3.Distance(currentPos, targetPos) < 0.1f)
{
currentPathIndex++;
// If reached end of path, find new path
if (currentPathIndex >= currentPath.Count)
{
currentPath.Clear();
UpdatePathfinding();
}
}
}
///
/// Updates the path visualization
///
private void UpdatePathVisualization()
{
if (!showPath || pathRenderer == null) return;
pathRenderer.positionCount = currentPath.Count;
for (int i = 0; i < currentPath.Count; i++)
{
Vector2Int tile = currentPath[i];
pathRenderer.SetPosition(i, new Vector3(tile.x + 0.5f, tile.y + 0.5f, -0.1f));
}
}
///
/// Converts world position to tile coordinates
///
private Vector2Int WorldToTile(Vector3 worldPos)
{
return new Vector2Int(Mathf.FloorToInt(worldPos.x), Mathf.FloorToInt(worldPos.y));
}
void OnDestroy()
{
if (fogOfWar != null)
{
fogOfWar.RemoveEntity(gameObject);
}
}
}