[Unity] 貝茲曲線假彈道製作原理與語法

發表日期:
2022.03.24
/
分類:
想要在 Unity 中讓物體進行拋物線運動,平常的我可能會直接用 Rigidbody 來施加力,讓系統自己去運算物理的大小事。但有時候我只是要做個「看起來」是拋物線運動的效

想要在 Unity 中讓物體進行拋物線運動,平常的我可能會直接用 Rigidbody 來施加力,讓系統自己去運算物理的大小事。但有時候我只是要做個「看起來」是拋物線運動的效果,沒有必要弄到非常真實,就可以套個曲線公式來施作。

原理

我們最常看到的貝茲曲線是只有一個原點、一個終點與一個控制點的「二次方貝茲曲線」,你可以透過移動控制點來修改曲線。在推導「二次方貝茲曲線」前,必須先了解「線性貝茲曲線」,它等同於我們常用的線性插值。

線性貝茲曲線

線性貝茲曲線等同線性差值,我們現在已知 A 與 B 的座標位置,也假定總共移動所需的時間 t 為 1,t 介於 0~1,可以直接想像成此次移動的完成百分比 (0~100%)。

t 代表時間,A 和 B 分別代表原點與終點,C 就是結果。

先以簡單的一維空間來解釋…

假設 A = 0、B = 10,求 t = 0.2 時 C 點的位置。這時就可以用 A~B 的距離乘 t 來表示 C 的位置:

$latex C=(B-A)t&s=2$

但是這個假設是建立在原點 = 0 的情況,所以正確的式子是:

$latex C=A+(B-A)t&s=2$

用分配律整理一下,這就是「線性貝茲曲線」的公式:

$latex C=A+(B-A)t=AB-A^2t=(1-t)A+Bt&s=2$

接下來帶入剛才假設的數值,得 C = 2:

$latex C=(1-0.2)0+10\times{0.2}=2&s=2$

二次方貝茲曲線

進入主題,二次方貝茲曲線是由三條線性貝茲曲線組合出來的產物,以下方影片為例,我們將使用到 $latex \overline{AB}$、$latex \overline{BC}$ 與 $latex \overline{DE}$:

  1. 先定義由左到右的座標分別為 A、B、C 點。

  2. 分別計算 A~B 與 B~C 上的點於指定 t 的時候所在的座標,可得出的 D、E 兩個座標點。

  3. 將 D 與 E 帶入公式最後得出我們要的 F 座標:

    $latex F=(1-t)D+Et&s=2$

能用三個線性貝茲曲線算出座標後,是時候將他整理成一個公式…

首先是剛才計算 AB、 BC 線段上的 D、E 座標式子:

$latex D=(1-t)A+Bt&s=2$

$latex E=(1-t)B+Ct&s=2$

還有最終的 F 座標式子:

$latex F=(1-t)D+Et&s=2$

將 D 和 E 帶入 F 座標的式子:

$latex F=(1-t)[(1-t)A+Bt]+E[(1-t)B+Ct]&s=2$

整理後可得以下公式,t 介於 0~1 之間:

$latex F=(1-t)^2A+2t(1-t)B+t^2C , t \in[0 ,1]&s=2$


二次方貝茲曲線公式

$latex B(t)=(1-t)^2P_0+2t(1-t)P_1+t^2P_2 , t \in[0 ,1]&s=2$


語法

Unity 中的次方可使用 Mathf.Pow 來實現,最後的 Bt 座標就是指定時間點 t 的貝茲曲線座標。

public Transform P0, P1, P2, Bt;

private void Start()
{
    var t = 0.5f;
    var p0 = P0.position;
    var p1 = P1.position;
    var p2 = P2.position;
    Bt.position = Mathf.Pow(1 - t, 2) * p0 + 2 * t * (1 - t) * p1 + Mathf.Pow(t, 2) * p2;
}

也可以放到 Update 中並給予時間與線段特效,以下影片為例:

完整腳本:

using UnityEngine;

public class BezierCalculator : MonoBehaviour
{
    public int Duration = 5;
    public Transform P0, P1, P2, Bt;

    private float _time;
    private float _duration;

    private void Update()
    {
        if (_duration > Duration)
        {
            _duration = 0;
        }

        var t = _duration / Duration;
        Bt.position =
            Mathf.Pow(1 - t, 2) * P0.position +
            2 * t * (1 - t) * P1.position +
            Mathf.Pow(t, 2) * P2.position;

        _duration += Time.deltaTime;
    }
}
comments powered by Disqus