Mathf.SmoothDamp 在协程中花费的时间比它应该花费的时间长

Mathf.SmoothDamp takes longer than it should inside a coroutine

我正在尝试在协同程序中移动和旋转游戏对象以顺利到达目标位置。为此,我尝试使用 Mathf.SmoothDamp() 来计算一个因子,我在 lerping 函数中使用它。这是我的方法:

    private IEnumerator ReachTarget()
    {
        _speed = 0f;
        var lerpFactor = 0f;
        var startingPosition = transform.position;
        var startingRotation = transform.rotation;

        while (lerpFactor < 0.99f)
        {
            lerpFactor = Mathf.SmoothDamp(lerpFactor, 1f, ref _speed, 1f);

            transform.position = Vector3.Lerp(startingPosition, _targetTransform.position, lerpFactor);
            transform.rotation = Quaternion.Lerp(startingRotation, _targetTransform.rotation, lerpFactor);

            yield return null;
        }

        transform.position = _targetTransform.position;
        transform.rotation = _targetTransform.rotation;
    }

根据 Mathf.SmoothDamp() 的文档,它应该在一秒钟内将我的 lerpFactor0 更改为 1,这反过来应该将我的对象移动并旋转到一秒钟内到达目标位置。然而,这根本不会发生, lerpFactor 需要更长的时间(大约 3s)才能达到 1 (我使用 0.99 因为它永远不会真正达到 1 , 离得很近)。

我认为这可能是 Mathf.SmoothDamp() 默认使用 Time.deltaTime 的原因,这可能在协同程序中不起作用。所以我尝试提供自己的价值:

    private IEnumerator ReachTarget()
    {
        _speed = 0f;
        var lerpFactor = 0f;
        var startingPosition = transform.position;
        var startingRotation = transform.rotation;
        var prevTime = Time.realtimeSinceStartup;

        while (lerpFactor < 0.99f)
        {
            var time = Time.realtimeSinceStartup - prevTime;
            prevTime = Time.realtimeSinceStartup;

            lerpFactor = Mathf.SmoothDamp(lerpFactor, 1f, ref _speed, 1f, maxSpeed: 1000f, time); // using 1000f for max speed

            transform.position = Vector3.Lerp(startingPosition, _targetTransform.position, lerpFactor);
            transform.rotation = Quaternion.Lerp(startingRotation, _targetTransform.rotation, lerpFactor);

            yield return null;
        }

        transform.position = _targetTransform.position;
        transform.rotation = _targetTransform.rotation;
    }

这并没有改变任何东西,lerpFactor 达到 1 需要相同的时间。

我怎样才能让它按预期工作?

您不必使用平滑阻尼来计算 lerpFactor。 Lerp 因子就是经过的时间和持续时间的比率。所以如果你想要你的动画在一秒钟内;

private IEnumerator ReachTarget()
{
    float timeElapsed = 0;
    float lerpDuration = 1f;
    float lerpFactor;
    while (timeElapsed < lerpDuration)
    {
        lerpFactor = timeElapsed / lerpDuration;
        // Use lerpFactor in your lerp functions
        timeElapsed += Time.deltaTime;
        yield return null;
    }
}

SmoothDamp 的 smoothTime 只是一个近似值,并不精确,因为要付出更多的努力才不会超过目标。

我建议您切换到 SmoothStep 或 Animation 曲线,您可以根据需要自定义插值(例如添加过冲和弹跳效果)。

这是一个 smoothTime 更精确的例子

using System.Collections;
using UnityEngine;

public class LerpTest : MonoBehaviour
{
    [SerializeField] private Transform _targetTransform;
    [SerializeField] private AnimationCurve _animationCurve;
    private Vector3 startingPosition;
    private Quaternion startingRotation;

    void Start()
    {
        startingPosition = transform.position;
        startingRotation = transform.rotation;
    }

    private void Update()
    {
        if (Input.anyKeyDown)
        {
            StopAllCoroutines();
            transform.position = startingPosition;
            transform.rotation = startingRotation;
            StartCoroutine(ReachTarget(1f));
        }
    }

    private IEnumerator ReachTarget(float smoothTime)
    {
        var elapsedTime = 0f;

        while (elapsedTime < smoothTime)
        {
            float lerpFactor = Mathf.SmoothStep(0f, 1f, elapsedTime / smoothTime);
            // Or use an animation curve to customize the smoothing curve:
            // float lerpFactor = _animationCurve.Evaluate(elapsedTime / smoothTime);
            
            elapsedTime += Time.deltaTime;
            
            transform.position = Vector3.Lerp(startingPosition, _targetTransform.position, lerpFactor);
            transform.rotation = Quaternion.Slerp(startingRotation, _targetTransform.rotation, lerpFactor);

            yield return null;
        }

        Debug.Log($"Elapsed time: {elapsedTime} seconds");

        transform.position = _targetTransform.position;
        transform.rotation = _targetTransform.rotation;
    }
}

作为旁注,我建议您使用 Quaternion.Slerp 而不是 lerp 以获得更好看的插值,并随意在 corountines 中使用 Time.deltaTime - 它工作得很好。