

Global Game Jam: Pathfinding
source link: https://matthewminer.com/2021/03/21/global-game-jam-pathfinding
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Matthew Miner
Global Game Jam: Pathfinding
March 21, 2021Along with fog of war, another challenge I tackled for our 2021 Global Game Jam submission was pathfinding.
There are a slew of Unity Asset Store packages that provide pathfinding. Like this one that has been around for eternity. If you have more complex requirements than I did — say, continuous rather than grid-based movement1 — it’s worth your time to evaluate these. A basic A* implementation was all I needed though, so I went the DIY route instead of the drudgery of integrating a third party solution.
A* is a tried and true classic, the kind of algorithm you study in computer science class. The Wikipedia article is a good overview, but give Red Blobs Games’ Introduction to the A* Algorithm a read for an even better explanation.
Here’s my version, a line-by-line translation of the Wikipedia pseudocode:2
List<T> FindShortestPath<T>(
T start,
T goal,
Func<T, int> getCostEstimate,
Func<T, T, int> getEdgeWeight,
Func<T, IEnumerable<T>> getNeighbors) where T : IEquatable<T>
{
// The set of discovered nodes that may need to be (re-)expanded.
// Initially, only the start node is known.
// This is usually implemented as a min-heap or priority queue rather than a hash-set.
var openSet = new HashSet<T> {start};
// For node n, cameFrom[n] is the node immediately preceding it on the cheapest path from start to n currently known.
var cameFrom = new Dictionary<T, T>();
// For node n, gScore[n] is the cost of the cheapest path from start to n currently known.
var gScore = new Dictionary<T, int>();
gScore[start] = 0;
// For node n, fScore[n] = gScore[n] + getCostEstimate(n).
// fScore[n] represents our current best guess as to how short a path from start to finish can be if it goes through n.
var fScore = new Dictionary<T, int>();
fScore[start] = getCostEstimate(start);
while (openSet.Count > 0)
{
// Find the node in openSet with the lowest fScore value.
// This operation can occur in O(1) time if openSet is a min-heap or a priority queue.
var current = openSet
.OrderBy(position => fScore.TryGetValue(position, out var fValue) ? fValue : int.MaxValue)
.First();
if (current.Equals(goal))
{
return ReconstructPath(cameFrom, current);
}
openSet.Remove(current);
var neighbors = getNeighbors(current);
foreach (var neighbor in neighbors)
{
// tentativeGScore is the distance from start to the neighbor through current.
var tentativeGScore = gScore[current]+ getEdgeWeight(current, neighbor);
var neighborGScore = gScore.TryGetValue(neighbor, out var gValue) ? gValue : int.MaxValue;
if (tentativeGScore < neighborGScore)
{
// This path to neighbor is better than any previous one. Record it!
cameFrom[neighbor] = current;
gScore[neighbor] = tentativeGScore;
fScore[neighbor] = gScore[neighbor] + getCostEstimate(neighbor);
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
}
// Open set is empty but goal was never reached.
return null;
}
List<T> ReconstructPath<T>(Dictionary<T, T> cameFrom, T current)
{
var totalPath = new List<T> {current};
while (cameFrom.ContainsKey(current))
{
current = cameFrom[current];
totalPath.Insert(0, current);
}
return totalPath;
}
To use it you pass five arguments. start
is where you begin and goal
is where you want to end up. getEdgeWeight
determines how expensive it is to travel from a node to its neighbour. Our game only had one ground tile type so we returned a constant of 1
, but you might want to make some terrain (e.g. mud) slower to traverse.
getNeighbours
is self-explanatory: given a node, return a list of nodes adjacent to it. getCostEstimate
is a heuristic function that returns how expensive it is to travel from a given node to the goal. For a grid, this can simply be horizontal + vertical distance. Our calling function wound up looking like this:
List<Vector3Int> GetPathFromEnemyToPlayer(Transform enemy, Transform player)
{
var start = Vector3Int.RoundToInt(enemy.position);
var goal = Vector3Int.RoundToInt(player.position);
int getCostEstimate(Vector3Int position) => Mathf.Abs(goal.x - position.x) + Mathf.Abs(goal.z - position.z);
int getEdgeWeight(Vector3Int current, Vector3Int neighbour) => 1;
List<Vector3Int> getNeighbors(Vector3Int current) => level.GetAdjacentPositions(current);
return FindShortestPath(start, goal, getCostEstimate, getEdgeWeight, getNeighbors);
}
If this would be helpful in your project, please take it and use it friend. Sharing is caring.
If your game lends itself to grid-based movement, praise be. It frees you from so many fiddly edge cases. Pathfinding is no exception. ↩
Recommend
-
95
Maze generator and solver Python scripts for generating random solvable mazes using the depth-first search and recursive backtracking algorithms. The code also implements a recursive backtracking pathfinding algorithm for solving the gene...
-
50
reddit: the front page of the internet
-
56
In a Tower Defense game, there are many enemies that are all headed to the same place. In many Tower Defense games, there is a predetermined path, or a small number of paths. In some, such as the classic
-
37
ByJoe Depeau, Senior Pre-Sales Consultant, Neo4j | May 2, 2019 Reading time: 6 m...
-
16
Introduction Pathfinding is one of these topics that usualy baffles game developers. The A* algorithm in particular is poorly understood, and the general belief seems to be that it’s arcane ma...
-
17
Making a game where your enemies need to chase the player? This starts out easy, make the enemy run towards the player! But what happens when they are behind a tree? Or around the corner of a wall? Well, now your enemy...
-
12
Matthew Miner Global Game Jam 2017 January 31, 2017 Here’s a game I made for this year’s Global Game Jam. For...
-
7
Matthew Miner Global Game Jam: Fog of War February 21, 2021 One challenge I tackled for our
-
9
Matthew Miner Global Game Jam 2021 February 7, 2021 One diversifier for this year’s Global Game Jam was “on th...
-
9
We will look at the single source shortest path problem using Bellman Ford Algorithm, but first let’s understand the concept of Dynamic programming. DYNAMIC PROGRAMMING Dynamic programming in computer science refers t...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK