Friday 28 March 2014

How to use Lerp in Unity. Like a BOSS.


Hello there!

I heard you want to make videogames. I know you want to make GOOD videogames. Am I right? Then keep reading. =)

Lerp stands for Linear Interpolation. The meaning of it is very simple: you have 2 numbers and you need to find some value in the middle.


This is a LERP example with two points. It is not that bad :)


Let's see how it works. Usually Lerp has 3 parameters. Lets see some example:


// Example values
value1 = 10;
value2 = 20;
result = Mathf.Lerp(value1, value2, 0.5f);
// result == 15

What is so great about Lerp is that it can be used with pretty much anything! Vectors, Rotations (Quaternions), even Colors!

I made an small example where you can see different effects that can be achieved using different kinds of Lerp:





"What time is it?" - Lerp time!





As you can see, the characters move from left to right, at the same time. But they do not do it in the same way. That is because there is at least two ways of effectively use lerp to "animate" elements of the game.

The first kind of lerp is what I call "Smooth". Smooth is fast at the beginning but slows down as it arrive to the destination point. The result is good looking. It is desired that all the element of your game that have a fast change in some of their attributes do this kind of transition. 


void Update(){
    // From actual position to destination with "constant" t
    transform.position = Vector3.Lerp(transform.position,
                                      destination.position,
                                      speed * 3.0f * Time.deltaTime);
}

The line of code above is the heart of the Lerp. The parameters are the actual position and the destination. The function is called every frame and every time moves the game object. Just like in the fable of the hare and the turtle, this is the hare. Every time walks a portion of the remaining path. This also means that it never really arrive to destination.



The second kind of lerp is the "Constant". Constant gives the feeling of constant speed though all the animation. The result also looks nice and is useful when you want to animate characters, vehicles and things that should not happen too fast. Is also very useful to define way points. Imagine a tower defence game.


void Update(){
    // From position1 to position2 with increasing t
    transform.position = Vector3.Lerp(position1.position,
                                      position2.position,
                                      speed * timer);

    // Increase or decrease the constant lerp timer
    if (destination == position1){
        // Go to position1 t = 0.0f
        timer = Mathf.Clamp(timer - Time.deltaTime, 0.0f, 1.0f/speed);
    } else {
        // Go to position2 t = 1.0f
        timer = Mathf.Clamp(timer + Time.deltaTime, 0.0f, 1.0f/speed);
    }
}

This code is a little bit more complicated that the other. The starting position and destination are always the same. What changes is the third parameter that increases or decreases with time depending on the destination. The value is clamped between 0.0f and 1.0f. You can give different values to Lerp but its going to clamp it anyway. This is the turtle of the fable. It will always arrive to destination.


As I said you can also Lerp many other things. In the example the arrow uses a "Smooth" Lerp for the rotation and the color


void Update (){
    // Lerp color
    spriteRenderer.color = Color.Lerp(spriteRenderer.color,
                                      destinationColor,
                                      speed * Time.deltaTime);

    // Lerp rotation
    transform.rotation = Quaternion.Lerp(transform.rotation,
                                         destinationRotation,
                                         speed * 4.0f * Time.deltaTime);
}



As you can see. Both kinds of Lerp can come in handy. It is curious because the original use for Lerp is the constant one, but people mostly use the smooth one for one reason or another. Also because it can be achieved with only one line of code.

In general, is a fairly common mistake in beginner videogame developers that elements in the games  are either in position A or B. For us, programmers, is much easier the world were things are never in a "travelling" or "transition" status. But the truth is that you better start to remove that idea from your mind, as things that happens instantaneously harm the player experience.


"May the Lerp be with you" - A wise man

Here is the link to the complete example posted on github.

Here are the links to the official documentation: numbers, vectors, colors, rotations.


