Skip to content

Instantly share code, notes, and snippets.

@DanBrooker
Last active November 21, 2015 23:24
Show Gist options
  • Save DanBrooker/1f8855367ae4add40792 to your computer and use it in GitHub Desktop.
Save DanBrooker/1f8855367ae4add40792 to your computer and use it in GitHub Desktop.
Unity 3D A* pathing, adapted from Redblob http://www.redblobgames.com/pathfinding/a-star/introduction.html
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Collections;
// A* needs only a WeightedGraph and a location type L, and does *not*
// have to be a grid. However, in the example code I am using a grid.
public interface WeightedGraph<L>
{
int Cost(Location a, Location b);
IEnumerable<Location> Neighbors(Location id);
}
public class Location
{
// Implementation notes: I am using the default Equals but it can
// be slow. You'll probably want to override both Equals and
// GetHashCode in a real project.
public readonly int x, y;
public Location(int x, int y)
{
this.x = x;
this.y = y;
}
public Location(Vector3 position)
{
this.x = Mathf.RoundToInt(position.x);
this.y = Mathf.RoundToInt(position.y);
}
public override bool Equals(System.Object obj){
if(obj == null) {
return false;
}
Location location = obj as Location;
if((System.Object)location == null) {
return false;
}
return this.x == location.x && this.y == location.y;
}
public override int GetHashCode() {
return x ^ y;
}
public Vector3 vector3() {
return new Vector3 (this.x, this.y, 0f);
}
}
public class SquareGrid : WeightedGraph<Location>
{
// Implementation notes: I made the fields public for convenience,
// but in a real project you'll probably want to follow standard
// style and make them private.
public static readonly Location[] DIRS = new []
{
new Location(1, 0),
new Location(0, -1),
new Location(-1, 0),
new Location(0, 1)
};
public int x, y, width, height;
public HashSet<Location> floor = new HashSet<Location>();
public HashSet<Location> objects = new HashSet<Location>();
public HashSet<Location> forests = new HashSet<Location>();
public SquareGrid(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public bool InBounds(Location id)
{
return x <= id.x && id.x < width
&& y <= id.y && id.y < height;
}
public bool Passable(Location id)
{
return floor.Contains(id) && !objects.Contains(id);
}
public int Cost(Location a, Location b)
{
return forests.Contains(b) ? 5 : 1;
}
public IEnumerable<Location> Neighbors(Location id)
{
foreach (var dir in DIRS) {
Location next = new Location(id.x + dir.x, id.y + dir.y);
if (InBounds(next) && Passable(next)) {
yield return next;
}
}
}
}
public class Tuple<T1, T2>
{
public T1 Item1 { get; private set; }
public T2 Item2 { get; private set; }
internal Tuple(T1 first, T2 second)
{
Item1 = first;
Item2 = second;
}
}
public static class Tuple
{
public static Tuple<T1, T2> Create<T1, T2>(T1 first, T2 second)
{
var tuple = new Tuple<T1, T2>(first, second);
return tuple;
}
}
public class PriorityQueue<T>
{
// I'm using an unsorted array for this example, but ideally this
// would be a binary heap. Find a binary heap class:
// * https://bitbucket.org/BlueRaja/high-speed-priority-queue-for-c/wiki/Home
// * http://visualstudiomagazine.com/articles/2012/11/01/priority-queues-with-c.aspx
// * http://xfleury.github.io/graphsearch.html
// * http://stackoverflow.com/questions/102398/priority-queue-in-net
private List<Tuple<T, int>> elements = new List<Tuple<T, int>>();
public int Count
{
get { return elements.Count; }
}
public void Enqueue(T item, int priority)
{
elements.Add(Tuple.Create(item, priority));
}
public T Dequeue()
{
int bestIndex = 0;
for (int i = 0; i < elements.Count; i++) {
if (elements[i].Item2 < elements[bestIndex].Item2) {
bestIndex = i;
}
}
T bestItem = elements[bestIndex].Item1;
elements.RemoveAt(bestIndex);
return bestItem;
}
}
public class Pathing
{
public Dictionary<Location, Location> cameFrom
= new Dictionary<Location, Location>();
public Dictionary<Location, int> costSoFar
= new Dictionary<Location, int>();
private Location start;
private Location goal;
// Note: a generic version of A* would abstract over Location and
// also Heuristic
static public int Heuristic(Location a, Location b)
{
return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);
}
public Pathing(WeightedGraph<Location> graph, Location start, Location goal)
{
this.start = start;
this.goal = goal;
var frontier = new PriorityQueue<Location>();
frontier.Enqueue(start, 0);
cameFrom.Add(start, start);
costSoFar.Add(start, 0);
while (frontier.Count > 0)
{
var current = frontier.Dequeue();
if (current.Equals(goal))
{
break;
}
foreach (var next in graph.Neighbors(current))
{
int newCost = costSoFar[current] + graph.Cost(current, next);
if (!costSoFar.ContainsKey(next) || newCost < costSoFar[next])
{
if(costSoFar.ContainsKey(next)) {
cameFrom.Remove(next);
costSoFar.Remove(next);
}
costSoFar.Add(next, newCost);
int priority = newCost + Heuristic(next, goal);
frontier.Enqueue(next, priority);
cameFrom.Add(next, current);
}
}
}
}
public List<Location> WhereTo() {
List<Location> path = new List<Location>();
Location current = goal;
while (!current.Equals(start)) {
if (!cameFrom.ContainsKey(current)) {
return new List<Location>();
}
Location from = cameFrom[current];
path.Add(current);
current = from;
}
path.Reverse();
return path;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment