Home > Back-end >  Mathf.SmoothDamp takes longer than it should inside a coroutine
Mathf.SmoothDamp takes longer than it should inside a coroutine

Time:11-16

I'm trying to move and rotate a gameobject inside a coroutine to smoothly reach a target position. To do this I tried using Mathf.SmoothDamp() to calculate a factor, which I use in a lerping function. This is my method:

    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;
    }

Based on the documentation for Mathf.SmoothDamp() it should change my lerpFactor from 0 to 1 in one second, which in turn should move and rotate my object to to it's target position in one second. This however simply doesn't happen and it takes much longer (approximately 3s) for the lerpFactor to reach 1 (I used 0.99 to because it will never actually reach 1, just get very close).

I thought the reason for this might be that the Mathf.SmoothDamp() uses Time.deltaTime by default, which maybe doesn't work inside coroutines. So I tried supplying my own value:

    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;
    }

This didn't change anything and it takes the same amount of time for the lerpFactor to reach 1.

How can I make it work as it's supposed to?

CodePudding user response:

SmoothDamp's smoothTime is only an approximation and won't be precise as more effort is put into not overshooting the target.

I suggest you switch to either SmoothStep or an Animation curve where you can customize the interpolation as you like (e.g. add overshooting and bouncing effects).

Here is an example where smoothTime is much more precise

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;
    }
}

As a side note, I suggest that you use Quaternion.Slerp instead of lerp to get a nicer looking interpolation and feel free to use Time.deltaTime in corountines - it works just fine.

CodePudding user response:

You don't have to use smooth damp to calculate lerpFactor. Lerp factor is simply the ratio of elapsed time and duration. So if you want your animations in one second;

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;
    }
}
  • Related