Limit movement between nodes with obstacle in between them?

I’m pretty new to this asset so I’m probably missing something simple, but how do you prevent a grid graph from generating nodes that have an obstacle in between them? You can see here how the nodes on the vertical walls are marked as non-walkable because the colliders occupy the center of the nodes, but the horizontal wall’s colliders exist between the nodes. I want to maintain a node size of 1, and keep all NPCs constrained to the grid. I also don’t want to exclude the nodes above or below the wall by expanding the collider.

I’ve tried writing a script that uses raycasts to remove connections between nodes with obstacle colliders between them, but I’m still a baby programmer and not sure why it isn’t working…or if there’s a less resource intensive option I haven’t figured out yet.


I’ve updated the code and it’s correctly identifying obstacles in world space, getting the nearest nodes, and removing connections between them. At least that’s what the console is telling me. It still isn’t actually removing those connections and I don’t know why. I’m posting the code this time for reproducibility.

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

public class FindThinObstacles : MonoBehaviour
{
    public LayerMask obstacleLayer;

    private void Start()
    {
        AstarPath.active.AddWorkItem(ProcessObstacles);
    }

    private void ProcessObstacles(Pathfinding.IWorkItemContext context)
    {
        GridGraph graph = AstarPath.active.data.gridGraph;

        List<KeyValuePair<Vector3, Vector3>> connectionsToRemove = new List<KeyValuePair<Vector3, Vector3>>();

        foreach (GraphNode node in graph.nodes)
        {
            GridNode gridNode = node as GridNode;

            gridNode.GetConnections(neighbor => {
                GridNode gridNeighbor = neighbor as GridNode;

                // Convert grid node positions to world space
                Vector3 nodePosition = (Vector3)gridNode.position;
                Vector3 neighborPosition = (Vector3)gridNeighbor.position;

                // Check for obstacles between node and neighbor
                if (HasObstacleBetween(nodePosition, neighborPosition))
                {
                    connectionsToRemove.Add(new KeyValuePair<Vector3, Vector3>(nodePosition, neighborPosition));
                }
            });
        }

        foreach (var connection in connectionsToRemove)
        {
            Vector3 nodePosition = connection.Key;
            Vector3 neighborPosition = connection.Value;

            // Find corresponding grid nodes
            GridNode node = graph.GetNearest(nodePosition).node as GridNode;
            GridNode neighbor = graph.GetNearest(neighborPosition).node as GridNode;

            if (node != null && neighbor != null)
            {
                node.RemoveConnection(neighbor);
                neighbor.RemoveConnection(node);

                // No need to recalculate connections here as removing connections already updates the grid graph

                Debug.Log($"Removed connection between nodes at {nodePosition} and {neighborPosition}");
            }
        }
    }

    private bool HasObstacleBetween(Vector3 from, Vector3 to)
    {
        Vector2 from2D = new Vector2(from.x, from.y);
        Vector2 to2D = new Vector2(to.x, to.y);

        Vector2 direction2D = to2D - from2D;
        float distance2D = direction2D.magnitude;

        RaycastHit2D hit = Physics2D.Raycast(from2D, direction2D, distance2D, obstacleLayer);

        Debug.DrawRay(from2D, direction2D, Color.red, 2.0f);

        if (hit.collider != null)
        {
            Debug.Log($"Obstacle detected between {from} and {to}");
            return true;
        }

        return false;
    }
}

Hi

In the currently released version, RemoveConnection will not work for grid-connections. They are stored in a separate way and required SetConnectionInternal to update them.
In the beta version, RemoveConnection will automatically work with grid connections too, though.

You may also be interested in Writing Custom Grid Graph Rules - A* Pathfinding Project (but this is a bit more complex, you may want to stick to your current approach for simplicity).

Thank you!

Custom grid graph rules seem powerful but complex, like you said. SetConnectionInternal was just what I needed. I’ll paste my finished code here in case anyone else is looking for solutions to the problem of thin walls between grid nodes…but with a disclaimer - I’m still a novice programmer so my code might not be the most optimized solution.

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

public class FindThinObstacles : MonoBehaviour
{
    public LayerMask obstacleLayer;

    private HashSet<GridNode> processedNodes = new HashSet<GridNode>();

    private void Start()
    {
        AstarPath.active.AddWorkItem(ProcessObstacles);
    }

    private void ProcessObstacles(Pathfinding.IWorkItemContext context)
    {
        GridGraph graph = AstarPath.active.data.gridGraph;

        foreach (GraphNode node in graph.nodes)
        {
            GridNode gridNode = node as GridNode;

            if (processedNodes.Contains(gridNode))
            {
                continue; // Skip this node and move to the next one
            }

            gridNode.GetConnections(neighbor => {
                GridNode gridNeighbor = neighbor as GridNode;

                Vector3 nodePosition = (Vector3)gridNode.position;
                Vector3 neighborPosition = (Vector3)gridNeighbor.position;

                if (HasObstacleBetween(nodePosition, neighborPosition))
                {
                    int direction = GetDirection(nodePosition, neighborPosition);

                    // Remove connection from node to neighbor in the direction
                    gridNode.SetConnectionInternal(direction, false);

                    // Calculate opposite direction
                    int oppositeDirection = (direction + 2) % 4;  // Opposite direction is 2 positions away

                    // Remove connection from neighbor to node in the opposite direction
                    gridNeighbor.SetConnectionInternal(oppositeDirection, false);

                    DrawDebugLine(nodePosition, neighborPosition, Color.yellow, 3.0f);
                    Debug.Log($"Removed connection between nodes at {nodePosition} and {neighborPosition}");

                    // Mark nodes as processed to prevent re-detecting obstacles
                    processedNodes.Add(gridNode);
                    processedNodes.Add(gridNeighbor);
                }
            });
        }
    }

    private bool HasObstacleBetween(Vector3 from, Vector3 to)
    {
        RaycastHit2D hit = Physics2D.Linecast(from, to, obstacleLayer);

        Debug.DrawLine(from, to, Color.red, 2.0f);

        if (hit.collider != null)
        {
            Debug.Log($"Obstacle detected between {from} and {to}");
            return true;
        }

        return false;
    }

    private int GetDirection(Vector3 from, Vector3 to)
    {
        Vector3 dir = (to - from).normalized;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
        if (angle < 0) angle += 360;

        // Convert angle to direction and subtract 1 to correct the mapping
        int direction = Mathf.RoundToInt(angle / 90) + 1;
        if (direction < 0) direction += 4; // Ensure the direction is non-negative

        // Adjust directions for the X-axis case
        if (Mathf.Approximately(dir.x, 0))
        {
            if (dir.y > 0)
                direction = 2; // Up
            else
                direction = 0; // Down
        }

        return direction;
    }

    private void DrawDebugLine(Vector3 start, Vector3 end, Color color, float duration)
    {
        Debug.DrawLine(start, end, color, duration);
    }
}
1 Like