PrimeTween icon indicating copy to clipboard operation
PrimeTween copied to clipboard

[Unity 6.2] Tween.RigidbodyMoveRotation and Tween.Custom<T, Quaternion> do not behave properly if the starting rotation is the same as the target rotation

Open coty-crg opened this issue 4 months ago • 2 comments

After upgrading from Unity 2022.3 to Unity 6.2, I noticed that our rotation tweens are sometimes broken. After some digging, it turns out that it only happens when the "start" and "end" rotations are equal, and it has something to do with PrimeTweens interpolation of Quaternions.

Quick examples:

public static Tween RigidbodyMoveRotation(UnityEngine.Rigidbody target, Quaternion rotation, TweenSettings settings)
{
    var startRotation = target.rotation;

    // correct: nothing changes (0, 0, -1)
    return Tween.Custom<Rigidbody>(target, 0f, 1f, settings, onValueChange: (Rigidbody target, float t) =>
    {
        if (target != null)
        {
            var targetRotation = Quaternion.Slerp(startRotation, rotation, t);
            target.MoveRotation(targetRotation.normalized);
            Debug.Log($"target.rotation: {target.rotation * Vector3.forward}, value: {targetRotation * Vector3.forward}, value.normalized: {targetRotation.normalized * Vector3.forward}");
        }
    });

    // incorrect: somehow ends up facing towards (1, 0, 0) 
    return Tween.Custom(target, target.rotation, rotation, settings, onValueChange: (target, value) =>
    {
        if (target != null)
        {
            target.MoveRotation(value.normalized);
            Debug.Log($"target.rotation: {target.rotation * Vector3.forward}, value: {value * Vector3.forward}, value.normalized: {value.normalized * Vector3.forward}");
        }
    });
}

coty-crg avatar Sep 08 '25 20:09 coty-crg

Yes, I know its very strange that this only seems to happen on Unity 6. I have no idea why.

coty-crg avatar Sep 08 '25 20:09 coty-crg

For anyone else who runs into this, I wrote a kind of disgusting work around..

public static class TweenE
{
    private class RigidbodyRotationTweenData
    {
        public Rigidbody target;
        public Quaternion start;
        public Quaternion end;
    }

    // pooling these since you cannot use structs for custom tweens 
    private static List<RigidbodyRotationTweenData> _tweenDatasPool = new List<RigidbodyRotationTweenData>(); 

    public static Tween TweenMoveRotation(this Rigidbody target, Quaternion rotation, TweenSettings settings)
    {
        RigidbodyRotationTweenData data = null; 
        if (_tweenDatasPool.Count > 0)
        {
            data = _tweenDatasPool[_tweenDatasPool.Count - 1];
            _tweenDatasPool.RemoveAt(_tweenDatasPool.Count - 1); 
        }

        if(data == null)
        {
            data = new RigidbodyRotationTweenData(); 
        }

        data.target = target;
        data.start = target.rotation;
        data.end = rotation;

        return Tween.Custom(data, 0f, 1f, settings, onValueChange: (RigidbodyRotationTweenData data, float t) =>
        {
            if (data.target != null)
            {
                var targetRotation = Quaternion.Slerp(data.start, data.end, t);
                data.target.MoveRotation(targetRotation.normalized);
            }
        }).OnComplete(data, data =>
        {
            _tweenDatasPool.Add(data); 
        });
    }
}

coty-crg avatar Sep 08 '25 20:09 coty-crg

Hi, thanks for reporting the issue. Unfortunately, I couldn't reproduce it locally. I see the same result with both "correct" and "incorrect" versions. Would it be possible for you to create a small reproducible sample for me?

The only difference I spotted is that PrimeTween uses Quaternion.SlerpUnclamped internally, allowing animations to under- or overshoot with certain eases. But that should not make a difference if the startValue == endValue.

KyryloKuzyk avatar Nov 23 '25 08:11 KyryloKuzyk