Nodes just outside constant path range are showing in NavMesh but not in constPath.allNodes

Objective:

Game Type: 2D Top down tile based

I’m trying to use ConstantPath in A* Pathfinding Project Pro to make it so I can preview how many tiles my player can move to. After movement of the player or if there was not a lastPath set it runs a ConstantPath to return a ConstantPath with a max range of 4 (just a small range for testing). When it gets the ConstantPath I have sprites spawned in the game at each of the nodes returned by the ConstantPath (e.g. looping through constPath.allNodes). What I am aiming for is that I can click the mouse and have it allow or disallow pathing to the selected point based on if the mouse click is on a node returned by the ConstantPath. Code for my class is at the end.

Problem:

The ConstantPath runs and I can spawn objects (preview “Tiles”) on each of the constant path’s nodes and they appear in game seemingly OK, but in the game window while using Gizmos, it displays the navmesh as having an extra layer of adjacent nodes (E.g. if I’m expecting 4 nodes to the right, i have 4 preview spawned tiles to the right and one more after that displayed by the gizmo showing it is a navmesh). Sometimes when I click in the ‘navmesh’ area displayed in the gizmo (that doest have a preview tile on it) the distance checking I am doing says it is a valid node. The distance checking is looping through the last path returned by the last ran ConstantPath and checking if the distance between each node in the constant path and the mouse click is < 1. This seems OK for everything that is a ‘preview tile’ that was spawned but sometimes it lets you click outside of it just by 1 node.

I feel like I’m probably missing something obvious. Thanks for the help/input.

What my scene looks like with the ‘preview tiles’ around the player and the navmesh gizmo:

Gif of the problem (External link, sorry, i dont know if gifs will upload and if this is against policy, i will redo it): https://imgur.com/a/fxNXr

Screenshot of my AStartPath Inspector:

Screenshot of my “player” inspector:

My class code, Sorry if it doesnt come out formatted but I didnt see a code option in the formatting:

using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using UnityEditor.Graphs;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Networking;

public class PathSelector : NetworkBehaviour
{
    [SerializeField]
    private GameObject highlightTile;

    [SerializeField]
    private GameObject targetHighlightTile;

    [SerializeField]
    private GameObject highlightInvalidTile;

    [SerializeField]
    private LayerMask blockingLayer;

    private PlayerMovement playerMovement;
    private GridLayout gridLayout;

    private Vector3 cellSize;

    private Seeker seeker;

    private EventSystem eventSystem;
    private Vector3Int previousHighlightedPos;
    private Vector3Int previousMousePos;

    private List<GameObject> previewTiles = new List<GameObject>();

    private ConstantPath lastPath;

    void Start ()
    {
        eventSystem = GameObject.Find("EventSystem").GetComponent<EventSystem>();

        playerMovement = gameObject.GetComponent<PlayerMovement>();
        seeker = GetComponent<Seeker>();

        gridLayout = GameObject.Find("Grid").GetComponent<Grid>();

        cellSize = gridLayout.cellSize;

        playerMovement.OnPlayerStopMovement += OnPlayerStopMovement;
        playerMovement.OnPlayerStartMovement += OnPlayerStartMovement;
    }

    void Awake()
    {
        
    }

    void OnDisable()
    {
        playerMovement.OnPlayerStopMovement -= OnPlayerStopMovement;
        playerMovement.OnPlayerStartMovement -= OnPlayerStartMovement;
    }

    #region Event Handlers

    private void OnPlayerStopMovement(Vector3 stopPosition)
    {
        ClearPreview();

        StartCoroutine(PreviewPath(transform.position));
    }

    private void OnPlayerStartMovement(Vector3 startPosition, Vector3 endPosition)
    {
        ClearPreview();
    }

