Seeker won't find path in point graph

Hello,

I’m trying to implement pathfinding for my 2d platformer game. I didn’t want to create a graph for each map manually, so I created a script that creates nodes when running a level. Then I use the “StartPath” method on Seeker to find a path. As a start point I use the position of the NPC and as an end point the place where I click with the mouse. However, the method returns a path of length 1 with two vectors. Both vectors have the same position, the position of the node that is closest to the start point.

PlatformPathfinder.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
using Pathfinding;

public class PlatformPathfinder : MonoBehaviour
{
    public List<Vector2> actions;

    [SerializeField] private GameObject solid;
    [SerializeField] private GameObject ladders;
    [SerializeField] private GameObject pointGraph;
    [SerializeField] private GameObject nodeObject;
    
    private LayerMask groundMask;
    private Seeker seeker;
    private Tilemap solidTilemap;
    private Tilemap laddersTilemap;
    private List<Vector2> solidTilePositions;
    private List<GraphNode> twoWayConnectionPoints;
    private List<GraphNode> oneWayConnectionPoints;

    private int jumpHeight = 2;
    private int jumpDistance = 3;

    void Awake()
    {
        actions = new List<Vector2>();
        seeker = GetComponent<Seeker>();
        solidTilePositions = new List<Vector2>();
        solidTilemap = solid.GetComponent<Tilemap>();
        laddersTilemap = ladders.GetComponent<Tilemap>();
        groundMask = LayerMask.GetMask("Ground");
        BoundsInt solidBounds = solidTilemap.cellBounds;
        TileBase[] solidTiles = solidTilemap.GetTilesBlock(solidBounds);
        BoundsInt laddersBounds = laddersTilemap.cellBounds;
        TileBase[] laddersTiles = laddersTilemap.GetTilesBlock(laddersBounds);
        for (int y = 0; y < solidBounds.yMax - solidBounds.y; y++)
        {
            for (int x = 0; x < solidBounds.xMax - solidBounds.x; x++)
            {
                TileBase tile = solidTiles[x + y * (solidBounds.xMax - solidBounds.x)];
                if (tile != null)
                {
                    solidTilePositions.Add(new Vector2(x + solidBounds.x, y + solidBounds.y));
                }
            }
        }

        for (int y = 0; y < laddersBounds.yMax - laddersBounds.y; y++)
        {
            for (int x = 0; x < laddersBounds.xMax - laddersBounds.x; x++)
            {
                TileBase tile = laddersTiles[x + y * (laddersBounds.xMax - laddersBounds.x)];
                if (tile != null)
                {
                    Vector3 tilePosition = new Vector3(x + laddersBounds.x + 0.5f, y + laddersBounds.y + 0.5f, 0);
                    Instantiate(nodeObject, tilePosition, Quaternion.identity, pointGraph.transform);
                }
            }
        }

        foreach (var solidTilePosition in solidTilePositions)
        {
            Vector2 type = GetCellType(solidTilePosition);
            if (type != new Vector2(0, 0))
            {
                CreatePoint(solidTilePosition);
                if (type.x == -1)
                {
                    Vector2 position = new Vector2(solidTilePosition.x - 1, solidTilePosition.y);
                    Vector2 rayPosition = new Vector2(solidTilePosition.x - 0.5f, solidTilePosition.y + 0.5f);
                    Vector2 direction = Vector2.down;
                    RaycastHit2D result = Physics2D.Raycast(rayPosition, direction, 100, groundMask);
                    if (result)
                        CreatePoint(new Vector2(position.x, result.point.y), true);
                }

                if (type.y == -1)
                {
                    Vector2 position = new Vector2(solidTilePosition.x + 1, solidTilePosition.y);
                    Vector2 rayPosition = new Vector2(solidTilePosition.x + 1.5f, solidTilePosition.y + 0.5f);
                    Vector2 direction = Vector2.down;
                    RaycastHit2D result = Physics2D.Raycast(rayPosition, direction, 100, groundMask);
                    if (result)
                        CreatePoint(new Vector2(position.x, result.point.y), true);
                }
            }
        }
        AstarPath.active.Scan();
    }

