aIPath.remainingDistance <= aIPath.endReachedDistance remains true when I change destination in quick succession

  • A* version: [5.3.8]
  • Unity version: [2023.2.5f1]
  • Hey There, Im using AIPath and a simple state machine to move an agent that has a simple job: Move to a position, grab an item, move to a different position, drop that item. During the first update loop, where the agent is required to move to the item, everything works perfect. But as soon as the agent picks up the item, I check this:
  • if (aIPath.remainingDistance <= aIPath.endReachedDistance)
  • Which results in ‘true’ but that should not be the case. The new destination is far away. I’m guessing the seeker component didn’t calculate the new path properly?. Due to this, the agent never moves to the second destination but instead it drops the item right from the first destination. Right now, I’m adding a timer of 3 seconds after change destination and setting can move to true again to avoid this bug and that solves the issue. So right after setting the target, wait 3 seconds and then check if AIPath.remainingDistance <= AiPath.endReachedDistance. Here’s my complete script. Refer to the update loop.
using Pathfinding;
using System;
using UnityEngine;
using DG.Tweening;
using System.Linq;
public class LoadingAreaForkLiftAINew : Resource
{

    private PutAwaySuggestion currentPutAwaySuggestionItem;
    [SerializeField] AIPath aIPath;
    [SerializeField] AIDestinationSetter destinationSetter;
    [SerializeField] Seeker seeker;
    [SerializeField] Transform cratePositionTransform;
    private float timer;
    private string currentAction = "";
    bool foundItem = false;
    public static event Action OnAnyForkLiftjobComplete;
    public Transform destinationRack = null;
    public DashboardRackData currentRack = null;
    private bool isMoving = false;
    //Delete Later
    public string itemToSearch;
    protected override void Start()
    {
        base.Start();
        SimulationManager.Instance.OnItemArrangedOnTruck += SetTarget;
    }
    private void SetTarget()
    {
        if (isMoving || SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count == 0)
            return;

        currentPutAwaySuggestionItem = SimulationManager.Instance.putAwaySuggestionsGameObjectList.First();
        bool found = false;

        foreach (var rack in SimulationManager.Instance.createdRackList)
        {
            foreach (var locator in rack.locators)
            {
                if (string.Equals(locator.locationName.Trim(), currentPutAwaySuggestionItem.suggested_location.Trim(), StringComparison.OrdinalIgnoreCase))
                {
                    SimulationManager.Instance.putAwaySuggestionsGameObjectList.Remove(currentPutAwaySuggestionItem);
                    foundItem = true;

                    destinationRack = rack.binTransforms[UnityEngine.Random.Range(0, rack.binTransforms.Count)];
                    currentRack = rack;

                    isMoving = true; // Now we're busy moving!
                    //seeker.StartPath(transform.position, currentPutAwaySuggestionItem.transform.position, OnPathComplete);
                    destinationSetter.target = currentPutAwaySuggestionItem.transform;
                    currentState = ResourceState.PackingRequest;
                    found = true;
                    break;
                }
            }

            if (found) break;
        }

        if (!found)
        {
            Debug.Log($"No locator found for suggested location: {currentPutAwaySuggestionItem.suggested_location}");
            SimulationManager.Instance.putAwaySuggestionsGameObjectList.Remove(currentPutAwaySuggestionItem);
            Destroy(currentPutAwaySuggestionItem);

            if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
            {
                SetTarget();
            }
        }
    }
   
