Saturday, March 28, 2015

Unity: Two flavours of LERP





 In this post I will show two uses of LERP as you can often see in Unity scripts.


LERP


 LERP stands for linear interpolation and it is easy way how to get "mid-points" if you need to travel from point A to point B. Let's say you need to get from point A to point B in 3 seconds. We will call these 3 seconds as "MOVEMENT_TIME". You set up some counter "_currentTime" that will increase every frame with elapsed time until you reach MOVEMENT_TIME. Your current position on every frame is then calculated like this:

newPosition = point A + (point B - point A) * (_curerntTime / MOVEMENT_TIME)

 What it does is:
  • elapsed time is changed into range 0 to 1 (_curerntTime / MOVEMENT_TIME),
  • distance between ending and starting point (point B - point A) is calculated,
  • these two values are multiplied to know how much of the distance we already traveled (if time is 0 we are still in beginning and if time is 1 we are in target position),
  • finally we add the distance to starting position (point A) to get current position
 As this is used very frequently Unity has Lerp function in its Mathf class. It simplifies whole thing to single call to:

float newValue = Mathf.Lerp(valueA, valueB, time);

 But points are not single values! Fortunately, Unity has whole bunch of Lerp functions for Vector2, Vector3, Color, ... Our position change would look like this in code:

Vector2 newPosition = Vector2.Lerp(PointA, PointB, _currentTime / MOVEMENT_TIME);



Two common usages of LERP


 Now look at following picture:

 Both, ship and zeppelin are using LERP for its movement. But, it is obvious that zeppelin is not moving linearly.

 The ship is using our previous formula: we have starting point, end point and every frame we are looking for current point between them. Here is chart with our x position with regard to time:


 Here is complete code for ship:

using UnityEngine;
using System.Collections;

public class Ship : MonoBehaviour {

    public const float MOVEMENT_TIME = 3f;

    private float _startX;
    private float _endX;
    private float _currentTime;

    void Start() {
        _startX = -1f;
        _endX = 1f;
        _currentTime = 0;
    }

    void Update() {
        // update time
        _currentTime = Mathf.Min(_currentTime + Time.deltaTime, MOVEMENT_TIME);

        // update position
        Vector3 newPosition = transform.position;
        newPosition.x = Mathf.Lerp(_startX, _endX, _currentTime / MOVEMENT_TIME);
        transform.position = newPosition;

        // check if already in destination
        if (_currentTime >= MOVEMENT_TIME) {
            // move again, but switch start and destination
            float tmpX = _startX;
            _startX = _endX;
            _endX = tmpX;

            // init timer again
            _currentTime = 0;

            // turn ship to head in right direction
            Vector3 newScale = transform.localScale;
            newScale.x *= -1;
            transform.localScale = newScale;
        }
    }
}


 Zeppelin is moving also using LERP, but instead of interpolating between start and end position, it is interpolating between current and end position. It also does not calculate time between 0 and 1. Instead it uses fixed value. This fixed value says how much of the remaining distance (from current position to target position) should be traveled. As the remaining distance is getting shorter and shorter then also the calculated distance to move in every frame is shorter and shorter, so the zeppelin is slowing down. Exactly, it does never reach target position, but it gets so close to it that this is not problem for us. The chart for movement looks like this:


 And here is complete code for zeppelin:

using UnityEngine;
using System.Collections;

public class Zeppelin : MonoBehaviour {

    public const float MOVEMENT_TIME = 3f;

    private float _endX;

    void Start() {
        _endX = 1f;
        StartCoroutine(InfiniteSwap());
    }

    void Update() {
        // update position
        Vector3 newPosition = transform.position;
        newPosition.x = Mathf.Lerp(transform.position.x, _endX, MOVEMENT_TIME * 0.5f * Time.deltaTime);
        transform.position = newPosition;
    }

    IEnumerator InfiniteSwap() {
        while (true) {
            // suspend for 3 seconds
            yield return new WaitForSeconds(MOVEMENT_TIME);

            // change end position
            _endX *= -1;

            // turn zeppelin to head in right direction
            Vector3 newScale = transform.localScale;
            newScale.x *= -1;
            transform.localScale = newScale;
        }
    }
}

 We are using coroutine here to change target position every 3 seconds. In both cases we are moving form -1 to 1 in x direction.

 The second way of using Lerp has lot of usages. For example if you want your camera move from player to focus on some object, it looks much better if you do it in this way. In Unity example projects you can see this for camera movement in Survival shooter or for alarm lights intensity in Stealth project.



1 comment: