This project demonstrates the implementation of the A* pathfinding algorithm within a randomly generated maze. The maze generation is achieved using a recursive division algorithm, and the pathfinding process is visualized in real-time, showcasing the F, G, and H values used to determine the optimal path.
This project showcases the implementation of the A* pathfinding algorithm within a randomly generated maze. The combination of recursive maze generation and real-time visualization of the pathfinding process provides a comprehensive demonstration of algorithmic thinking and problem-solving skills.
Core Features
- Random Maze Generation: Utilizes a recursive division algorithm to create complex and varied mazes.
- A Pathfinding Algorithm*: Implements the A* algorithm to find the shortest path from the start point to the endpoint.
- Real-Time Visualization: Displays the F, G, and H values for each node during the pathfinding process, providing a clear understanding of the algorithm’s decision-making.
- Interactive Controls: Allows users to generate new mazes and initiate the pathfinding process with keyboard inputs.





Key Components
1. A* Pathfinding (FindPathAStar.cs)
This script implements the A* pathfinding algorithm, calculating the F, G, and H values for each node and visualizing the search process.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class PathMarker {
public MapLocation location;
public float G, H, F;
public GameObject marker;
public PathMarker parent;
public PathMarker(MapLocation l, float g, float h, float f, GameObject m, PathMarker p) {
location = l;
G = g;
H = h;
F = f;
marker = m;
parent = p;
}
public override bool Equals(object obj) {
if ((obj == null) || !this.GetType().Equals(obj.GetType()))
return false;
else
return location.Equals(((PathMarker)obj).location);
}
public override int GetHashCode() {
return 0;
}
}
public class FindPathAStar : MonoBehaviour {
public Maze maze;
public Material closedMaterial;
public Material openMaterial;
public GameObject start;
public GameObject end;
public GameObject pathP;
PathMarker startNode;
PathMarker goalNode;
PathMarker lastPos;
bool done = false;
List<PathMarker> open = new List<PathMarker>();
List<PathMarker> closed = new List<PathMarker>();
void RemoveAllMarkers() {
GameObject[] markers = GameObject.FindGameObjectsWithTag("marker");
foreach (GameObject m in markers) Destroy(m);
}
void BeginSearch() {
done = false;
RemoveAllMarkers();
List<MapLocation> locations = new List<MapLocation>();
for (int z = 1; z < maze.depth - 1; ++z) {
for (int x = 1; x < maze.width - 1; ++x) {
if (maze.map[x, z] != 1) {
locations.Add(new MapLocation(x, z));
}
}
}
locations.Shuffle();
Vector3 startLocation = new Vector3(locations[0].x * maze.scale, 0.0f, locations[0].z * maze.scale);
startNode = new PathMarker(new MapLocation(locations[0].x, locations[1].z),
0.0f, 0.0f, 0.0f, Instantiate(start, startLocation, Quaternion.identity), null);
Vector3 endLocation = new Vector3(locations[1].x * maze.scale, 0.0f, locations[1].z * maze.scale);
goalNode = new PathMarker(new MapLocation(locations[1].x, locations[1].z),
0.0f, 0.0f, 0.0f, Instantiate(end, endLocation, Quaternion.identity), null);
open.Clear();
closed.Clear();
open.Add(startNode);
lastPos = startNode;
}
void Search(PathMarker thisNode)
{
if (thisNode == null) return;
if (thisNode.Equals(goalNode)) //goal position found
{
done = true;
return;
}
foreach (MapLocation dir in maze.directions)
{
MapLocation neighbour = dir + thisNode.location;
if (maze.map[neighbour.x, neighbour.z] == 1) continue;
if(neighbour.x < 1 || neighbour.x >= maze.width || neighbour.z < 1 || neighbour.z >= maze.depth) continue;
if (IsClosed(neighbour)) continue;
float G = Vector2.Distance(thisNode.location.ToVector(), neighbour.ToVector()) + thisNode.G;
float H = Vector2.Distance(neighbour.ToVector(), goalNode.location.ToVector());
float F = G + H;
GameObject pathBlock = Instantiate(pathP,
new Vector3(neighbour.x * maze.scale, 0, neighbour.z * maze.scale), Quaternion.identity);
TextMesh[] values = pathBlock.GetComponentsInChildren<TextMesh>();
values[0].text = "G: " + G.ToString("0.00");
values[1].text = "H: " + H.ToString("0.00");
values[2].text = "F: " + F.ToString("0.00");
if(!UpdateMarker(neighbour, G, H, F, thisNode))
open.Add(new PathMarker(neighbour, G, H, F, pathBlock, thisNode));
}
open = open.OrderBy(p => p.F).ThenBy(n => n.H).ToList<PathMarker>();
PathMarker pm = open.ElementAt(0);
closed.Add(pm);
open.RemoveAt(0);
pm.marker.GetComponent<Renderer>().material = closedMaterial;
lastPos = pm;
}
bool UpdateMarker(MapLocation pos, float g, float h, float f, PathMarker prt)
{
foreach (PathMarker p in open)
{
if (p.location.Equals(pos))
{
p.G = g;
p.H = h;
p.F = f;
p.parent = prt;
return true;
}
}
return false;
}
bool IsClosed(MapLocation marker)
{
foreach (PathMarker p in closed)
{
if (p.location.Equals(marker)) return true;
}
return false;
}
void Start() {
}
void Update() {
if (Input.GetKeyDown(KeyCode.P)) BeginSearch();
if (Input.GetKeyDown(KeyCode.C)) Search(lastPos);
}
private void FixedUpdate()
{
Search(lastPos);
}
}
2. Maze Generation (Maze.cs)
This script sets up the maze grid and provides the foundation for the pathfinding algorithm.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapLocation
{
public int x;
public int z;
public MapLocation(int _x, int _z)
{
x = _x;
z = _z;
}
public Vector2 ToVector()
{
return new Vector2(x, z);
}
public static MapLocation operator +(MapLocation a, MapLocation b)
=> new MapLocation(a.x + b.x, a.z + b.z);
public override bool Equals(object obj)
{
if ((obj == null) || !this.GetType().Equals(obj.GetType()))
return false;
else
return x == ((MapLocation)obj).x && z == ((MapLocation)obj).z;
}
public override int GetHashCode()
{
return 0;
}
}
public class Maze : MonoBehaviour
{
public List<MapLocation> directions = new List<MapLocation>() {
new MapLocation(1,0),
new MapLocation(0,1),
new MapLocation(-1,0),
new MapLocation(0,-1) };
public int width = 30; //x length
public int depth = 30; //z length
public byte[,] map;
public int scale = 6;
// Start is called before the first frame update
void Start()
{
InitialiseMap();
Generate();
DrawMap();
}
void InitialiseMap()
{
map = new byte[width,depth];
for (int z = 0; z < depth; z++)
for (int x = 0; x < width; x++)
{
map[x, z] = 1; //1 = wall 0 = corridor
}
}
public virtual void Generate()
{
for (int z = 0; z < depth; z++)
for (int x = 0; x < width; x++)
{
if(Random.Range(0,100) < 50)
map[x, z] = 0; //1 = wall 0 = corridor
}
}
void DrawMap()
{
for (int z = 0; z < depth; z++)
for (int x = 0; x < width; x++)
{
if (map[x, z] == 1)
{
Vector3 pos = new Vector3(x * scale, 0, z * scale);
GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
wall.transform.localScale = new Vector3(scale, scale, scale);
wall.transform.position = pos;
}
}
}
public int CountSquareNeighbours(int x, int z)
{
int count = 0;
if (x <= 0 || x >= width - 1 || z <= 0 || z >= depth - 1) return 5;
if (map[x - 1, z] == 0) count++;
if (map[x + 1, z] == 0) count++;
if (map[x, z + 1] == 0) count++;
if (map[x, z - 1] == 0) count++;
return count;
}
public int CountDiagonalNeighbours(int x, int z)
{
int count = 0;
if (x <= 0 || x >= width - 1 || z <= 0 || z >= depth - 1) return 5;
if (map[x - 1, z - 1] == 0) count++;
if (map[x + 1, z + 1] == 0) count++;
if (map[x - 1, z + 1] == 0) count++;
if (map[x + 1, z - 1] == 0) count++;
return count;
}
public int CountAllNeighbours(int x, int z)
{
return CountSquareNeighbours(x,z) + CountDiagonalNeighbours(x,z);
}
}
3. Recursive Maze Generation (Recursive.cs)
This script uses a recursive algorithm to generate the maze, providing a method for creating complex and interesting mazes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Recursive : Maze
{
public override void Generate()
{
Generate(5, 5);
}
void Generate(int x, int z)
{
if (CountSquareNeighbours(x, z) >= 2) return;
map[x, z] = 0;
directions.Shuffle();
Generate(x + directions[0].x, z + directions[0].z);
Generate(x + directions[1].x, z + directions[1].z);
Generate(x + directions[2].x, z + directions[2].z);
Generate(x + directions[3].x, z + directions[3].z);
}
}

Leave a Reply