Newer
Older
Hierarchical-Task-Network-Unity-3D / Assets / Scripts / AnimationController.cs
using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class AnimationController : MonoBehaviour
{
    private Animator animator;
    private NavMeshAgent agent;

    [SerializeField] private Transform targetDestination;

    [Header("Animation Settings:")]
    [SerializeField] private bool rootMotion = true;
    [SerializeField] private float movingTurnSpeed = 360f;
    [SerializeField] private float stationaryTurnSpeed = 120f;

    [Header("Idle Rotation Settings:")]
    [SerializeField] private float rotationTriggerThreshold = 45f;
    [SerializeField] private float turnSmoothingSpeed = 0.1f;
    private float turnAngle;

    private Vector2 velocity;
    private Vector2 smoothDeltaPosition;

    private void Awake()
    {
        animator = GetComponent<Animator>();
        agent = GetComponent<NavMeshAgent>();

        animator.applyRootMotion = rootMotion;
        agent.updatePosition = false;
        agent.updateRotation = false;
    }

    private void OnAnimatorMove()
    {
        Vector3 rootPosition = animator.rootPosition;
        rootPosition.y = agent.nextPosition.y;
        transform.position = rootPosition;
        agent.nextPosition = rootPosition;
    }

    void Update()
    {
        //agent.destination = targetDestination.position;
        SynchronizeAnimatorAndAgent();
    }

    private void SynchronizeAnimatorAndAgent()
    {
        Vector3 worldDeltaPosition = agent.nextPosition - transform.position;
        worldDeltaPosition.y = 0;

        // Calculate velocity
        float dx = Vector3.Dot(transform.right, worldDeltaPosition);
        float dy = Vector3.Dot(transform.forward, worldDeltaPosition);
        Vector2 deltaPosition = new Vector2(dx, dy);

        float smooth = Mathf.Min(1, Time.deltaTime / turnSmoothingSpeed);
        smoothDeltaPosition = Vector2.Lerp(smoothDeltaPosition, deltaPosition, smooth);
        velocity = smoothDeltaPosition / Time.deltaTime;

        // Handle stopping
        if (agent.remainingDistance <= agent.stoppingDistance)
        {
            velocity = Vector2.Lerp(Vector2.zero, velocity, agent.remainingDistance / agent.stoppingDistance);
        }

        bool shouldMove = velocity.magnitude > 0.2f && agent.remainingDistance > 0.1f;

        if(!shouldMove) velocity = Vector2.zero;
        animator.SetBool("move", shouldMove);
        velocity = velocity.normalized;
        animator.SetFloat("velocity", velocity.magnitude);

        // Handle position sync
        if (worldDeltaPosition.magnitude > agent.radius / 2f)
        {
            transform.position = Vector3.Lerp(animator.rootPosition, agent.nextPosition, smooth);
        }



        // ---------- HANDLE ROTATION -----------

        Vector3 toTarget = agent.steeringTarget - transform.position;
        toTarget.y = 0;

        float angle = 0f;
        if (toTarget.sqrMagnitude > 0.01f)
        {
            angle = Vector3.SignedAngle(transform.forward, toTarget.normalized, Vector3.up);
        }

        // Smooth turn angle (used for animation blend)
        turnAngle = Mathf.Lerp(turnAngle, angle, smooth);

        float normalizedTurnAngle = turnAngle / rotationTriggerThreshold;
        normalizedTurnAngle = Mathf.Clamp(normalizedTurnAngle, -1f, 1f);
        animator.SetFloat("turnAngle", turnAngle);

        // ACTUALLY ROTATE THE AGENT (for nav mesh and visual sync)
        if (shouldMove)
        {
            float rotationSpeed = Mathf.Lerp(stationaryTurnSpeed, movingTurnSpeed, velocity.magnitude);
            Quaternion targetRotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
            transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
        }
    }
}