    // Start is called before the first frame update
    void Start()
    {
        for (int i = 0; i < pointGraph.transform.childCount; i++)
        {
            int closestRightIndex = -1;
            int closestLeftDropIndex = -1;
            int closestRightDropIndex = -1;
            Transform closestRightNodeTransform = null;
            Transform closestLeftDropNodeTransform = null;
            Transform closestRightDropNodeTransform = null;
            Transform nodeTransform = pointGraph.transform.GetChild(i);

            oneWayConnectionPoints = new List<GraphNode>();
            twoWayConnectionPoints = new List<GraphNode>();
            Vector2 nodeType = GetCellType(nodeTransform.position, true, true);
            for (int j = 0; j < pointGraph.transform.childCount; j++)
            {
                Transform nodeTransform2 = pointGraph.transform.GetChild(j);
                if (nodeType.y == 0 && nodeTransform2.position.y == nodeTransform.position.y && nodeTransform2.position.x > nodeTransform.position.x)
                {
                    if (closestRightIndex < 0 || nodeTransform2.position.x < pointGraph.transform.GetChild(closestRightIndex).position.x)
                    {
                        closestRightIndex = j;
                        closestRightNodeTransform = nodeTransform2;
                    }
                }

                if (nodeType.x == -1)
                {
                    if (nodeTransform2.position.x == nodeTransform.position.x - 1 && nodeTransform2.position.y < nodeTransform.position.y)
                    {
                        if (closestLeftDropIndex < 0 || nodeTransform2.position.y > pointGraph.transform.GetChild(closestLeftDropIndex).position.y)
                        {
                            closestLeftDropIndex = j;
                            closestLeftDropNodeTransform = nodeTransform2;
                        }
                    }

                    if (nodeTransform2.position.y >= nodeTransform.position.y - jumpHeight &&
                        nodeTransform2.position.y <= nodeTransform.position.y &&
                        nodeTransform2.position.x > nodeTransform.position.x - (jumpDistance + 2) &&
                        nodeTransform2.position.x < nodeTransform.position.x &&
                        GetCellType(nodeTransform2.position, true, true).y == -1)
                    {
                        GraphNode node2 = AstarPath.active.GetNearest(nodeTransform2.position).node;
                        twoWayConnectionPoints.Add(node2);
                    }
                }

                if (nodeType.y == -1)
                {
                    if (nodeTransform2.position.x == nodeTransform.position.x + 1 && nodeTransform2.position.y < nodeTransform.position.y)
                    {
                        if (closestRightDropIndex < 0 || nodeTransform2.position.y > pointGraph.transform.GetChild(closestRightDropIndex).position.y)
                        {
                            closestRightDropIndex = j;
                            closestRightDropNodeTransform = nodeTransform2;
                        }
                    }
                }
            }

            GraphNode node = AstarPath.active.GetNearest(nodeTransform.position).node;
            if (closestRightNodeTransform != null)
            {
                GraphNode node2 = AstarPath.active.GetNearest(closestRightNodeTransform.position).node;
                twoWayConnectionPoints.Add(node2);
            }

            if (closestLeftDropNodeTransform != null)
            {
                GraphNode node2 = AstarPath.active.GetNearest(closestLeftDropNodeTransform.position).node;
                if (node2.position.y <= node.position.y + jumpHeight)
                    twoWayConnectionPoints.Add(node2);
                else
                    oneWayConnectionPoints.Add(node2);
            }

            if (closestRightDropNodeTransform != null)
            {
                GraphNode node2 = AstarPath.active.GetNearest(closestRightDropNodeTransform.position).node;
                if (node2.position.y <= node.position.y + jumpHeight)
                    twoWayConnectionPoints.Add(node2);
                else
                    oneWayConnectionPoints.Add(node2);
            }

            foreach (GraphNode point in twoWayConnectionPoints)
            {
                node.AddConnection(point, 10);
                point.AddConnection(node, 10);
            }

            foreach (GraphNode point in oneWayConnectionPoints)
                node.AddConnection(point, 10);
        }
    }

    public IEnumerator FindPath(Vector2 start, Vector2 end)
    {
        Path path = seeker.StartPath(start, end, OnPathComplete);
        yield return StartCoroutine(path.WaitForPath());
    }

    private void OnPathComplete(Path path)
    {
        if (path.error)
        {
            Debug.Log("No path found.");
        }
        else
        {
            actions.Clear();
            List<Vector3> pathVectors3 = path.vectorPath;
            foreach (var vector in pathVectors3)
            {
                actions.Add(vector);
            }
        }
    }

