Comments and answers for "How do I highlight all available paths with Dijkstra's algorithm on a tile based map?"
http://answers.unity.com/questions/1063687/how-do-i-highlight-all-available-paths-with-dijkst.html
The latest comments and answers for the question "How do I highlight all available paths with Dijkstra's algorithm on a tile based map?"Answer by AceVanCleef
http://answers.unity.com/answers/1530099/view.html
I implemented a solution using Breadth First Search (BFS) in order to avoid the following two issues of the previous suggested solution:
- `!validMoves.Contains(startTile.connected[i]` prevented from visiting all valid tiles a unit should be able to move onto.
- After removing `!validMoves.Contains(startTile.connected[i]`, tiles get visitied multiple times and duplicates will be added to `List<Tile> validMoves`.
## BFS Concept ##
Before showing my solution, I recommend watching this video explaining the concept of BFS in a short and simple manner:
[Breadth First Search Algorithm][1]
## Get all tiles in a unit's walking range ##
Units have a certain amount of `movePoints` they can use to walk around. Tiles in the otherhand have topographical features such as grassland, forests and mountains. A forest is more expensive to walk on ( `MovementCost` ) than grassland. A mountain even more thus influencing how far a unit can actually walk.
public List<Node> GetValidMoves(int startPosX, int startPosY, int movePoints)
{
List<Node> validTiles = new List<Node>();
int[,] distance = new int[BoardSizeX, BoardSizeY];
for(int x = 0; x < BoardSizeX; ++x) {
for (int y = 0; y < BoardSizeY; ++y) {
distance[x, y] = Int32.MaxValue;
}
}
distance[startPosX, startPosY] = 0;
Queue<Node> queue = new Queue<Node>();
queue.Enqueue( graph[startPosX, startPosY] );
while (queue.Count > 0) {
Node current = queue.Dequeue();
foreach(Node neighbour in current.Neighbours) {
if( distance[neighbour.x , neighbour.y] > movePoints) {
int movementCost = GetTileTypeAt(neighbour.x , neighbour.y).MovementCost;
distance[neighbour.x , neighbour.y] = movementCost + distance[current.x, current.y];
if (distance[neighbour.x , neighbour.y] <= movePoints) {
queue.Enqueue(neighbour);
}
}
}
if (distance[current.x, current.y] > 0 && distance[current.x, current.y] <= movePoints) {
validTiles.Add(current);
}
}
return validTiles;
}
## Attack Range ##
Here is a variation for getting all tiles that are attackable within a units `maxAttackRange`
public List<Node> GetTilesInAttackRange(int startPosX, int startPosY, int maxAttackRange, int minAttackRange)
{
if (minAttackRange < 0) {
minAttackRange = 0;
Debug.LogError("minAttackRange was below 0 and got corrected to 0");
}
if (minAttackRange >= maxAttackRange) {
throw new System.ArgumentException("MinAttackRange can't be larger than or equal to MaxAttackRange.");
}
List<Node> validTiles = new List<Node>();
int[,] distance = new int[BoardSizeX, BoardSizeY];
for(int x = 0; x < BoardSizeX; ++x) {
for (int y = 0; y < BoardSizeY; ++y) {
distance[x, y] = Int32.MaxValue;
}
}
distance[startPosX, startPosY] = 0;
Queue<Node> queue = new Queue<Node>();
queue.Enqueue( graph[startPosX, startPosY] );
while (queue.Count > 0) {
Node current = queue.Dequeue();
foreach(Node neighbour in current.Neighbours) {
if( distance[neighbour.x , neighbour.y] == Int32.MaxValue) {
distance[neighbour.x , neighbour.y] = 1 + distance[current.x, current.y];
if (distance[neighbour.x, neighbour.y] <= maxAttackRange) {
queue.Enqueue(neighbour);
}
}
}
if (distance[current.x, current.y] > minAttackRange && distance[current.x, current.y] <= maxAttackRange) {
validTiles.Add(current);
}
}
return validTiles;
}
[1]: https://www.youtube.com/watch?v=QRq6p9s8NVgTue, 17 Jul 2018 02:08:11 GMTAceVanCleefComment by AceVanCleef on AceVanCleef's answer
http://answers.unity.com/comments/1530083/view.html
While working on my own tile map based project, I noticed an issue in this suggested solution. "!validMoves.Contains(startTile.connected[i]" prevents the code from aquiring all validMoves. For example, the root tile R has its neighbours A (top), B (right), C (left) and D (bottom/below) around it, the algorithm will first go to A. A's neighbours will be now added. I call them A_1 to the right, A_2 to the top and A_3 to the left (A_4 is R and already included in validTiles).
Sooner or later, A_3's neighbour will be processed. It's neighbour below is actually Tile C. Now because you check "!validMoves.Contains(startTile.connected[i])"), C can no longer be used to check for its own neighbours. The path from R to all its left neighbour is blocked.
I suggest to draw a 5x5 array on paper and emulate my steps I explained. This will help visualizing the issue.
Technically, you can easily fix this by removing "&& !validMoves.Contains(startTile.connected[i]" from the code, but this comes with the downside of adding duplicates to "List"validMoves. I suggest using the Breath first search algorithm.Tue, 17 Jul 2018 00:31:59 GMTAceVanCleefAnswer by cjdev
http://answers.unity.com/answers/1064301/view.html
You're on the right track with the tile objects and the graph of connecting tiles but I have a feeling you're making the function harder than it needs to be. In a case like this where you need to chain through a relationship of child to parent in succession recursive functions are often the way to go. Assuming you have a Tile class that looks like this:
public class Tile {
public int moveCost;
public List<Tile> connected;
}
Then you can implement a recursive function to get the connected Tiles like this:
private List<Tile> validMoves;
void Start()
{
validMoves.Clear();
//Tiles initialized and startTile is the current Tile
GetValidMoves(startTile, 42);
}
private void GetValidMoves(Tile startTile, int movePoints)
{
validMoves.Add(startTile);
for (int i = 0; i < startTile.connected.Count; i++)
{
int nextMoveCost = movePoints - startTile.connected[i].moveCost
if (nextMoveCost >= 0 && !validMoves.Contains(startTile.connected[i]))
GetValidMoves(startTile.connected[i], nextMoveCost);
}
}Wed, 09 Sep 2015 20:07:07 GMTcjdev