Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Lerp/Coroutine - Timing is off

Discussion in 'Scripting' started by TaleOf4Gamers, Jul 22, 2017.

  1. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    Hey,
    I am writing a simple and small wrapper to make lerping easier for myself in the future.
    I am trying to run the lerp over a set amount of time, in this example, 2 seconds.
    However, I knew something was off and when testing it with a stopwatch, it consistently takes 1.7s (ish).
    Its probably something to do with how I calculate the increment every frame, however I do not see it.

    Any help would be appreciated,
    Thanks.

    The Lerp I am using:
    Code (CSharp):
    1.  
    2. public static void Colour(MonoBehaviour mono, Action<Color> OnLerpUpdated, Action OnLerpCompleted, ColorLerpProperties properties)
    3. {
    4.     mono.StartCoroutine(LerpColour(OnLerpUpdated, OnLerpCompleted, properties));
    5. }
    6.  
    7. static IEnumerator LerpColour(Action<Color> OnLerpUpdated, Action OnLerpCompleted, ColorLerpProperties properties)
    8. {
    9.     float t = 0f;
    10.  
    11.     while (t < 1)
    12.     {
    13.         t += Time.deltaTime / properties.time;
    14.         OnLerpUpdated(Color.Lerp(properties.from, properties.to, t));
    15.         yield return null;
    16.     }
    17.  
    18.     OnLerpCompleted();
    19. }
    How I am calling it:
    Code (CSharp):
    1. [SerializeField] SpriteRenderer sprite;
    2.     [SerializeField] ColorLerpProperties properties = new ColorLerpProperties(Color.red, Color.green, 5f);
    3.  
    4.     diagnostics.Stopwatch watch = new diagnostics.Stopwatch();
    5.  
    6.     public void OnLerpComplete()
    7.     {
    8.         watch.Stop();
    9.         Debug.Log($"Lerp took: {watch.ElapsedMilliseconds / 1000f} seconds");
    10.         //Debug.Log($"Lerp on: {gameObject.name} complete!");
    11.         watch.Reset();
    12.     }
    13.  
    14.     public void OnLerpUpdate(Color color)
    15.     {
    16.         sprite.color = color;
    17.     }
    18.  
    19.     [ContextMenu("Start a Colour Lerp")]
    20.     private void StartLerp()
    21.     {
    22.         watch.Start();
    23.  
    24.         Lerp.Colour(
    25.             this,
    26.             OnLerpUpdate,
    27.             OnLerpComplete,
    28.             properties
    29.         );
    30.     }
    A screenshot of the inspector and console with timing:
     
  2. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
  3. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    It's got something to do with the execution form the context menu.

    Running StartLerp the normal way (from Awake, Start or as any of those by renaming it to one of them) should execute just fine over approx. 2seconds.

    I'm not quite sure why that's the case, but if you log Time.deltaTime in your update callback, you'll note a value of (in my case always ~ 0.333) just right after starting it from the context menu.
    It seems the execution from context menu is somehow scheduled and Time.deltaTime returns a corresponding value for that special execution phase.
    After all you're essentially missing that time, since it does not actually take a third of a second (or whatever value is shown in your specific case), yet it is still considered during the accumulation of the delta values.
    I'll try to take another look at it later in the evening. :)
     
    Last edited: Jul 23, 2017
  4. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    Oh wow, thanks.
    Here is my results when I run it within Start.

    Thanks, I would not of noticed that.
     
  5. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    It appears you're still missing a frame though, as you'd probably want it to finish after 2.0xxx seconds.

    If you yielded first, you'd keep the initial value during the frame in which the coroutine is started. Currently, you're kind of starting off with 0 + Time.deltaTime.
    This way, if you considered 'delta1' to be the delta time during coroutine start, you'd effectively always run 'properties.time - delta1' instead of 'properties.time'.
     
    TaleOf4Gamers likes this.
  6. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    There:

    Now thats what I would call 'close enough'. ;)
    Thanks.
     
  7. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Technically it should always be > 2s . Well, that's still okay though.
     
    TaleOf4Gamers likes this.
  8. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    I'm not sure if it's important, but OnLerpComplete will be one frame late, since you're yielding after moving the color, even if you've reached 1. I would maybe do:

    Code (csharp):
    1. while(true) {
    2.     t += Time.deltaTime / properties.time;
    3.     OnLerpUpdated(Color.Lerp(properties.from, properties.to, t));
    4.     if(t > 1f)
    5.         break;
    6.     yield return null;
    7. }
    I'm pretty sure the delay from hitting the context menu is due to the game freezing while it's open, so the deltaTime is the time since you clicked the cog.
     
  9. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    Nice, it is fixed now, but something to keep in mind in the future.
     
  10. daxiongmao

    daxiongmao

    Joined:
    Feb 2, 2016
    Posts:
    412
    I always like to add in an lerp at the end to 1.0. Otherwise with your time method you might be a little under or a little over 1. Depending on what your lerping might get weird results if > 1.
     
  11. TaleOf4Gamers

    TaleOf4Gamers

    Joined:
    Nov 15, 2013
    Posts:
    825
    Thats a valid point, havent thought of including that before.
     
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    That'd be fixed though, if he yielded first as suggested, wouldn't it?

    Color.Lerp clamps the value internally (0...1), which probably most of the implementations do. So it's gonna end up being the target value.
    Color.LerpUnclamped doesn't clamp the values and may have the described effect though.
     
  13. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Then it only starts changing color the next frame.

    Which maybe is what you want! Or maybe it doesn't matter! OP's original code changed color the same frame, I didn't want to change that.
     
  14. daxiongmao

    daxiongmao

    Joined:
    Feb 2, 2016
    Posts:
    412
    I am not sure if lerp clamps it or not. I haven't actually looked at it. I assumed it would not.
    If it does I might have to start always using unclamped. I wouldn't expect I would being paying for the extra logic to clamp it if I am controlling the range externally.

    Or maybe I have been doing to much shader work.

    I just looked and your right, vector 3 too!

    My assumption that a lerp would never clamp. And I would have to call a clamped version.
    But that's what assumptions will get you. Now to remember that if I am writing fast code needing lerp.
     
  15. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    All of Unity's Lerp functions clamp. If you have a loop that's so tight that the < 0 and > 1checks are a problem, you have a loop that's so tight that the overhead of calling a function is already a problem, and you should inline the code.

    What would Color.Lerp(Color.red, Color.green, 1.2f) mean if it wasn't clamped?
     
  16. daxiongmao

    daxiongmao

    Joined:
    Feb 2, 2016
    Posts:
    412
    Granted my statement about performance in that tight of a loop is probably over blown, but
    It was more about my realization that is clamping already than anything else.

    But in shaders they don't clamp, but there every extra instruction is important.

    A lerp is just (X * T) + (Y * (1 - T)). So that formula can be used for other things, as its just a linear interpolation, the line continues outside the 0 - 1 range. Granted this is probably not a common case and I can't come up with an example off the top of my head now.
    Since they do offer an unclamped version there is some need for it. I just figured that would of been the initail case.

    Like I assume Clamp to take (min ,max) and Clamp01 to be a simplified version already passing Clamp(x, 0, 1);
    I would of thought it would be Lerp and LerpClamped. But I was wrong. Not that I ever had any performance related reasons to investigate the speed of Lerp.
     
  17. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,168
    Lerp Unclamped exist on Math and vector functions. There it makes sense. You basically over extend. But in the context of Color, it doesn't really makes sense.

    But if the t var is greater than 1. You are basically extrapolating and not interpolating.