A*, Tile costs and heuristic; How to approach
- by Kevin Toet
I'm doing exercises in tile games and AI to improve my programming. I've written a highly unoptimised pathfinder that does the trick and a simple tile class.
The first problem i ran into was that the heuristic was rounded to int's which resulted in very straight paths. Resorting a Euclidian Heuristic seemed to fixed it as opposed to use the Manhattan approach.
The 2nd problem I ran into was when i tried added tile costs. I was hoping to use the value's of the flags that i set on the tiles but the value's were too small to make the pathfinder consider them a huge obstacle so i increased their value's but that breaks the flags a certain way and no paths were found anymore.
So my questions, before posting the code, are:
What am I doing wrong that the Manhatten heuristic isnt working?
What ways can I store the tile costs? I was hoping to (ab)use the enum flags for this
The path finder isnt considering the chance that no path is available, how do i check this?
Any code optimisations are welcome as I'd love to improve my coding.
public static List<Tile> FindPath( Tile startTile, Tile endTile, Tile[,] map )
{
return FindPath( startTile, endTile, map, TileFlags.WALKABLE );
}
public static List<Tile> FindPath( Tile startTile, Tile endTile, Tile[,] map, TileFlags acceptedFlags )
{
List<Tile> open = new List<Tile>();
List<Tile> closed = new List<Tile>();
open.Add( startTile );
Tile tileToCheck;
do
{
tileToCheck = open[0];
closed.Add( tileToCheck );
open.Remove( tileToCheck );
for( int i = 0; i < tileToCheck.neighbors.Count; i++ )
{
Tile tile = tileToCheck.neighbors[ i ];
//has the node been processed
if( !closed.Contains( tile ) && ( tile.flags & acceptedFlags ) != 0 )
{
//Not in the open list?
if( !open.Contains( tile ) )
{
//Set G
int G = 10;
G += tileToCheck.G;
//Set Parent
tile.parentX = tileToCheck.x;
tile.parentY = tileToCheck.y;
tile.G = G;
//tile.H = Math.Abs(endTile.x - tile.x ) + Math.Abs( endTile.y - tile.y ) * 10;
//TODO omg wtf and other incredible stories
tile.H = Vector2.Distance( new Vector2( tile.x, tile.y ), new Vector2(endTile.x, endTile.y) );
tile.Cost = tile.G + tile.H + (int)tile.flags; //Calculate H; Manhattan style
open.Add( tile );
}
//Update the cost if it is
else
{
int G = 10;//cost of going to non-diagonal tiles
G += map[ tile.parentX, tile.parentY ].G;
//If this path is shorter (G cost is lower) then change
//the parent cell, G cost and F cost.
if ( G < tile.G ) //if G cost is less,
{
tile.parentX = tileToCheck.x; //change the square's parent
tile.parentY = tileToCheck.y;
tile.G = G;//change the G cost
tile.Cost = tile.G + tile.H + (int)tile.flags; // add terrain cost
}
}
}
}
//Sort costs
open = open.OrderBy( o => o.Cost).ToList();
}
while( tileToCheck != endTile );
closed.Reverse();
List<Tile> validRoute = new List<Tile>();
Tile currentTile = closed[ 0 ];
validRoute.Add( currentTile );
do
{
//Look up the parent of the current cell.
currentTile = map[ currentTile.parentX, currentTile.parentY ];
currentTile.renderer.material.color = Color.green;
//Add tile to list
validRoute.Add( currentTile );
}
while ( currentTile != startTile );
validRoute.Reverse();
return validRoute;
}
And my Tile class:
[Flags]
public enum TileFlags: int
{
NONE = 0,
DIRT = 1,
STONE = 2,
WATER = 4,
BUILDING = 8,
//handy
WALKABLE = DIRT | STONE | NONE,
endofenum
}
public class Tile : MonoBehaviour
{
//Tile Properties
public int x, y;
public TileFlags flags = TileFlags.DIRT;
public Transform cachedTransform;
//A* properties
public int parentX, parentY;
public int G;
public float Cost;
public float H;
public List<Tile> neighbors = new List<Tile>();
void Awake()
{
cachedTransform = transform;
}
}