Custom graph runtime update issue

Hi everyone. I’m trying to build custom graph based on point graph that dynamicly updates in runtime based on entities as obstacles. Everything works fine untill I try to attach DynamicGridObstacle to the agent. Sometimes my agents just getting stuck permanently while trying to repath with this message:

Path Failed : Computation Time 0.000 ms Searched Nodes 0
Error: Couldn’t find a close node to the end point

There’s definately close node to the end point (since it works w/o DynamicGridObstacle), and settings is:

Graph UpdateArea, Connections and Collisions code:

public void UpdateArea(GraphUpdateObject o) {
        if (nodes == null) {
            Debug.LogWarning("Graph is not scanned, cannot update area ");
            return;
        }

        //Copy the bounds
        Bounds b = o.bounds;

        bool willChangeWalkability = o.updatePhysics || o.modifyWalkability;

        // Expanding bounds to fit more points
        // Either the collision data is used a.k.a. capsule raycast or node size specified
        if (collision.collisionCheck) {
            Vector3 margin = new Vector3(collision.diameter, 0, collision.diameter) * 0.5F;

            b.Expand(margin);
        } else {
            b.Expand(new Vector3(nodeSize, nodeSize, nodeSize));
        }

        if (maxDistance <= 0) {
            b.Expand(b.size * 2);
        } else {
            // Increasing size of bounds to fit points that are left out
            b.Expand(b.size * maxDistance);
        }

        // Initialization
        if (_intersectedNodes == null) {
            _intersectedNodes = new List<PointNode>();
        } else {
            _intersectedNodes.Clear();
        }

        // Creating list of nodes that intersect with these bounds
        for (int i = 0; i < nodes.Length; i++) {
            if (b.Contains((Vector3) nodes[i].position)) {
                _intersectedNodes.Add(nodes[i]);

                // Mark nodes that might be changed
                o.WillUpdateNode(nodes[i]);
            }
        }

        // Update Physics first
        if (o.updatePhysics && !o.modifyWalkability) {
            collision.Initialize(matrix, nodeSize);

            foreach (PointNode node in _intersectedNodes) {
                UpdateNodePositionCollision(node, o.resetPenaltyOnPhysics);
            }
        }

        //Apply GUO
        foreach (PointNode node in _intersectedNodes) {
            if (b.Contains((Vector3) node.position)) o.Apply(node);
        }

        //To avoid too many allocations, these lists are reused for each node in CalculateConnections
        if (_connections == null) {
            _connections = new List<PointNode>(3);
        }
        if (_costs == null) {
            _costs = new List<uint>(3);
        }

        // Recalculate connections
        if (willChangeWalkability) {
            for (int i = 0; i<_intersectedNodes.Count; i++){
                CalculateConnections(_intersectedNodes[i], i);
            }
        }
    }

    /// <summary>
    /// Calculates connections for a single node.
    /// </summary>
    /// <param name="node">Graph node</param>
    /// <param name="index">Index of node in graph</param>
     public virtual void CalculateConnections(PointNode node, int index) {
        // All connections are disabled if the node is not walkable
        if (!node.Walkable) {
            // Reset all connections
            // This makes the node have NO connections to any neighbour nodes
            // Also removes reverted connections
            node.ClearConnections(true);
            return;
        }

        if (maxDistance >= 0) {
            //Loop through all nodes and add connections to other nodes
            for (int j = 0; j < _intersectedNodes.Count; j++) {
                if (index == j) continue;

                PointNode other = _intersectedNodes[j];

                float dist;
                if (IsValidConnection(node, other, out dist)) {
                    _cost = (uint) Mathf.RoundToInt(dist * Int3.FloatPrecision);
                    node.AddConnection(other, _cost);
                    other.AddConnection(node, _cost);
                }
            }
        }
    }

    public virtual void UpdateNodePositionCollision(PointNode node, bool resetPenalty = true) {
        RaycastHit hit;

        bool walkable;

        // Calculate the actual position using physics raycasting (if enabled)
        // walkable will be set to false if no ground was found (unless that setting has been disabled)
        Vector3 position = collision.CheckHeight((Vector3) node.position, out hit, out walkable);
        node.position = (Int3) position;

        if (resetPenalty) {
            node.Penalty = initialPenalty;

            // Calculate a penalty based on the y coordinate of the node
            if (penaltyPosition) {
                node.Penalty += (uint) Mathf.RoundToInt(
                    (node.position.y - penaltyPositionOffset) * penaltyPositionFactor);
            }
        }

        // Check if the node is on a slope steeper than permitted
        if (walkable && useRaycastNormal && collision.heightCheck) {
            if (hit.normal != Vector3.zero) {
                // Take the dot product to find out the cosinus of the angle it has (faster than Vector3.Angle)
                float angle = Vector3.Dot(hit.normal.normalized, collision.up);

                // Add penalty based on normal
                if (penaltyAngle && resetPenalty) {
                    node.Penalty +=
                        (uint) Mathf.RoundToInt((1F - Mathf.Pow(angle, penaltyAnglePower)) * penaltyAngleFactor);
                }

                // Cosinus of the max slope
                float cosAngle = Mathf.Cos(maxSlope * Mathf.Deg2Rad);

                // Check if the ground is flat enough to stand on
                if (angle < cosAngle) {
                    walkable = false;
                }
            }
        }

        // If the walkable flag has already been set to false, there is no point in checking for it again
        // Check for obstacles
        node.Walkable = walkable && collision.Check((Vector3) node.position);
    }

