想要在 Unity 中讓物體進行拋物線運動,平常的我可能會直接用 Rigidbody 來施加力,讓系統自己去運算物理的大小事。但有時候我只是要做個「看起來」是拋物線運動的效果,沒有必要弄到非常真實,就可以套個曲線公式來施作。
原理
我們最常看到的貝茲曲線是只有一個原點、一個終點與一個控制點的「二次方貝茲曲線」,你可以透過移動控制點來修改曲線。在推導「二次方貝茲曲線」前,必須先了解「線性貝茲曲線」,它等同於我們常用的線性插值。
線性貝茲曲線
線性貝茲曲線等同線性差值,我們現在已知 A 與 B 的座標位置,也假定總共移動所需的時間 t 為 1,t 介於 01,可以直接想像成此次移動的完成百分比 (0100%)。
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}$:
先定義由左到右的座標分別為 A、B、C 點。
分別計算 A
B 與 BC 上的點於指定 t 的時候所在的座標,可得出的 D、E 兩個座標點。將 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 的貝茲曲線座標。
1public Transform P0, P1, P2, Bt;
2
3private void Start()
4{
5 var t = 0.5f;
6 var p0 = P0.position;
7 var p1 = P1.position;
8 var p2 = P2.position;
9 Bt.position = Mathf.Pow(1 - t, 2) * p0 + 2 * t * (1 - t) * p1 + Mathf.Pow(t, 2) * p2;
10}
也可以放到 Update 中並給予時間與線段特效,以下影片為例:
完整腳本:
1using UnityEngine;
2
3public class BezierCalculator : MonoBehaviour
4{
5 public int Duration = 5;
6 public Transform P0, P1, P2, Bt;
7
8 private float _time;
9 private float _duration;
10
11 private void Update()
12 {
13 if (_duration > Duration)
14 {
15 _duration = 0;
16 }
17
18 var t = _duration / Duration;
19 Bt.position =
20 Mathf.Pow(1 - t, 2) * P0.position +
21 2 * t * (1 - t) * P1.position +
22 Mathf.Pow(t, 2) * P2.position;
23
24 _duration += Time.deltaTime;
25 }
26}