Approach and final rotation arc for an agent approaching a destination

I’m trying to think of a way to make an agent turn to face a certain direction at the destination by following an arc. An example would be a plane coming into land at an airport runway, a car driving into parking space, or an NPC character walking around to sit at a dining table.

My current thinking is to calculate the A* path to a certain distance from the destination, and then calculate my own tangent lines (using this technique) for the final approach arc, appending them to the Astar path.

I think this is out-of-scope for the A* pathfinding project, but am posting here to see if anyone has any suggestions.

Hello,

I believe your current thinking is pretty much it. Pathfinding is just the calculation of a valid path from point a to b.
I use a state machine for the ai, and to sit it in a chair, i add a waypoint to the char as a child, the ai moves towards the waypoint, which is the state MoveTo.

Enter state example:

public override void EnterState(AIController aiController, StateManager stateManager)
{
    aiController.AINavigator.enabled = true;
    aiController.AINavigator.canSearch = true;
    aiController.AINavigator.isStopped = false;
}

When it reaches it’s destination:

public override void ExitState(AIController aiController, StateManager stateManager)
{
    aiController.AINavigator.enabled = false;
    aiController.AINavigator.canSearch = false;
    aiController.AINavigator.isStopped = true;
}

and the rotation and sitting animations has nothing to do with pathfinding.

Another example i have is a LungeAttack, when the ai is within a ranged distance, it exits the Chase state which is continuously updating the target position and setting the pathfinding destination to it’s position.

[CreateAssetMenu(fileName = "LungeAttackState", menuName = "AI/State/LungeAttack")]
public class LungeAttackState : BaseState
{
    [SerializeField] private float _stopDistance = 0.5f; // Distance to stop from the target position.
    [SerializeField] private float _cooldownTime = 3.5f; // Distance to stop from the target position.

    private Animator _animator;
    private int _overrideLayerIndex = -1;
    private const string _overrideLayerName = "Override";

    private Vector3 _targetPosition;
    private bool _isLunging = false;

    private float _cooldownTimer = 0.0f;

    public override void EnterState(AIController aiController, StateManager stateManager)
    {
        _animator = aiController.AnimatorHandler.Animator;
        _overrideLayerIndex = _animator.GetLayerIndex(_overrideLayerName);

        aiController.AINavigator.enableRotation = true;
    }

    public override void ExitState(AIController aiController, StateManager stateManager)
    {
    }

    public override void UpdateState(AIController aiController, StateManager stateManager)
    {
        if (_isLunging || aiController.AnimatorHandler.IsInteracting || !stateManager.VisualSensorManager.Target)
            return;

        Vector3 directionToPlayer = (stateManager.VisualSensorManager.Target.position - aiController.transform.position).normalized;
        _targetPosition = stateManager.VisualSensorManager.Target.position - directionToPlayer * _stopDistance;

        GameController.Instance.StartCoroutine(LungeToPosition(aiController));
    }

    private IEnumerator LungeToPosition(AIController aiController)
    {
        aiController.AnimatorHandler.PlayTargetAnimation("Tell_Attack01", "IsInteracting", true);

        while (!_animator.GetCurrentAnimatorStateInfo(_overrideLayerIndex).IsName("RightHand@Attack01"))
            yield return null;

        _isLunging = true;

        float animationLength = _animator.GetCurrentAnimatorStateInfo(_overrideLayerIndex).length;

        float elapsedTime = 0.0f;
        while (elapsedTime < animationLength)
        {
            float normalizedTime = elapsedTime / animationLength;
            aiController.transform.position = Vector3.Lerp(aiController.transform.position, _targetPosition, normalizedTime);

            elapsedTime += Time.deltaTime;

            yield return null;
        }

        _cooldownTimer = _cooldownTime;
        while (_cooldownTimer > 0.0f)
        {
            _cooldownTimer -= Time.deltaTime;

            yield return null;
        }

        _isLunging = false;
    }

    private void OnDisable()
    {
        _isLunging = false;
        _cooldownTimer = 0.0f;
    }
}

Hope this helps.

Thanks for your suggestions, Mythran.

I’m going to try calculating the Astar path to a point a certain radius from the final destination, depending on the final rotation required. I’ll then procedurally steer the NPC in an arc to finally face the correct rotation, and then handle the sit animation.

Hi

In the beta version you can use the FollowerEntity movement script which has a SetDestination method that takes an orientation. If that one is used, the FollowerEntity will try to approach the endpoint in an arc (set by the “Lead In Radius When Approaching Endpoint” field). It will also automatically make the arc smaller if there’s not enough space for a bigger arc.

FollowerEntity may be just what I’m looking for. I’ll try the beta and report back.

Works very well when approaching a destination with a shallow angle. With the FollowerEntity’s “Approach With Orientation” debugger enabled I can see the agent’s graceful curve, which is exactly what I want.

The only problem is that it appears to work only with shallow angles - if the destination’s rotation faces towards the agent, then the agent moves directly to the target, and rotates on the spot (which I need to avoid - “Allow Rotating On The Spot” is disabled).

Looking at the code I found STEEP_ANGLE_THRESHOLD_COS inside OffsetCornerForApproach() which appears to be the angle threshold limit. The ideal behaviour would be for the agent to arc slightly behind the destination to arrive with the correct rotation, but without rotating on the spot (similar to an aircraft landing at a runway). Are there any plans to implement this in a future beta?

Vague plans, but it might take a while, I’m afraid. My current approach would need some tweaks to handle that.

Vague plans, but it might take a while, I’m afraid. My current approach would need some tweaks to handle that.

No problem! Looking forward to testing at some point in the future. It’ll be a valuable feature once released.

Hi Fred, did you ever come up with a solution to this? I have just reached the point of needing to implement the same and found this thread so thought I would message you in case you have solved the problem and wouldn’t mind sharing some wisdom! Thanks

Hi Fred, did you ever come up with a solution to this?

Hi Sam, not yet, although I did have a message from Aron which suggests that it may be implemented in the latest beta:

This is implemented in the FollowerEntity movement script which exists in the beta.
You can use ai.SetDestination(destination, orientation) to activate it.

Perhaps try the latest beta to see if it works?

Just tried the latest 4.3.95 beta, and the agent still rotates on the spot if the angle threshold is exceeded.