    #endregion
    void FixedUpdate ()
	{
	    if (!isLocalPlayer)
	    {
	        return;
	    }

        Vector3 mouseTarget = Camera.main.ScreenToWorldPoint(Input.mousePosition);

	    // Get cell mouse clicked on
	    Vector3Int mouseGridPos = gridLayout.WorldToCell(mouseTarget);
	    Vector3 mouseGridPosWorld = gridLayout.CellToWorld(mouseGridPos);
	    mouseGridPosWorld += new Vector3(cellSize.x / 2, cellSize.y / 2);

	    if (lastPath == null)
	    {
	        StartCoroutine(PreviewPath(transform.position));
        }

        if (gridLayout 
            && Input.GetMouseButtonDown(0) 
            && !playerMovement.isMoving 
            && !eventSystem.IsPointerOverGameObject()
            && Utils.IsOnScreen(Input.mousePosition))
        {
            Vector3Int playerGridPos = gridLayout.WorldToCell(transform.position);
            Vector3Int targetGridPos = playerGridPos;

            if (playerGridPos != mouseGridPos)
            {
                targetGridPos = mouseGridPos;

                var target = gridLayout.CellToWorld(targetGridPos);
                target += new Vector3(cellSize.x / 2, cellSize.y / 2);

                if (IsPathValid(target))
                {
                    playerMovement.StartMovement(target);
                }
            }
        }
    }

    IEnumerator PreviewPath(Vector3 start)
    {
        int maxMoves = playerMovement.MaxMoves; // This is 4, since i want to be able to move 4 'tiles'
        ConstantPath constPath = ConstantPath.Construct(start, 1000 * maxMoves, null);

        AstarPath.StartPath(constPath);
        lastPath = constPath;

        // Wait for the path to be calculated
        yield return StartCoroutine(constPath.WaitForPath());

        ClearPreview();

        List<GraphNode> nodes = constPath.allNodes;

        for (int i = 0; i < nodes.Count; i++)
        {
            var vector = (Vector3)nodes[i].position;

            SpawnTile(highlightTile, vector);
        }
    }

    void ClearPreview()
    {
        for (int i = 0; i < previewTiles.Count; i++)
        {
            GameObject.Destroy(previewTiles[i]);
        }

        previewTiles.Clear();
    }

    private void SpawnTile(GameObject tile, Vector3 pos)
    {
        var obj = Instantiate(tile, pos, Quaternion.identity);
        
        previewTiles.Add(obj);
    }

    private bool IsPathValid(Vector3 pos)
    {
        if (lastPath != null)
        {
            for (int i = 0; i < lastPath.allNodes.Count; i++)
            {
                var vector = (Vector3)lastPath.allNodes[i].position;

                float distance = Vector3.Distance(pos, vector);
                if (distance < 1.0f)
                {
                    Debug.Log("** Click: " + pos + "; Path: " + vector + "; distance: " + distance);

                    return true;
                }
                else
                {
                    Debug.Log("Click: " + pos + "; Path: " + vector + "; distance: " + distance);
                }
            }
        }

        return false;
    }
}

I think I resolved this. Had to do with the offset for the center of tiles in the tilemap.

    private bool IsPathValid(Vector3 pos)
{
    if (lastPath != null)
    {
        for (int i = 0; i < lastPath.allNodes.Count; i++)
        {
            var vector = (Vector3)lastPath.allNodes[i].position;

            // SOLVED HERE: Probably cleaner ways of handling it though.
            Vector3Int vectorCell = gridLayout.WorldToCell(vector);
            vector = gridLayout.CellToWorld(vectorCell);
            vector += new Vector3(cellSize.x / 2, cellSize.y / 2);

            float distance = Vector3.Distance(vector, pos);
            if (distance < 1.0f)
            {
                return true;
            }
        }
    }

    return false;
}
1 Like

Hi

In case you only want a fixed number of nodes, no costs or anything like that you might want to have a look at PathUtilities.BFS instead which is much simpler to use in your case.

See https://arongranberg.com/astar/docs/pathutilities.html#BFS

Also note that a distance of 1 from a node will reach into an adjacent node. Note that the side of the node is 1. If you want to create a minimal circle that covers the node it should have a radius of sqrt(2)/2*nodeSize or in your case 0.707.