Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

help with float lerp

Discussion in 'Scripting' started by melonhead, Apr 8, 2020.

  1. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    i am trying to lerp between 2 float values with a smooth start and a smooth end, i have tried mathf.lerp and smoothstep, smoothstep seems to start off faster and then go really slow at the end and mathf lerp also only seems to slow at the end and not gradually speed up at the start

    grateful for any help
     
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    8,988
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    are you working with quaternions or with positions?
    because you can't work with quaternions like that.

    lerp will linearly transition from start to end
    smoothstep will do this instead

    make sure your t is in range 0.0 to 1.0
     
  4. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    here is code thanks, can not get smooth at start and end
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MATHFLERPTEST1 : MonoBehaviour {
    6.  
    7.  
    8. public float speed = 2;
    9.  
    10. public float score = 0f;
    11. public float scoreTo = 100f;
    12.  
    13.  
    14.     void Start () {
    15.        
    16.     }
    17.  
    18.  
    19.    
    20.    
    21.     void Update () {
    22.  
    23.  
    24.  
    25.         score = Mathf.SmoothStep(score, scoreTo, Time.deltaTime*speed);
    26.  
    27.  
    28.  
    29.  
    30.      Debug.Log("Value " + score);
    31.  
    32.  
    33.     }
    34. }
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    You cannot use Time.deltaTime in a lerp.
    You're mixing units [frame/second] with something that is essentially just a unitless ratio (%).

    You need to convert/rephrase your lerp into units that are bound to time and work with that.
    The easiest way to achieve this would be to define what is the absolute time interval you're working with.

    Let's say totalTime is 2 seconds.
    Then you get your unitless ratio if you get your currentTime / totalTime, and synchronize some other value against this.

    Code (csharp):
    1. // public and settable from inspector
    2. public float startScore = 0f;
    3. public float endScore = 100f;
    4.  
    5. // settable from inspector
    6. [SerializeField] [Range(0.001f, 5f)] float totalLerpTime = 2f;
    7. [SerializeField] bool animate = false;
    8.  
    9. // private
    10. float _currentTime = 0f;
    11.  
    12. void Update() {
    13.   if(animate) {
    14.     float ratio = Mathf.Min(1f, _currentTime / totalLerpTime);
    15.     score = Mathf.SmoothStep(startScore, endScore, ratio);
    16.     Debug.Log(score);
    17.     animate = ratio < 1f;
    18.     if(animate) _currentTime += Time.deltaTime;
    19.   }
    20. }
    21.  
    22. // usable from inspector
    23. public void Reset() {
    24.   _currentTime = 0f;
    25.   animate = false;
    26. }
    27.  
    28. // usable from code
    29. public void Run(float time) {
    30.   Reset();
    31.   totalLerpTime = time;
    32.   animate = true;
    33. }
     
    Last edited: Apr 8, 2020
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Alternative way, if you're not sure what's what
    Code (csharp):
    1. void Update() {
    2.  
    3.   if(animate) { // process the passage of time
    4.  
    5.     // compute the ratio
    6.     var ratio = _currentTime / totalLerpTime;
    7.  
    8.     // check the ratio (we don't want to overshoot 100%)
    9.     if(ratio > 1f) ratio = 1f;
    10.  
    11.     // actual job (syncing and interpolating the other value)
    12.     score = Mathf.SmoothStep(startScore, endScore, ratio);
    13.     Debug.Log(score);
    14.  
    15.     // getting ready for the next step
    16.     if(ratio < 1f) { // we're not there yet
    17.       _currentTime += Time.deltaTime;
    18.     } else { // we're there
    19.       animate = false;
    20.     }
    21.  
    22.   }
    23.  
    24. }
     
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    If you want to work with speed instead, you just have to calculate totalLerpTime from this.
    If velocity V = st then t = V/s , so

    oops, got that swapped
    If velocity
    V = s/t
    then
    t = s/V
    , so
    Code (csharp):
    1. totalLerpTime = Mathf.Abs(endScore - startScore) / speed;
     
    Last edited: Apr 9, 2020
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I.e.
    Code (csharp):
    1. public void RunFor(float time) {
    2.   Reset();
    3.   totalLerpTime = time;
    4.   animate = true;
    5. }
    6.  
    7. public void RunAtSpeed(float speed) {
    8.   RunFor(Mathf.Abs(endScore - startScore) / speed);
    9. }
     
  9. Alvarezmd90

    Alvarezmd90

    Joined:
    Jul 21, 2016
    Posts:
    149
    Use slerp. It's spherical interpolation instead of linear.
    Or download a free copy of Lean-tween on the asset store. It's absolutely genius.
     
  10. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    Thanks for all the replies, i am sure i can get it working now i have all the helpfull stuff above, thanks again
     
    Alvarezmd90 likes this.
  11. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    i am using the orionsyndrome script but there still dont seem to be much smoothing at start and end, it only seems to start to slowdon when the score reaches 99 can it be made to gradually speed upto about a quarter then slow down in the last quarter
     
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    you can use AnimationCurve if you don't like what you're getting with SmoothStep

    Code (csharp):
    1. // public and settable from inspector
    2. public float startScore = 0f;
    3. public float endScore = 100f;
    4.  
    5. // settable from inspector
    6. [SerializeField] AnimationCurve curve; // tweak this in the inspector
    7. [SerializeField] [Range(0.001f, 5f)] float totalLerpTime = 2f;
    8. [SerializeField] bool animate = false;
    9.  
    10. // private
    11. float _currentTime = 0f;
    12.  
    13. void Update() {
    14.   if(animate) {
    15.     float ratio = Mathf.Min(1f, _currentTime / totalLerpTime);
    16.     score = Mathf.Lerp(startScore, endScore, curve.Evaluate(ratio));
    17.     Debug.Log(score);
    18.     animate = ratio < 1f;
    19.     if(animate) _currentTime += Time.deltaTime;
    20.   }
    21. }
    22.  
    23. // usable from inspector
    24. public void Reset() {
    25.   _currentTime = 0f;
    26.   animate = false;
    27. }
    28.  
    29. // usable from code
    30. public void RunFor(float time) {
    31.   Reset();
    32.   totalLerpTime = time;
    33.   animate = true;
    34. }
    35.  
    36. // speed is assumed as uniform throughout
    37. // in order to compute the total duration
    38. public void RunAtSpeed(float speed) {
    39.   RunFor(Mathf.Abs(endScore - startScore) / speed);
    40. }
     
  13. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    In general, animation tweening is a large topic, I could also give you many functions to customize this behavior to your heart's content, but this would (probably) only confuse you, and it all boils down to only two things anyway, using AnimationCurve to manually draw the desired slowdowns and speedups, or getting a fully-featured tweening library as recommended before by mgear and Alvarezmd90
     
  14. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Also depending how many of these you wish to run at the same time, I'd propose setting enabled to false, instead of using animate variable. This is a better way of ensuring Update won't be called at all, if it's not needed.

    I.e.
    Code (csharp):
    1. public class MyTweeningTest : MonoBehaviour {
    2.  
    3.   public float startScore = 0f;
    4.   public float endScore = 100f;
    5.  
    6.   // private but inspectable
    7.   [SerializeField] AnimationCurve curve;
    8.   [SerializeField] [Range(0.001f, 5f)] float totalTime = 2f;
    9.  
    10.   // private
    11.   float _time = 0f;
    12.  
    13.   void Awake() {
    14.     Reset();
    15.   }
    16.  
    17.   void Update() {
    18.     float ratio = Mathf.Min(1f, _time / totalTime);
    19.     float score = Mathf.Lerp(startScore, endScore, curve.Evaluate(ratio));
    20.     Debug.Log(score);
    21.     enabled = ratio < 1f;
    22.     if(enabled) _time += Time.deltaTime;
    23.   }
    24.  
    25.   // usable from inspector
    26.   public void Reset() {
    27.     _time = 0f;
    28.     enabled = false;
    29.   }
    30.  
    31.   // usable from code
    32.   public void RunFor(float time) {
    33.     Reset();
    34.     totalTime = time;
    35.     enabled = true;
    36.   }
    37.  
    38.   public void RunAtSpeed(float speed) {
    39.     RunFor(Mathf.Abs(endScore - startScore) / speed);
    40.   }
    41.  
    42. }
     
    Last edited: Apr 9, 2020
  15. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    I have a small problem now, i have the following code that when run in update is fine but i will need to use it in a function so i need a while loop, but it is making unity freeze, can anyone show me the right way to use the while loop with the code i have below, thank you
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class ANIMATIONCURVE17 : MonoBehaviour {
    6.  
    7.  
    8.  
    9.  
    10.  
    11.  
    12.  
    13.     private static float score;
    14.     private static float scoreStart = 0f;
    15.     private static float scoreEnd = 50f;
    16.  
    17.  
    18.     [SerializeField] AnimationCurve curve;
    19.     [SerializeField] [Range(0.001f, 50f)] float totalLerpTime = 20f;
    20.  
    21.     private bool animate = true;
    22.    
    23.     // private
    24.     float _currentTime = 0f;
    25.  
    26.  
    27.     void Start(){
    28.     Runner();
    29.     }
    30.  
    31. //     void Update() {
    32.  
    33.          void Runner() {
    34.  
    35.  
    36.     //    if(score !=scoreEnd) {
    37.  
    38.             while(score !=scoreEnd) {
    39.  
    40.             float ratio = Mathf.Min(1f, _currentTime / totalLerpTime);
    41.  
    42.                 score = Mathf.Lerp(scoreStart, scoreEnd, curve.Evaluate(ratio));
    43.  
    44.                 Debug.Log(" SCORE  " + score);
    45.  
    46.             animate = ratio < 1f;
    47.  
    48.         if(animate) _currentTime += Time.deltaTime;
    49.  
    50.    
    51.         }
    52.         return;
    53.     }
    54. }
     
  16. Alvarezmd90

    Alvarezmd90

    Joined:
    Jul 21, 2016
    Posts:
    149
    The 'return'.. is it inside the while loop? It doesn't seem to be the case as far as I can tell.
    You ALWAYS require to break out of a loop, otherwise crashes will occur 100%.
     
    Last edited: Apr 11, 2020
  17. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    if i move it into the bottom of the loop , the lerp wont run, this is why i need help, i just cant seem to get it right
     
  18. Alvarezmd90

    Alvarezmd90

    Joined:
    Jul 21, 2016
    Posts:
    149
    Then you have to create a for loop to go through until the lerp is done.
    Code (CSharp):
    1. for (float t = 0.0f; t < 1.0f; t += Time.deltaTime / aTime)
    2. {
    3.      ValueToChange = Mathf.Lerp(OldValue, TargetValue, t)
    4. }
     
  19. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    @Alvarezmd90 I'm positive he doesn't want to do that.

    Why do you need
    while
    @melonhead?
    Wasn't the solution good enough the way it was?
    If not, you should ask about the thing that bothers you.

    Thankfully I was the creator of the script, so it's better to go to the root of the problem.
     
  20. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Ah you basically switched Update into your own function. Well, you can't do that.
    You see, Unity will internally call Update each frame, and this is how that script of mine works. You can't do that with your own functions.

    Well you can, but you'd still end up calling them from within Update, it's the main thing that gets called from the underlying Unity engine, and the only reliable way to update your game in sync with the engine itself.

    The question is what about Update you find non-intuitive so that you have to call your own method 'Runner' from Start?
     
  21. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Sorry if I sound like I'm in a discontinuity, I probably am, because I respond to a lot of threads, and I used this example more than once (in the meanwhile). I've just noticed this was the original thread where I gave this solution.

    Anyway, what happens when you use the script as is? And what would you like to add?
     
  22. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    it works perfectly when it is in update, but for the project i am doing i would need it to be called as a custom function or a co routine, i have tried both, but i need to put the lerp into a loop to keep it running until it is complete which is why i tried the while loop but i can not get it to work, why wont it work in a custom function with a loop? outside of update
     
  23. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I think it would be the best if you'd describe the entire problem, so that I can approach it in a more general way. Perhaps you need a co-routine, perhaps you don't, let's figure it out together. Perhaps you could learn something about how to tame the Unity's MonoBehaviour system without circumventing it, or perhaps there is a genuine reason why you might truly need a co-routine. I'll try to help you solve this either way, but let's try and focus on the actual root of the problem, not its symptoms.
     
  24. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    Hi, thanks for help so far, i'l try to explain, basically it works great just as i wanted when in the update, but i really need to be able to call it when i need it to run through, i know it prob is not best practice but this is how i need it to work in my project, i have some complex objects and most coding is now done and would be just easier to incorperate a new co routine or function call rather than needing more work to incorperate it into one of the existing scripts, its just to save me a lot more problems, thank you, if there is any way this could be made to run in a coroutine or function call would help no end. cheers
     
  25. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Yeah well the easiest way to turn this into a function call is to turn it into a situation like this. (And this is exactly why I made you RunFor and RunAtSpeed, otherwise what would be the point?)

    Take one object on your scene that is meant to be the GUI driver, or just make an empty one.
    Have two scripts on it. Let the first script be your own script that already does other things (let's call it MyActualScript for the reference).

    The second script should be MyTweening (same one as in post #14), that is modified to do something meaningful, like this.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3.  
    4. public class MyTweening : MonoBehaviour {
    5.  
    6.   public float startScore = 0f;
    7.   public float endScore = 100f;
    8.  
    9.   // private but inspectable
    10.   [SerializeField] AnimationCurve curve;
    11.   [SerializeField] [Range(0.001f, 5f)] float totalTime = 2f;
    12.  
    13.   // private
    14.   float _time = 0f;
    15.   Text _target;
    16.  
    17.   void Awake() {
    18.     Reset();
    19.   }
    20.  
    21.   void Update() {
    22.     float ratio = Mathf.Min(1f, _time / totalTime);
    23.     float score = Mathf.Lerp(startScore, endScore, curve.Evaluate(ratio));
    24.     if(_target != null) {
    25.       _target.text = Mathf.RoundToInt(score).ToString();
    26.     } else {
    27.       Debug.Log(score);
    28.     }
    29.     enabled = ratio < 1f;
    30.     if(enabled) _time += Time.deltaTime;
    31.   }
    32.  
    33.   // usable from inspector
    34.   public void Reset() {
    35.     _time = 0f;
    36.     enabled = false;
    37.   }
    38.  
    39.   public void SetTarget(Text target) {
    40.     _target = target;
    41.   }
    42.  
    43.   // usable from code
    44.   public void RunFor(float time) {
    45.     Reset();
    46.     totalTime = time;
    47.     enabled = true;
    48.   }
    49.  
    50.   public void RunAtSpeed(float speed) {
    51.     RunFor(Mathf.Abs(endScore - startScore) / speed);
    52.   }
    53.  
    54. }
    Now, in your MyActualScript, you simply do
    Code (csharp):
    1. private int _lastPlayerScore;
    2.  
    3. public void ChangeScore(Text scoreTextUI, int newScore, float speed = 5f) {
    4.   // basically you fetch the other script
    5.   var scoreTween = gameObject.GetComponent<MyTweening>();
    6.   // and then configure it
    7.   scoreTween.SetTarget(scoreTextUI);
    8.   // we use the last score as our starting point
    9.   scoreTween.startScore = (float)_lastPlayerScore;
    10.   // and the new score as our ending point
    11.   _lastPlayerScore = newScore;
    12.   scoreTween.endScore = (float)_lastPlayerScore;
    13.   // and tell it to start animation
    14.   scoreTween.RunAtSpeed(speed);
    15. }
    Call this locally in that same script whenever you need the score to be animated.
    And this is how you tame MonoBehaviour system of updates to do what you want without having to use coroutines.

    Now the only issue is when you call this too frequently, as the subsequent calls will reset the previous animation, and that won't look nice.

    This has to be handled differently inside the actual tweening logic. Tell me if you need this as well.
     
    Last edited: Apr 12, 2020
  26. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    thanks but just got it working in a function, i will try the code above also see which i like to use

    thank you
     
  27. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    You're welcome.
    I had a typo btw, updated the last code box.
     
  28. melonhead

    melonhead

    Joined:
    Jun 3, 2014
    Posts:
    603
    cheers will give it a go, thanks again for the help