iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
自我挑戰組

Unity 基本功能實作與日常紀錄系列 第 12

Day12: Use Lerp move A to B by Time or Speed!

  • 分享至 

  • xImage
  •  

在Unity 中我們會希望可以平滑的移動某物件到環境的某個位置,那我們可以透過線性插植 Lerp 的方式來實現。我這邊就不太做一些Lerp 數學細節上的說明,但必須要注意到Lerp 範圍為 0~1,這是非常重要的概念之後會做簡短說明。但我今天要透過 Lerp 來實現從起點 A 到終點 B,以自己設定速度與時間完成該物件直線的移動。

今天我們會有兩個簡單得實作,由 Speed 控制 Lerp 讓物件移動的方法,以及由 Time 控制 Lerp 使物件移動的方法。

建置場景

  1. 首先我們設定兩個空白物件,為起點與終點。StartPoint 與 EndPoint 。

  2. 因為在 Scene 中無法顯示空白物件,所以說這邊我們可以新增 Tag。 新增的方式在左手邊的方塊中。我們點選自己喜歡的顏色。

  3. 接著請務必記得開啟 Gizmo 來顯示我們的 Tag,若沒開啟 Scene 的Gizmo 就無法看到我們預設的Tag。

開啟後就會看到當前空白物件的位置。

  1. 接著就自行決定想要的起點與終點位置。

  2. 接著我們創建一個火車的 Prefab。隨便做做即可。設定 Prefab 後就可以先將Scene 的物件給刪除,目前不太需要。

  3. 新增一個空白物件叫做 LerpManagement ,後續新增一個文本,LerpMovement.cs。

開始撰寫文本

  1. 首先我想做的功能是可以隨機切換要透過 Time 還是 Speed 來控制 Lerp 驅使物件移動的效果,這邊我先撰寫兩種不同的 IEnumerator 型別的方法,目的是為了要讓後續 StartCoroutine 可以進行呼叫。這邊若不熟悉什麼是 StartCoroutine 引入該IEnumerator 的方法,可以去參考Unity 網站的Documetation ,這邊提供連結 [https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html]

  2. 首先撰寫透過 Speed 來控制Lerp 移動物件的 IEnumerator 方法。

(StartPoint) 0 ~ 1 (EndPoint)
  • 首先要引入三個變數,從哪裡到哪裡,以及要傳入的速度。
public IEnumerator TrainLerpBySpeed(Vector3 _from, Vector3 _to, float _speed)
  • 宣告三個變數,紀錄當前時間、以及距離、還有線性插值 0 ~ 1的紀錄
float startTime = Time.time;
float disLength = Vector3.Distance(_from, _to);
float frac = 0f;
  • 線性插值過程,首先請注意到 subDististance = 速度 * (時間差) ,這邊的時間差就是 Time.time - startTime ,接下來因為線性插值是 0~ 1所以計算出來的距離要除以總長度來觀察 0~1 目前在哪一個區間。
 while(frac < 1.0f)
        {
            float subDistance = _speed * (Time.time - startTime);
            frac += subDistance/disLength;         // because Lerp is from 0~1, so distance/disLength
            AirplaneObj.position = Vector3.Lerp(_from, _to, frac);
            yield return null;
        }
  • 使用 Speed 作為線性插值 IEnumerator 方法,整個程式碼
// distance = speed * deltaT
public IEnumerator TrainLerpBySpeed(Vector3 _from, Vector3 _to, float _speed)
    {
        float startTime = Time.time;
        float disLength = Vector3.Distance(_from, _to);
        float frac = 0f;
        while(frac < 1.0f)
        {
            float subDistance = _speed * (Time.time - startTime);
            frac += subDistance/disLength;         // because Lerp is from 0~1, so distance/disLength
            AirplaneObj.position = Vector3.Lerp(_from, _to, frac);
            yield return null;
        }
    }
  1. 撰寫透過 Time 來控制Lerp 移動物件的 IEnumerator 方法。
(time = 0) 0 ~ 1 (setTime)
  • 首先要引入三個變數,從哪裡到哪裡,以及要傳入的運行總時間。
public IEnumerator TrainLerpByTime(Vector3 _from, Vector3 _to, float _time)
  • 透過時間進行Lerp 線性插值很簡單,畢竟Unity 內部就內建 Time.deltaTime 的型態,主要就是透過加上該 DeltaTime並除以總時間來確認在線性插值的區間。
var sub_dur = 0f;
        while(sub_dur <= _time)
        {
            sub_dur += Time.deltaTime;
            float frac = sub_dur / _time;
            AirplaneObj.position = Vector3.Lerp(_from, _to, frac);
            yield return null;
        }
  • 使用 Time作為線性插值 IEnumerator 方法,整個程式碼
public IEnumerator TrainLerpByTime(Vector3 _from, Vector3 _to, float _time)
    {
        var sub_dur = 0f;
        while(sub_dur <= _time)
        {
            sub_dur += Time.deltaTime;
            float frac = sub_dur / _time;
            AirplaneObj.position = Vector3.Lerp(_from, _to, frac);
            yield return null;
        }
    }
  • 接下來我想要控制該物件可以來回移動,所以說一開始若在起點,就往終點移動,若在終點則就會往起點移動。這邊會根據不同控制 Lerp 的方式傳入各種不同的值。