In DynamicGridObstacle I use GUO with settings:

_graphUpdateObject = new GraphUpdateObject(bounds) {
                 resetPenaltyOnPhysics = false
};

Edit: If I disable Constrain.Suitable check I’m getting this:

There is no valid path to the target (start area: 54, target area: 1)

First I thought the issue was in start point being unwalkable, it seems it’s finding start point just fine, I’ve checked that.
Any idea what I’m doing wrong?

Update: I think I’ve found the problem.
For some odd reasons graph also seems to be degenerating even when it’s NOT been used with DynamicGridObstacle:
After scan:


After it’s been used by agent:

Hi

Do you perhaps have A* Inspector -> Settings -> Debug -> Show Search Tree enabled? That will display the tree of shortest paths in the scene view for every path that is calculated. If you disable it you will see the full graph again.

It seems likely that you have removed all connections from the node around the player, but the node is still walkable. What will happen in that case is that when the player tries to request a path, it will find that node close to itself and then try to find a node close to the target point that it can reach, but since the start node is disconnected from all other nodes it will not be able to find one (though it would set the end node to the same node as the start node if it was close enough, controlled by the A* Inspector -> Settings -> Max Nearest Node Distance).

That says roughly the same thing. There is no path from the closest node to the start point to the closest node to the end point.

Thanks for your reply. Yeah I had Show Search Tree enabled, so that’s not an issue >.<
And about the node… well I’ve tweaked the size of gizmo:

They’re marking as unwalkable.

Do you think you could set ‘Graph Coloring’ to ‘Area’ to see if the areas haven’t been updated properly?

Also. You could try to edit the ABPath class and add something like

Debug.DrawRay((Vector3)startNode.position, Vector3.up, Color.red);

In the ABPath.Prepare method to see which node it is selecting.

Seems like it’s picking closest walkable node as a start point.

I’ve switched Graph Coloring to Area. There’s multiple areas with different colors. That’s what suppose to happen?

Ok… so that looks fine. The different colors correspond to different connected components (https://en.wikipedia.org/wiki/Connected_component_(graph_theory)) in the graph. This means that there is no valid path between nodes of different colors.

Which point is it trying to move to? Is that point close to a node with the same color as the start node?

It’s a different color node. Does that mean it cannot be reached? Even though it’s connected?

When you construct the graph (because this was a custom graph, right?) are you sure you add bidirectional connections?

node1.AddConnection(node2);

will not add a bidirectional connection. You also need to call

node2.AddConnection(node1);

Wow, I think that did it!
I’ve forgot to connect the nodes properly. My bad. Updated the first post just in case.
Now areas are connected.

Thank you! :slight_smile:

1 Like