20 comments:

  1. Another interesting article and i think is very useful.
    I like your blog. I think i'll learn a lot from it.

    ReplyDelete
    Replies
    1. Thanks for the comment! It fills my energy bar! =D

      Delete
  2. Nice article, I learnt a lot from it!

    In the "constant" lerp case, I think it would be more advanced if you multiply the speed when calculating timer instead in lerp function.

    timer = Mathf.Clamp(timer + Time.deltaTime * speed, 0.0f, 1.0f);

    Now you can also use timer as a variable that tells you exactly when you're in your destination. In this case, its when timer == 1.0f.

    final code:

    void Update(){
    // From position1 to position2 with increasing t
    transform.position = Vector3.Lerp(position1.position,
    position2.position,
    timer);

    // Increase or decrease the constant lerp timer
    if (destination == position1){
    // Go to position1 t = 0.0f
    timer = Mathf.Clamp(timer - Time.deltaTime * speed, 0.0f, 1.0f);
    } else {
    // Go to position2 t = 1.0f
    timer = Mathf.Clamp(timer + Time.deltaTime * speed, 0.0f, 1.0f);
    }
    }

    ReplyDelete
    Replies
    1. Well said! :)

      Indeed, there are more advanced ways of implementing this kind of behaviours.
      For example, the "constant" lerp should be inside a coroutine compulsory. It makes everything cleaner, plus you get the beneficts of yielding.

      Thanks for the comment! :)

      Delete
  3. great articles more please :D

    ReplyDelete
    Replies
    1. More articles always on the way... Thanks! ;)

      Delete
  4. Nailed it! Simple and well written

    ReplyDelete
  5. What you call a "constant lerp" is the correct use of the Lerp function; the "smooth lerp" is simply incorrect. I've written a blog article on why that is (and the problems the incorrect method causes) here:
    http://www.blueraja.com/blog/404/how-to-use-unity-3ds-linear-interpolation-vector3-lerp-correctly

    ReplyDelete
    Replies
    1. Thanks BlueRaja!
      Although I agree with you that smooth Lerp is not the intended use of the function, I don't see any problems in the smooth Lerp. I guess that if you want you can invent some... In general, sometimes it does exactly what you want, so why call it "incorrect"? :D
      For example, this Lerp is frame rate independant. You make a mistake if in FixedUpdate you use Time.deltaTime instead of Time.fixedDeltaTime. This is when you get spooky effects :)
      Happy learning! ;)

      Delete
  6. Hi Juan. This is really good article and helped me a lot to understand details of lerp. But I have a little problem and I would be glad if you can help me.

    void Update ()
    {
    if (destination == position1)
    {
    // Go to position1 t = 0.0f
    timer = Mathf.Clamp(timer - Time.deltaTime, 0.0f, 1.0f);
    }
    else if(destination == position2)
    {
    // Go to position2 t = 1.0f
    timer = Mathf.Clamp(timer + Time.deltaTime, 0.0f, 1.0f);
    }

    transform.position = Vector3.Lerp(position1.position, position2.position, speed * timer);

    }

    This is the update part of my code and above the code is same as yours. I remove the smooth part because I want to use constant lerp. When i clicked the mouse, from position1 to position2 lerp is really good and immediately but after reaching position2, when i clicked, unity waits a little(i think 0.3s - 0.5s) and then starting to lerp. First lerping is always as exactly as it should be but the second is laggy. What could be the problem here ?

    ReplyDelete
    Replies
    1. Hello Alper Silistre and thanks a lot for your comment :)

      I checked the code and I am sure it should work as expected, there are some things that could happen but the problem is not in this code.
      Here I have some ideas:
      - Make sure there is no null references after you delete stuff
      - Make sure the values for destination1 and destination2 are correct
      - Be careful if you modify any value of the script from outside at runtime unexpected things can happen
      - If you modified the timer to something different than 1.0f make sure you make the appropiate changes in the script
      - Try to remove this "if(destination == position2)" as is not neccesary
      - Try to Lerp before calculating the time "
      transform.position = Vector3.Lerp(position1.position, position2.position, speed * timer);" on the beggining of update

      Probably the cause is some messed up value, but I cannot tell you what. Try to reproduce it from the beggining and tell me how you ended up with this problem.

      Hope to help,
      Juan

      Delete
    2. Hi juan, i checked everything you said but there is a really funny bug out there :D in speed = 1 everything work as expected but when i make it 3, from position2 to position1 is laggy. As I said its waiting a little and then starting to move. I tried other numbers like speed=2,4,5 but its always lag. Why could it be maybe you can guess something. Thank you..

      Delete
    3. There you go! :D

      The script was not prepared for constant lerp with speed different than "1.0f", thanks to your feedback I already fixed it.

      This is what you should look: "1.0f/speed":
      // Increase or decrease the constant lerp timer
      if (destination == position1){
      // Go to position1 t = 0.0f
      timer = Mathf.Clamp(timer - Time.deltaTime, 0.0f, 1.0f/speed);
      } else {
      // Go to position2 t = 1.0f
      timer = Mathf.Clamp(timer + Time.deltaTime, 0.0f, 1.0f/speed);
      }

      Post and repo updated with the fix.

      Thanks a lot! :D

      Delete
    4. Thanks that's work :D Could you explain how this code work. Thank you.. :D

      Delete
    5. It's very simple. The amount of time that it takes to arrive to destination is the inverted of the speed.

      So assuming: at speed = 1 it takes 1 second to arrive to destination.
      At speed = x it will take 1/x seconds to arrive to destination.

      Cheers! :)

      Delete
    6. Yea it's true, thanks for explanation :D

      Delete
  7. Thank you for the submitted instruction! I'd like to let you know that it's easy to follow and does not require any efforts to fix the bug.

    ReplyDelete
  8. I am not actually sure I have easily understood everything and feel comfortable when reading the post, but I'd be happy to see more articles soon!

    ReplyDelete