Search code examples
c#unity-game-enginepath-findinga-star

Specific A* Pathfinding Issue with Implementation


I'm having some issues with my A* Implementation. It occasionally decides to do strange things on my grid, like ignoring movement costs and moving thru a high cost area, or going a tile in the wrong direction before getting back on track.

I've officially spent far too many hours on it then I'd like to admit so I'm reaching out to find a pair of fresh eyes.

private List<Vector2> PathFromTo(Vector2 startID, Vector2 targetID){
    List<Vector2> path = new List<Vector2> ();
    List<Node> closedList = new List<Node> ();
    List<Node> openList = new List<Node> ();
    Node startNode = nodeList.Find (tgt => tgt.nodeID == new Vector2 (startID.x, startID.y));
    if (startNode == null)
        return path;
    Node targetNode = nodeList.Find (tgt => tgt.nodeID == new Vector2 (targetID.x, targetID.y));
    if (targetNode == null)
        return path;

    openList.Add (startNode);

    while (openList.Count > 0) {
        Node current = openList [0];
        for (int i = 1; i < openList.Count; i++) 
            if (openList [i].GetFCost () <= current.GetFCost () && openList [i].GetHCost () < current.GetHCost ()) 
                current = openList [i];

        openList.Remove (current);
        closedList.Add (current);

        if (current == targetNode) {
            RetracePath (startNode, targetNode, ref path);
            return path;
        }

        foreach(Vector2 neighbour in current.neighbors) {
            Node neighbourNode = nodeList.Find (tgt => tgt.nodeID == new Vector2 (neighbour.x, neighbour.y));
            CheckNeighbor(ref neighbourNode, ref current, ref targetNode, ref closedList, ref openList);
        }
    }
    return path;
}

private void CheckNeighbor(ref Node neighborTile, ref Node currentTile, ref Node targetTile, ref List<Node> closedList, ref List<Node> openList){
    if (neighborTile != null) {
        if (!neighborTile.passable || closedList.Contains (neighborTile)) {
        } else {
            int newCostToNeighbor = (int)(currentTile.moveCost + CalculateDistance (currentTile.position, neighborTile.position));
            if (newCostToNeighbor < neighborTile.GetGCost() || !openList.Contains (neighborTile)) {
                neighborTile.SetGCost (newCostToNeighbor);
                neighborTile.SetHCost (CalculateDistance (neighborTile.position, targetTile.position));
                neighborTile.SetParent (currentTile);

                if (!openList.Contains (neighborTile)) 
                    openList.Add (neighborTile);
            }
        }
    }
}

public float CalculateDistance(Vector2 tileA_pos, Vector2 tileB_pos){ 
    float dX = Mathf.Abs (tileB_pos.x - tileA_pos.x); 
    float dY = Mathf.Abs (tileB_pos.y - tileA_pos.y); 
    float shift1 = -(tileA_pos.x + tileA_pos.y); 
    float shift2 = -(tileB_pos.x + tileB_pos.y); 
    float dZ = Mathf.Abs (shift2 - shift1); 
    return Mathf.Max (dX, dY, dZ); 
}

private void RetracePath(Node start, Node end, ref List<Vector2> pathInfo){
    pathInfo = new List<Vector2> ();
    Node current = end;
    while (current != start) {
        pathInfo.Add (current.nodeID);
        current = current.GetParent ();
    }
    pathInfo.Reverse ();
}

Solution

  • After far too many hours (And a full night's sleep) I was able to figure it out. The issue had to do with the CheckNeighbor Function. The new method looks like this:

    private void CheckNeighbor(ref Node neighborTile, ref Node currentTile, ref Node targetTile, ref List<Node> closedList, ref List<Node> openList, bool ignoreMoveCost = false){
        if (neighborTile != null) {
            if (!neighborTile.passable || closedList.Contains (neighborTile)) {
            } else {
                int newCostToNeighbor = (int)((ignoreMoveCost ? 1 : neighborTile.moveCost) + currentTile.GetGCost() + CalculateDistance (currentTile.position, neighborTile.position));
                if (!openList.Contains (neighborTile)) {
                    openList.Add (neighborTile);
                } else if (newCostToNeighbor >= neighborTile.GetGCost ()) {
                    return;
                }
                neighborTile.SetParent (currentTile);
                neighborTile.SetGCost (newCostToNeighbor);
                neighborTile.SetHCost (CalculateDistance (currentTile.position, neighborTile.position));
            }
        }
    }