    private void OnPathComplete(Path p)
    {
        isPathPending = false; // Path calculation complete

        if (!p.error)
        {
            // Path is valid; proceed to Move state
            if (currentAction == "")
            {
                destinationSetter.target = currentPutAwaySuggestionItem.transform;
                currentState = ResourceState.PackingRequest;
            }
            if (currentAction == "DropItem")
            {
                destinationSetter.target = destinationRack;
                currentState = ResourceState.Move;
            }


        }
        else
        {
            Debug.LogError("Pathfinding failed: " + p.errorLog);

            // Handle failure: Either retry or find another target
            SetTarget();
        }
    }
    protected override void Update()
    {
        SimulateBatteryBehaviour();
        if (isCharging) { return; }
        base.Update();
        if (!canOperate) return;
        //if (isPathPending) return;
        CheckAgentPosition();
        Vector3 pos = transform.position;
        pos.y = 0;
        transform.position = pos;
        if (currentState == ResourceState.Idle)
        {
            if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
            {
                SetTarget();
            }
        }
        if (currentState == ResourceState.PackingRequest)
        {
            currentAction = "GrabItem";

            //currentState = ResourceState.Move;
            float maxTime = 3f;
            timer += Time.deltaTime;
            if (timer > maxTime)
            {
                currentState = ResourceState.Move;
                Debug.Log("timer over");
                timer = 0f;
            }
        }
        if (currentState == ResourceState.Move)
        {
            aIPath.canMove = true;
            if (aIPath.reachedEndOfPath && aIPath.remainingDistance <= aIPath.endReachedDistance)
            {

                Debug.Log("Reached end distance");
                aIPath.canMove = false;
                SetState(currentAction);
            }
        }
        if (currentState == ResourceState.GrabItem)
        {
            currentPutAwaySuggestionItem.transform.parent = cratePositionTransform;
            currentPutAwaySuggestionItem.transform.DOLocalMove(Vector3.zero, .5f);
            //currentPutAwaySuggestionItem.GetComponent<DynamicGridObstacle>().enabled = false;
            currentAction = "DropItem";
            //isPathPending = true;
            //foundItem = false;
            //seeker.StartPath(transform.position, destinationRack.position, OnPathComplete);
            destinationSetter.target = destinationRack.transform;
            float maxTime = 3f;
            timer += Time.deltaTime;
            if (timer > maxTime)
            {
                currentState = ResourceState.Move;
                Debug.Log("timer over");
                timer = 0f;
            }
           
        }
        if (currentState == ResourceState.DropItem)
        {
            currentPutAwaySuggestionItem.transform.parent = destinationRack;
            currentPutAwaySuggestionItem.transform.DOLocalMove(Vector3.zero + new Vector3(0, .1f, 0), .5f);
            currentPutAwaySuggestionItem.transform.DOScale(new Vector3(.4f, .4f, .4f), .5f);
            currentPutAwaySuggestionItem.transform.DORotate(Vector3.zero, .5f);
            currentRack.stockedItems.Add(currentPutAwaySuggestionItem);
            currentPutAwaySuggestionItem = null;
            currentRack = null;
            currentAction = "";
            destinationRack = null;
            isPathPending = true;
            currentState = ResourceState.Idle;
            foundItem = false;
            //SimulationManager.Instance.TotalItems++;
            FinishPutAway();
            if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
            {
                SetTarget();
            }
            else
            {
                OnAnyForkLiftjobComplete?.Invoke();
            }
        }
    }
    private void FinishPutAway()
    {
        isMoving = false;

        if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
        {
            SetTarget();
        }
    }
    private void SetState(string currentAction)
    {
        if (currentAction == "GrabItem")
        {
            Debug.Log("Setting state to grab item");
            currentState = ResourceState.GrabItem;
        }
        if (currentAction == "DropItem")
        {
            Debug.Log("Setting state to drop item");
            currentState = ResourceState.DropItem;
        }
    }

}

Hi

This is a known limitation of the AIPath movement script. See AIPath - A* Pathfinding Project and AIPath - A* Pathfinding Project for some details.

The newer FollowerEntity movement script was written partly to remedy this. It uses a much more sophisticated method of repairing the path to try as hard as possible to ensure this value is up to date.

Any tips on what’s the most efficient way to solve this problem?

Best tip is to use the FollowerEntity movement script instead :wink:

You can also call ai.SearchPath() after setting the destination, and then repeatedly check ai.pathPending, it will be true while the path is being calculated.

