Techniques de rotation et déplacement des objets dans Unity

La gestion de la rotation dans Unity peut s'avérer complexe, notamment en raison des complications liées à l'interruption des animations lors de l'utilisation de solutions tierces comme DOTween. Une approche plus fiable consiste à implémenter une interpolation directement dans la méthode Update. Voici une série de méthodes d'extension pour la classe Transform, conçues pour simplifier et fiabiliser les opérations de déplacement et de rotatino.

Exemple d'utilisation

using UnityEngine;

public class EnemyBehavior : MonoBehaviour
{
    [SerializeField] private Transform destination;
    [SerializeField] private float velocity = 5f;
    [SerializeField] private float angularSpeed = 180f;

    private void Update()
    {
        // Déplacement vers une cible
        bool arrived = transform.GotoTarget(destination, velocity, Time.deltaTime);
        if (arrived)
        {
            Debug.Log("Point d'arrivée atteint.");
        }

        // Orientation vers la cible
        bool oriented = transform.TurnTowards(destination, angularSpeed, Time.deltaTime);

        // Rotation vers des angles eulériens définis
        transform.SpinToEulerAngles(new Vector3(0, 90, 0), angularSpeed, Time.deltaTime);

        // Déplacement vers un point précis
        Vector3 point = new Vector3(10, 0, 5);
        transform.GotoPoint(point, velocity, Time.deltaTime);

        // Retour au point d'origine sur le plan XZ local
        transform.GotoLocalOriginXZ(velocity, Time.deltaTime);
    }
}

Implémentasion des méthodes

using UnityEngine;

public static class MovementAndRotationUtils
{
    /// <summary>
    /// Déplace de manière fluide un Transform vers la position d'un autre Transform.
    /// </summary>
    public static bool GotoTarget(this Transform self, Transform target, 
                                  float speed, float deltaTime, bool freezeY = true)
    {
        if (target == null)
        {
            Debug.LogError("La cible spécifiée est nulle.");
            return false;
        }

        Vector3 selfPosition = self.position;
        Vector3 targetPosition = target.position;
        float currentDist = Vector3.Distance(selfPosition, targetPosition);

        if (currentDist <= 0.01f)
        {
            self.position = targetPosition;
            return true;
        }

        Vector3 updatedPos = Vector3.MoveTowards(selfPosition, targetPosition, speed * deltaTime);
        
        if (freezeY)
        {
            updatedPos.y = self.position.y;
        }
        
        self.position = updatedPos;
        float remainingDist = Vector3.Distance(updatedPos, targetPosition);
        return remainingDist < 0.1f;
    }

    /// <summary>
    /// Ramène progressivement l'objet à l'origine locale sur les axes X et Z.
    /// </summary>
    public static bool GotoLocalOriginXZ(this Transform self, float speed, float deltaTime)
    {
        Vector3 local = self.localPosition;
        
        if (Mathf.Abs(local.x) < 0.01f && Mathf.Abs(local.z) < 0.01f)
        {
            self.localPosition = new Vector3(0f, local.y, 0f);
            return true;
        }
        
        Vector3 originTarget = new Vector3(0f, local.y, 0f);
        Vector3 moved = Vector3.MoveTowards(local, originTarget, speed * deltaTime);
        self.localPosition = moved;
        
        float offset = Vector2.Distance(new Vector2(moved.x, moved.z), Vector2.zero);
        return offset < 0.01f;
    }

    /// <summary>
    /// Effectue une rotation progressive vers une orientation définie par des angles eulériens.
    /// </summary>
    public static bool SpinToEulerAngles(this Transform self, Vector3 eulerAngles, 
                                         float degPerSec, float deltaTime)
    {
        Quaternion currentRot = self.rotation;
        Quaternion desiredRot = Quaternion.Euler(eulerAngles);
        
        if (Quaternion.Angle(currentRot, desiredRot) < 0.1f)
        {
            self.rotation = desiredRot;
            return true;
        }

        Quaternion smoothedRot = Quaternion.RotateTowards(currentRot, desiredRot, degPerSec * deltaTime);
        self.rotation = smoothedRot;
        return Quaternion.Angle(smoothedRot, desiredRot) < 1f;
    }

    /// <summary>
    /// Tourne de façon progressive vers la position d'une cible.
    /// </summary>
    public static bool TurnTowards(this Transform self, Transform target, 
                                   float turnSpeed, float deltaTime, 
                                   Vector3 rotationOffset = default, bool horizontalOnly = true)
    {
        if (self == null || target == null)
        {
            return false;
        }

        Vector3 toTarget = target.position - self.position;
        
        if (horizontalOnly)
        {
            toTarget.y = 0f;
            if (toTarget.sqrMagnitude < 0.0001f)
            {
                return true;
            }
        }

        Quaternion finalRotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
        
        if (rotationOffset != Vector3.zero)
        {
            finalRotation *= Quaternion.Euler(rotationOffset);
        }

        Quaternion current = self.rotation;
        float initialAngle = Quaternion.Angle(current, finalRotation);
        
        if (initialAngle <= 0.1f)
        {
            self.rotation = finalRotation;
            return true;
        }

        self.rotation = Quaternion.RotateTowards(current, finalRotation, turnSpeed * deltaTime);
        float resultingAngle = Quaternion.Angle(self.rotation, finalRotation);
        return resultingAngle < 1f;
    }

    /// <summary>
    /// Déplace progressivement un Transform vers une position donnée.
    /// </summary>
    public static bool GotoPoint(this Transform self, Vector3 point, 
                                 float speed, float deltaTime, bool lockY = true)
    {
        Vector3 origin = self.position;
        float distance = Vector3.Distance(origin, point);

        if (distance < 0.01f)
        {
            self.position = point;
            return true;
        }

        Vector3 interpolated = Vector3.MoveTowards(origin, point, speed * deltaTime);
        
        if (lockY)
        {
            interpolated.y = self.position.y;
        }
        
        self.position = interpolated;
        float newDist = Vector3.Distance(interpolated, point);
        return newDist < 0.1f;
    }
}

Étiquettes: Unity3D C# Transform Interpolation CharacterMovement

Publié le 18 juillet à 18h55