    private Vector2 GetCellType(Vector2 position, bool global = false, bool isAbove = false)
    {
        if (global)
            position = new Vector2(position.x - 0.5f, position.y - 0.5f);
        if (isAbove)
            position = new Vector2(position.x, position.y - 1);

        Vector2 result = new Vector2(0, 0);
        if (solidTilePositions.Contains(new Vector2(position.x, position.y + 1))) 
            return result;

        if (solidTilePositions.Contains(new Vector2(position.x - 1, position.y + 1)))
            result += new Vector2(1, 0);
        else if (!solidTilePositions.Contains(new Vector2(position.x - 1, position.y)))
            result += new Vector2(-1, 0);

        if (solidTilePositions.Contains(new Vector2(position.x + 1, position.y + 1)))
            result += new Vector2(0, 1);
        else if (!solidTilePositions.Contains(new Vector2(position.x + 1, position.y)))
            result += new Vector2(0, -1);

        return result;
    }

    private void CreatePoint(Vector2 tilePosition, bool isAbove = false)
    {
        if (isAbove)
            tilePosition.y -= 1;

        Vector3 above = new Vector3(tilePosition.x + 0.5f, tilePosition.y + 1.5f, 0);
        for (int i = 0; i < pointGraph.transform.childCount; i++)
            if (pointGraph.transform.GetChild(i).position == above)
                return;
        if (tilePosition.y % 1 == 0)
            Instantiate(nodeObject, above, Quaternion.identity, pointGraph.transform);
    }
}

EnemyAI.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class EnemyAI : MonoBehaviour
{
    [SerializeField] private PlatformPathfinder pathfinder;
    
    private Character character;
    private LayerMask groundMask;
    private List<Vector2> currentPath;
    private Vector2 currentTarget;
    private Vector2 endPoint;

    private readonly Vector2 nullVector2 = new Vector2(-9999, -9999);

    // Start is called before the first frame update
    void Start()
    {
        character = GetComponent<Character>();
        currentTarget = nullVector2;
        endPoint = nullVector2;
        currentPath = new List<Vector2>();
        groundMask = LayerMask.GetMask("Ground");
    }

    // Update is called once per frame
    void Update()
    {
        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue());
            RaycastHit2D result = Physics2D.Raycast(mousePosition, Vector2.down,100, groundMask);
            Debug.DrawRay(mousePosition, Vector3.down * 100, Color.magenta, 10);
            if (result)
            {
                endPoint = result.point;
                StartCoroutine(pathfinder.FindPath(transform.position, endPoint));
                currentPath = pathfinder.actions;
                NextTarget();
            }
            else
            {
                endPoint = nullVector2;
            }
        }

        if (currentTarget != nullVector2)
        {
            if (currentTarget.x - 0.1 > transform.position.x)
                character.SetMoveDirection(1);
            else if (currentTarget.x + 0.1 < transform.position.x)
                character.SetMoveDirection(-1);
            else
                character.SetMoveDirection(0);

            if (Vector2.Distance(transform.position, currentTarget) < 1)
            {
                NextTarget();
            }
        }
        else
        {
            NextTarget();

            character.SetMoveDirection(0);
        }
    }

    void NextTarget()
    {
        if (currentPath.Count < 2)
        {
            currentTarget = nullVector2;
            return;
        }

        currentTarget = currentPath[1];
        currentPath.RemoveAt(0);
    }
}

Edit:
Added video.

https://drive.google.com/file/d/1XwXFoZo-uWQjTkitOuvobQW5AtgmnHTG/view?usp=sharing

Okay, I solved the problem myself. Since I was adding connections in the “Start” method, I thought I didn’t have to add them in the “AddWorkItem” method. My thinking turned out to be wrong.

Here is the part that was wrong.

foreach (GraphNode point in twoWayConnectionPoints)
{
      node.AddConnection(point, 10);
      point.AddConnection(node, 10);
}

foreach (GraphNode point in oneWayConnectionPoints)
      node.AddConnection(point, 10);

And here is correction.

foreach (GraphNode point in twoWayConnectionPoints)
{
     AstarPath.active.AddWorkItem(new AstarWorkItem(ctx =>
     {
             node.AddConnection(point, 10);
             point.AddConnection(node, 10);
     }));
}
        
foreach (GraphNode point in oneWayConnectionPoints) 
      AstarPath.active.AddWorkItem(new AstarWorkItem(ctx =>
      {
             node.AddConnection(point, 10);
      }));
1 Like

Hi

Great that you solved it!
For better performance, you can use a single AddWorkItem call that wraps all your code, instead of doing individual work items for each tiny update.