I search path like this
aIPath.SearchPath();
and them move to a wait for path state. In that state, I check this:

 if(currentState == ResourceState.WaitingForPath)
 {
     if (!aIPath.pathPending)
     {
         currentState = ResourceState.Move;
         Debug.Log("Found path");
     }
 }

But my agent is stuck forever searching for a path and this debug log never gets printed. Checked the inspector and found that the path pending field remains true forever

Sounds like you are calling ai.SearchPath every frame, without letting it finish calculating the path?

No, I call SearchPath in a state and then move to a different state like below. Therefore SearchPath will never be called more than once

 protected override void Update()
 {
 if (currentState == ResourceState.PackingRequest)
 {
     currentAction = "GrabItem";
     aIPath.SearchPath();
     Debug.Log("Searching path");
     currentState = ResourceState.WaitingForPath;
 }
if(currentState == ResourceState.WaitingForPath)
{
    if (!aIPath.pathPending)
    {
        currentState = ResourceState.Move;
        Debug.Log("Found path");
    }
}
}

Here’s the entire class for reference

using Pathfinding;
using System;
using UnityEngine;
using DG.Tweening;
using System.Linq;
public class LoadingAreaForkLiftAINew : Resource
{

    private PutAwaySuggestion currentPutAwaySuggestionItem;
    [SerializeField] AIPath aIPath;
    [SerializeField] AIDestinationSetter destinationSetter;
    [SerializeField] Seeker seeker;
    [SerializeField] Transform cratePositionTransform;
    private float timer;
    private string currentAction = "";
    bool foundItem = false;
    public static event Action OnAnyForkLiftjobComplete;
    public Transform destinationRack = null;
    public DashboardRackData currentRack = null;
    private bool isMoving = false;
    //Delete Later
    public string itemToSearch;
    protected override void Start()
    {
        base.Start();
        SimulationManager.Instance.OnItemArrangedOnTruck += SetTarget;
    }
    private void SetTarget()
    {
        if (isMoving || SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count == 0)
            return;

        currentPutAwaySuggestionItem = SimulationManager.Instance.putAwaySuggestionsGameObjectList.First();
        bool found = false;

        foreach (var rack in SimulationManager.Instance.createdRackList)
        {
            foreach (var locator in rack.locators)
            {
                if (string.Equals(locator.locationName.Trim(), currentPutAwaySuggestionItem.suggested_location.Trim(), StringComparison.OrdinalIgnoreCase))
                {
                    SimulationManager.Instance.putAwaySuggestionsGameObjectList.Remove(currentPutAwaySuggestionItem);
                    foundItem = true;

                    destinationRack = rack.binTransforms[UnityEngine.Random.Range(0, rack.binTransforms.Count)];
                    currentRack = rack;

                    isMoving = true; // Now we're busy moving!
                    //seeker.StartPath(transform.position, currentPutAwaySuggestionItem.transform.position, OnPathComplete);
                    //destinationSetter.target = currentPutAwaySuggestionItem.transform;
                    aIPath.destination = currentPutAwaySuggestionItem.transform.position;
                    currentState = ResourceState.PackingRequest;
                    found = true;
                    break;
                }
            }

            if (found) break;
        }

        if (!found)
        {
            Debug.Log($"No locator found for suggested location: {currentPutAwaySuggestionItem.suggested_location}");
            SimulationManager.Instance.putAwaySuggestionsGameObjectList.Remove(currentPutAwaySuggestionItem);
            Destroy(currentPutAwaySuggestionItem);

            if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
            {
                SetTarget();
            }
        }
    }
    protected override void Update()
    {
        SimulateBatteryBehaviour();
        if (isCharging) { return; }
        base.Update();
        if (!canOperate) return;
        //if (isPathPending) return;
        CheckAgentPosition();
        Vector3 pos = transform.position;
        pos.y = 0;
        transform.position = pos;
        if (currentState == ResourceState.Idle)
        {
            if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
            {
                SetTarget();
            }
        }
        if (currentState == ResourceState.PackingRequest)
        {
            currentAction = "GrabItem";

            //currentState = ResourceState.Move;
            //float maxTime = 3f;
            //timer += Time.deltaTime;
            //if (timer > maxTime)
            //{
            //    currentState = ResourceState.Move;
            //    Debug.Log("timer over");
            //    timer = 0f;
            //}
            aIPath.SearchPath();
            Debug.Log("Searching path");
            currentState = ResourceState.WaitingForPath;
        }
        if (currentState == ResourceState.Move)
        {
            aIPath.canMove = true;
            if (aIPath.reachedEndOfPath && aIPath.remainingDistance <= aIPath.endReachedDistance)
            {

                Debug.Log("Reached end distance");
                aIPath.canMove = false;
                SetState(currentAction);
            }
        }
        if (currentState == ResourceState.GrabItem)
        {
            currentPutAwaySuggestionItem.transform.parent = cratePositionTransform;
            currentPutAwaySuggestionItem.transform.DOLocalMove(Vector3.zero, .5f);
            //currentPutAwaySuggestionItem.GetComponent<DynamicGridObstacle>().enabled = false;
            currentAction = "DropItem";
            //isPathPending = true;
            //foundItem = false;
            //seeker.StartPath(transform.position, destinationRack.position, OnPathComplete);
            //destinationSetter.target = destinationRack.transform;
            aIPath.destination = destinationRack.transform.position;
            //float maxTime = 3f;
            //timer += Time.deltaTime;
            //if (timer > maxTime)
            //{
            //    currentState = ResourceState.Move;
            //    Debug.Log("timer over");
            //    timer = 0f;
            //}
            aIPath.SearchPath();
            Debug.Log("Searching path");
            currentState = ResourceState.WaitingForPath;
           
        }
        if(currentState == ResourceState.WaitingForPath)
        {
            if (!aIPath.pathPending)
            {
                currentState = ResourceState.Move;
                Debug.Log("Found path");
            }
        }
        if (currentState == ResourceState.DropItem)
        {
            currentPutAwaySuggestionItem.transform.parent = destinationRack;
            currentPutAwaySuggestionItem.transform.DOLocalMove(Vector3.zero + new Vector3(0, .1f, 0), .5f);
            currentPutAwaySuggestionItem.transform.DOScale(new Vector3(.4f, .4f, .4f), .5f);
            currentPutAwaySuggestionItem.transform.DORotate(Vector3.zero, .5f);
            currentRack.stockedItems.Add(currentPutAwaySuggestionItem);
            currentPutAwaySuggestionItem = null;
            currentRack = null;
            currentAction = "";
            destinationRack = null;
            isPathPending = true;
            currentState = ResourceState.Idle;
            foundItem = false;
            //SimulationManager.Instance.TotalItems++;
            FinishPutAway();
            if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
            {
                SetTarget();
            }
            else
            {
                OnAnyForkLiftjobComplete?.Invoke();
            }
        }
    }
    private void FinishPutAway()
    {
        isMoving = false;

        if (SimulationManager.Instance.putAwaySuggestionsGameObjectList.Count > 0)
        {
            SetTarget();
        }
    }
    private void SetState(string currentAction)
    {
        if (currentAction == "GrabItem")
        {
            Debug.Log("Setting state to grab item");
            currentState = ResourceState.GrabItem;
        }
        if (currentAction == "DropItem")
        {
            Debug.Log("Setting state to drop item");
            currentState = ResourceState.DropItem;
        }
    }

}

The “entire class” doesn’t contain your first snippet? Seems to be outdated?

The snippet I provided was after removing a lot of unnecessary code, sorry for the confusion. In the class, in the update loop, if (currentState == ResourceState.PackingRequest), if (currentState == ResourceState.GrabItem), if(currentState == ResourceState.WaitingForPath)
These deal with the concerned code

I note that you have set “Recalculate Paths automatically” to Every 0.01 seconds. That will cause it to recalculate its path basically every frame.