Search code examples
c#unity-game-enginepath-finding

A*Pathfinding generating wrong path


I'm building a maze game where there's an enemy ready to pursuit the player if the player gets in his range, I'm using A*Pathfinding algorithm to do so, and I build the maze in a procedural way, where each block in the maze has a MapLocation with x and z position, so the start position is the enemy pos and goal is the player position, but sometimes, when the player gets in the range of the enemy,the pathfing ignores some blocks of the maze to get to the player

IEnumerator Search(Vector3 start, Vector3 goal)
        {
            pathSuccess = false;
            waypoints = new Vector3[0];
            Heap<Path> open = new Heap<Path>(maze.MaxSize);
            HashSet<Path> closed = new HashSet<Path>();

            Path startNode = new Path(maze.GetMapLocation(start), 0, 0, 0, null);
            Path goalNode = new Path(maze.GetMapLocation(goal), 0, 0, 0, null);

            open.Add(startNode);
            lastPos = startNode;

            while (open.Count > 0)
            {
                if (lastPos.Equals(goalNode))
                {
                    pathSuccess = true;
                    break;
                }
                foreach (MapLocation dir in maze.directions)
                {
                    MapLocation neighbour = dir + lastPos.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, closed)) continue;

                    float G = Vector2.Distance(lastPos.location.ToVector(), neighbour.ToVector()) + lastPos.G;
                    float H = Vector2.Distance(neighbour.ToVector(), goalNode.location.ToVector());
                    float F = G + H;

                    if (!UpdateMarker(neighbour, G, H, F, lastPos, open))
                        open.Add(new Path(neighbour, G, H, F, lastPos));
                }
                Path pm = open.RemoveFirst();
                closed.Add(pm);

                lastPos = pm;
            }
            yield return null;
            if (pathSuccess)
            {
                waypoints = RetracePath(startNode);
            }
            PathRequestManager.Instance.FinishedProcessingPath(waypoints, pathSuccess);
        }

        bool UpdateMarker(MapLocation pos, float g, float h, float f, Path prt, Heap<Path> open)
        {
            foreach (Path 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, HashSet<Path> closed)
        {
            foreach (Path p in closed)
            {
                if (p.location.Equals(marker)) return true;
            }
            return false;
        }

        Vector3[] RetracePath(Path startNode)
        {
            Path begin = lastPos;
            List<Vector3> path = new List<Vector3>();
            while (!startNode.Equals(begin) && begin != null)
            {
                path.Add(begin.Position(maze));
                begin = begin.parent;
            }
            path.Reverse();
            return path.ToArray();
        }

public MapLocation GetMapLocation(Vector3 position)
        {
            MapLocation mapLocation = new MapLocation((int)position.x / scale, (int)position.z / scale);
            return mapLocation;
        }
public List<MapLocation> directions = new List<MapLocation>() {
                                            new MapLocation(1,0),
                                            new MapLocation(0,1),
                                            new MapLocation(-1,0),
                                            new MapLocation(0,-1) };

The maze is generated like this:

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);
        }

    }



public byte[,] map;
 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
                }
        }

I already tried using Manhattan's Distance and it didn't make any difference:

float H = Mathf.Abs(neighbour.x - goalNode.location.x) + Mathf.Abs(neighbour.z - goalNode.location.z); 

Solution

  • Debug the values you getting when you divide by scale in GetMapLocation() it could be skipping cells because of scaling/rounding?

    I would have a map[x,y] array, then multiple that by scale to get it right in pixels on the screen, but do the pathfinding on the raw unscaled Map[x,y] array. IE separate logic from rendering.