void CheckArriveBySpeed(float s)
    {
        Debug.Log("Set Speed to Lerp Move!");
        if(endB.position == AirplaneObj.position)
        {
            Debug.Log("Go to start position");
            AirplaneObj.LookAt(startA.position);
            StartCoroutine( TrainLerpBySpeed(endB.position, startA.position, s));
        }else if(startA.position == AirplaneObj.position)
        {
            Debug.Log("Go to end position");
            StartCoroutine( TrainLerpBySpeed(startA.position, endB.position, s));
            AirplaneObj.LookAt(endB.position);
        }
    }


    void CheckArriveByTime(float t)
    {
        Debug.Log("Set Time to Lerp Move!");
        if(endB.position == AirplaneObj.position)
        {
            Debug.Log("Go to start position");
            AirplaneObj.LookAt(startA.position);
            StartCoroutine(TrainLerpByTime(endB.position, startA.position, t));
        }else if(startA.position == AirplaneObj.position)
        {
            Debug.Log("Go to end position");
            StartCoroutine(TrainLerpByTime(startA.position, endB.position, t));
            AirplaneObj.LookAt(endB.position);
        }
    }
  1. 根據按下鍵盤的鍵來決定要使用 Time 還是 Speed 做為控制 Lerp 對象。
void Update() 
    {
        if(Input.GetKeyDown("k"))
        {
           CheckArriveByTime(setTime);
        }

        if(Input.GetKeyDown("l"))
        {
            CheckArriveBySpeed(setSpeed);
        }
       
        
    }
  1. 請務必在執行開始時將該物件先放在起點,若沒這樣做就無法有效執行。 畢竟後續判斷若一直沒有再該兩點上就無法移動。
void Start() 
    {
        AirplaneObj.position = startA.position;     // when active the system then set the new position to zero
    }
  1. 完整的程式碼如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LerpMovement : MonoBehaviour
{
    public Transform startA, endB;
    public Transform AirplaneObj;
    [SerializeField]public float setSpeed = 10f, setTime = 20f;
    bool setting = true;

    void Start() 
    {
        AirplaneObj.position = startA.position;     // when active the system then set the new position to zero
    }
    
    void Update() 
    {
        if(Input.GetKeyDown("k"))
        {
           CheckArriveByTime(setTime);
        }

        if(Input.GetKeyDown("l"))
        {
            CheckArriveBySpeed(setSpeed);
        }
       
        
    }

    void CheckArriveBySpeed(float s)
    {
        Debug.Log("Set Speed to Lerp Move!");
        if(endB.position == AirplaneObj.position)
        {
            Debug.Log("Go to start position");
            AirplaneObj.LookAt(startA.position);
            StartCoroutine( TrainLerpBySpeed(endB.position, startA.position, s));
        }else if(startA.position == AirplaneObj.position)
        {
            Debug.Log("Go to end position");
            StartCoroutine( TrainLerpBySpeed(startA.position, endB.position, s));
            AirplaneObj.LookAt(endB.position);
        }
    }

    void CheckArriveByTime(float t)
    {
        Debug.Log("Set Time to Lerp Move!");
        if(endB.position == AirplaneObj.position)
        {
            Debug.Log("Go to start position");
            AirplaneObj.LookAt(startA.position);
            StartCoroutine(TrainLerpByTime(endB.position, startA.position, t));
        }else if(startA.position == AirplaneObj.position)
        {
            Debug.Log("Go to end position");
            StartCoroutine(TrainLerpByTime(startA.position, endB.position, t));
            AirplaneObj.LookAt(endB.position);
        }
    }

    // distance = speed * deltaT
    public IEnumerator TrainLerpBySpeed(Vector3 _from, Vector3 _to, float _speed)
    {
        float startTime = Time.time;
        float disLength = Vector3.Distance(_from, _to);
        float frac = 0f;
        while(frac < 1.0f)
        {
            float subDistance = _speed * (Time.time - startTime);
            frac += subDistance/disLength;         // because Lerp is from 0~1, so distance/disLength
            AirplaneObj.position = Vector3.Lerp(_from, _to, frac);
            yield return null;
        }
    }

    // speed = distance / deltaT
    public IEnumerator TrainLerpByTime(Vector3 _from, Vector3 _to, float _time)
    {
        var sub_dur = 0f;
        while(sub_dur <= _time)
        {
            sub_dur += Time.deltaTime;
            float frac = sub_dur / _time;
            AirplaneObj.position = Vector3.Lerp(_from, _to, frac);
            yield return null;
        }
    }
}
  1. 接下來回到 Unity,將我們的起點、終點與移動的數值放入我們的 LerpMovement.cs 當中。

  2. 一開始我會將該 Train 設定 Position 到很遠的地方,先假裝沒有在 Camera 的範圍內。

  3. 執行後會看到下方 Console 顯示按下 K控制該時間決定 Lerp 移動,L 則是控制速度決定 Lerp 移動。

  4. 速度與時間我目前設定如下, Speed: 0.1, Time: 4

  5. 接著按下 L 可以看到物件移動極快。 Console 會顯示 "Set Speed to Lerp Move!” 以及 “Go to end position”。 代表說我們目前使用 Speed 控制與前往終點。

  6. 接著按下 K 可以看到物件移動極快。 Console 會顯示 "Set Time to Lerp Move!” 以及 “Go to start position”。 代表說我們目前使用 Time 控制與前往起點。

  7. 最後你可以加上 Trail Renderer Component 在 Train 上面讓整體移動更加有趣一點XDD。前幾天有深入說明,抱歉現在就不多說了。

  8. 執行結果如下

結論:

  1. 今天說明到 Lerp 線性插值介於0~1 之間。也透過使用 Lerp 來結合 Time, Speed 去控制該物件線性移動的方式。
  2. 今天也了解到如何要有效的控制物體的速度,並且了解時間差的計算方式。
  3. 讓物件在某兩邊來回移動也有效的完成,或許未來可以結合Line Renderer 實現一種讓火車在我們指定的路徑下移動,想到就覺得有趣。

最近越來越早起了....


上一篇
Day11: Understand Collision and Trigger
下一篇
Day13: Unity Camera Control 1
系列文
Unity 基本功能實作與日常紀錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言