Search Unity

Resolved Problem lerping a Vector3 transform.position

Discussion in 'Scripting' started by GuirieSanchez, Nov 24, 2022.

  1. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    452
    I tried creating a simple test in order to reproduce the issue I'm experiencing: It's an object that I want to move from point A to point B.

    In order to do so, I linearly interpolate between point A and B with the
    Vector3.lerp
    function in a coroutine, as can be seen in these examples, for instance:
    Code (CSharp):
    1. float lerpDuration = 3;
    2. float startValue = 0;
    3. float endValue = 10;
    4. float valueToLerp;
    5. void Start()
    6.     {
    7.         StartCoroutine(Lerp());
    8.     }
    9. IEnumerator Lerp()
    10.     {
    11.         float timeElapsed = 0;
    12.         while (timeElapsed < lerpDuration)
    13.         {
    14.             valueToLerp = Mathf.Lerp(startValue, endValue, timeElapsed / lerpDuration);
    15.             timeElapsed += Time.deltaTime;
    16.             yield return null;
    17.         }
    18.         valueToLerp = endValue;
    19.     }

    This is the code, using the same logic:

    Code (CSharp):
    1. private IEnumerator Move()
    2.     {
    3.         float t = 0;
    4.         while (t < 1)
    5.         {
    6.             t += Time.deltaTime / _duration;
    7.  
    8.  
    9.             _objectPosition = Vector3.Lerp(_pointA, _pointB, t);
    10.             transform.position = _objectPosition;
    11.  
    12.  
    13.             yield return null;
    14.         }
    15.     }
    I coded this kind of lerping before and I had no problems so far, but this time what happens is that the object moves at a fixed speed (it takes the same time), regardless of the
    _duration
    parameter.

    In order to illustrate this, I used a value for
    _duration
    of 10 seconds and printed the
    t
    value to see if that part was working, and indeed, it is:
    t
    will increase correctly from 0 to 1 on a period of 10 seconds; however, the
    transform.position
    will go from point A to point B in about 0.2 seconds, regardless of what duration I set up.
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    Well your logic is a touch flawed, given that your
    t < 1
    will only interpolate from zero to one second, regardless of the duration you specify.

    You want to interpolate from your starting time (usually zero) to your ending time:
    Code (CSharp):
    1. private IEnumerator Move(float duration)
    2. {
    3.     float t = 0;
    4.     while (t < duration)
    5.     {
    6.         t += Time.deltaTime;
    7.         float interpolation = t / duration;
    8.         Vector3 position = Vector3.Lerp(_pointA, _pointB, interpolation);
    9.         transform.position = position;
    10.        
    11.         yield return null;
    12.     }
    13.    
    14.     //once we finish interpolating we set ourselves the final position
    15.     //due to small innacuracies when increasing by deltaTime
    16.     transform.position = _pointB;
    17. }
     
  3. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    452
    I got the same results using this code: the transform.position will go from point A to point B in about 0.2s, regardless of the
    duration
    value.

    In fact, this method is similar to one solution I tried:

    Code (CSharp):
    1. private IEnumerator Move()
    2.     {
    3.         float t = 0;
    4.         while (t < _duration)
    5.         {
    6.             t += Time.deltaTime;
    7.  
    8.             float step = Mathf.Clamp01(t / _duration);
    9.  
    10.             _objectPosition = Vector3.Lerp(_pointA, _pointB, step);
    11.             transform.position = _objectPosition;
    12.  
    13.             yield return null;
    14.         }
    15.  
    16.         transform.position = _pointB;
    17.     }
    But none of these work either...
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    Then maybe you should debug what
    _duration
    is because I can assure you the method I showed works.
     
  5. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    452
    Code (CSharp):
    1.  private IEnumerator Move(Vector3 finalPos)
    2.     {
    3.         float t = 0;
    4.         float duration = 10; // 10 seconds
    5.    
    6.         while (t < duration)
    7.         {
    8.             t += Time.deltaTime;
    9.  
    10.             float interpolation = t / duration;
    11.  
    12.             Vector3 position = Vector3.Lerp(_currentPos, finalPos, interpolation);
    13.  
    14.             transform.position = position;
    15.  
    16.             Debug.Log($"t: {t}");
    17.             Debug.Log($"duration: {duration}");
    18.             Debug.Log($"interpolation: {interpolation}");
    19.             Debug.Log($"position: {position}");
    20.  
    21.  
    22.             yield return null;
    23.         }
    24.  
    25.         transform.position = finalPos;
    26.     }
    I made this code in order to debug it and it works... Can you spot what the difference might be? I can't see any difference between the pieces of code we posted. The code posted above doesn't work, while this one does.
     
    Last edited: Nov 24, 2022
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    To make sure I wasn't crazy I threw this together:
    Code (CSharp):
    1.     using System.Collections;
    2.     using System.Collections.Generic;
    3.     using UnityEngine;
    4.  
    5. #if UNITY_EDITOR
    6.     using UnityEditor;
    7. #endif
    8.  
    9.     public class TransformLerp : MonoBehaviour
    10.     {
    11.         public Vector3 PointStart = Vector3.zero;
    12.  
    13.         public Vector3 PointEnd = Vector3.forward;
    14.  
    15.         [SerializeField, Range(0, 20)]
    16.         private float duration = 5f;
    17.  
    18.         private void Start()
    19.         {
    20.             StartCoroutine(Move(duration));
    21.         }
    22.  
    23.         private IEnumerator Move(float lerpDuration)
    24.         {
    25.             float t = 0;
    26.             while (t < lerpDuration)
    27.             {
    28.                 t += Time.deltaTime;
    29.                 float interpolation = t / lerpDuration;
    30.                 Vector3 position = Vector3.Lerp(PointStart, PointEnd, interpolation);
    31.                 transform.position = position;
    32.  
    33.                 yield return null;
    34.             }
    35.  
    36.             transform.position = PointEnd;
    37.         }
    38.     }
    39.  
    40. #if UNITY_EDITOR
    41.     [CustomEditor(typeof(TransformLerp))]
    42.     public class TransformLerpEditor : Editor
    43.     {
    44.         TransformLerp transformLerp;
    45.  
    46.         private void OnEnable()
    47.         {
    48.             transformLerp = (TransformLerp)target;
    49.         }
    50.  
    51.         private void OnSceneGUI()
    52.         {
    53.             EditorGUI.BeginChangeCheck();
    54.             transformLerp.PointStart = transformLerp.transform.position;          
    55.  
    56.             transformLerp.PointEnd = Handles.PositionHandle(transformLerp.PointEnd, Quaternion.identity);
    57.             if (EditorGUI.EndChangeCheck())
    58.             {
    59.                 EditorUtility.SetDirty(this);
    60.             }
    61.         }
    62.     }
    63. #endif
    And as I hoped it works perfectly.

    The main thing I see is that
    duration
    is local to the method, whereas in your previous examples it seemed to be local to the monobehaviour or whatever object it was in. So as I said, if you debugged
    _duration
    you may have found your problem.
     
    Last edited: Nov 24, 2022
    GuirieSanchez likes this.
  7. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    452

    Indeed, I've tested both and that (the
    _duration
    vs the local variable of the function) is what makes the difference.

    I'm not sure why it behaves like this, but it does. I'll try to investigate it.
     
  8. GuirieSanchez

    GuirieSanchez

    Joined:
    Oct 12, 2021
    Posts:
    452
    Ok, I have to correct myself:

    I tested with local var to the function, and local var to the Monobehaviour, and both work as expected, so the issue might still be something else
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,925
    That's what I was hinting at, more or less. Some part of your logic that isn't setting _duration to what you expect, or is setting it when you don't want it to.

    In any case coroutines are best used in 'fire and forget' ways, so I would generally always keep the duration scoped to the method itself.
     
    Last edited: Nov 24, 2022
    Bunny83 and GuirieSanchez like this.
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,000
    Well, his code shoulw work fine as he's using:

    Code (CSharp):
    1. t += Time.deltaTime / _duration;
    So "t" goes from 0 to 1 in "_duration" of seconds.

    Though I agree that you should keep necessary values in local variables inside the coroutine. However since t actually goes from 0 to 1 in 10 seconds as he mentioned in the original post, the only left over variables are pointA and B. In order to do a linear lerp those two positions have to be constant during the lerping. We don't know where and how they are set. Though they must not be changed during the lerp, otherwise you don't get a linear interpolation. A common "abuse" of lerp is to feed the current position as starting position. However this would result in a non-linear ease out beheaviour where it moves fast at the beginning and slower at the end.

    So my guess would be that those two points are actually changing in between. So maybe keep them also in local variables to the coroutine. Though of course it depends on the actual usecase.
     
    GuirieSanchez likes this.