Handling Behavior for Unreachable Paths on Different Altitudes

I’m having some trouble detecting when a path is unreachable due to verticality, in a recast/navmesh, like so:

I have an agent who is attempting to path to the player, on top of a building. I want the agents to crowd around the building in these scenarios, instead of following the player beneath. Typically I would engage that ‘crowding’ behavior once I’ve detected that the agent cannot reach the player.

However, IsPathPossible returns “True” even for these scenarios so I’m not sure which test I should be running. Path.error is “False” as well.

Unity’s default NavAgent would set agent.PathStatus to PathPartial (instead of PathComplete) in these scenarios so I was able to check for that. I’m not sure what the equivalent would be here.

(As well, is there a good resource as to how the package handles these unreachable targets, e.g., how it even manages to follow the target below it in the first place, even though they’re on completely separate navmesh areas?)

EDIT:
Solution here -

Handling Behavior for Unreachable Paths on Different Altitudes

Hey,
Sorry for not responding to this earlier.
I made a similar setup and was not able to recreate this issue:

Are you sure you’re setting the correct position ?

Hi! Thanks for trying it out. What test are you using at the bottom? IsPathPossible?

Here’s a similar test on my side (the red wiresphere is the agent’s destination), and the test in the console is IsPathPossible(path.path) where path is the variable returned from the seeker’s pathCallback delegate.

One key difference I can spot is that the navmesh I’m using has been split into different tags, WalkableInterior and WalkableExterior

EDIT:
I did some tests with every area using the default tag, and using the exterior tag for the roof (instead of the interior tag) and got the same results.

Could you generate The Graph cache and send it to me? I’m on my way home and I’ll take a look at it.

Hey I’ve tested ran the same test using the graph you’ve provided me.
I was unable to reproduce this issue

Hi

When given a path request the package will try to find a path to the closest point to the target that it can reach. It will search up to A* Inspector -> Settings -> Max Nearest Node Distance world units away. So you can lower that value to make it mark the path as completely failed instead. Or you could use IsPathPossible.

That will not work at all. What you are checking there is “are all the nodes in the path I was given reachable from each other”. That is trivially true since you were just given a valid path with those nodes. What you should check is if the closest node to the agent and the closest node to the point you want to reach are reachable from each other.

GraphNode node1 = AstarPath.active.GetNearest(point1, NNConstraint.Default).node;
GraphNode node2 = AstarPath.active.GetNearest(point2, NNConstraint.Default).node;

if (PathUtilities.IsPathPossible(node1, node2)) {
    // Yay, there is a path between those two nodes
}

In my tests I also used the IsPathPossible(GraphNode, GraphNode)

The explanation of how IsPathPossible works helps a lot, thank you. I can confirm using IsPathPossible(node1, node2) (where node1 is ai.position, and node2 is the destination) returns False in this case as expected.

However, unlike @ToastyStoemp, I still get the AI navigating below the goal point. I’m not sure what differences there might be in our setup. This is the RichAI component on the AI:

image

I did tests with reducing the AI’s height down to 1.0 and End Reached Distance down to 0.5 in case that was letting it reach the end position ‘through’ the floor and it still was given a path leading beneath.

Not sure if the A* settings themselves could be relevant:

I’ve tried with the graph you’ve send me before, exact same settings on the Agent (although agent settings don’t affect PathUtilities)

As expected the result is false.

I’ll add the code I’ve used to generate this result.
Should be easy enough to remake my test as next:

Target mover modified
using UnityEngine;
using System.Linq;

namespace Pathfinding {
	/// <summary>
	/// Moves the target in example scenes.
	/// This is a simple script which has the sole purpose
	/// of moving the target point of agents in the example
	/// scenes for the A* Pathfinding Project.
	///
	/// It is not meant to be pretty, but it does the job.
	/// </summary>
	[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_target_mover.php")]
	public class TargetMover : MonoBehaviour {
		/// <summary>Mask for the raycast placement</summary>
		public LayerMask mask;

		public Transform agent;
		
		public Transform target;
		IAstarAI[] ais;

		/// <summary>Determines if the target position should be updated every frame or only on double-click</summary>
		public bool onlyOnDoubleClick;
		public bool use2D;

		Camera cam;

		public void Start () {
			//Cache the Main Camera
			cam = Camera.main;
			// Slightly inefficient way of finding all AIs, but this is just an example script, so it doesn't matter much.
			// FindObjectsOfType does not support interfaces unfortunately.
			ais = FindObjectsOfType<MonoBehaviour>().OfType<IAstarAI>().ToArray();
			useGUILayout = false;
		}

		public void OnGUI () {
			if (onlyOnDoubleClick && cam != null && Event.current.type == EventType.MouseDown && Event.current.clickCount == 2) {
				UpdateTargetPosition();
			}
		}

		/// <summary>Update is called once per frame</summary>
		void Update () {
			if (!onlyOnDoubleClick && cam != null) {
				UpdateTargetPosition();
			}
		}

		public void UpdateTargetPosition () {
			Vector3 newPosition = Vector3.zero;
			bool positionFound = false;

			if (use2D) {
				newPosition = cam.ScreenToWorldPoint(Input.mousePosition);
				newPosition.z = 0;
				positionFound = true;
			} else {
				// Fire a ray through the scene at the mouse position and place the target where it hits
				RaycastHit hit;
				if (Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit, Mathf.Infinity, mask)) {
					newPosition = hit.point;
					positionFound = true;
				}
			}

			if (positionFound && newPosition != target.position) {
				target.position = newPosition;

				if (onlyOnDoubleClick)
				{

					GraphNode startNode = AstarPath.active.GetNearest(agent.transform.position).node;
					GraphNode endNode = AstarPath.active.GetNearest(newPosition).node;
					
					Debug.LogError(PathUtilities.IsPathPossible(startNode, endNode));
					
					for (int i = 0; i < ais.Length; i++) {
						if (ais[i] != null) ais[i].SearchPath();
					}
				}
			}
		}
	}
}

Thanks, yeah, I’m using IsPathPossible correctly now and it is working as expected.

I still don’t know why my agent is able to find a path even while IsPathPossible is false and it clearly cannot get to the roof - but it is of no issue since I’m calling custom code after I detect the failure, which works successfully.

Thank you so much for the help, my issue is (practically) solved.

When the destination is unreachable the agents will automatically find the closest node to that destination. And navigate there. If the path isn’t possible you can just not update it.

Ah, I didn’t realize it did that automatically. Is there no other way to ‘adjust’ that behavior than checking IsPathPossible along with every pathfind call? e.g., is there a callback or something when it realizes it cannot get to the destination?

1 Like

Hi

Did you try adjusting A* Inspector -> Settings -> Max Nearest Node Distance?