1. 程式人生 > 實用技巧 >Unity 以一定角速度轉向動態目標的旋轉方式對比

Unity 以一定角速度轉向動態目標的旋轉方式對比

1.尤拉角旋轉

public void Rotate(Vector3 eulers, [DefaultValue("Space.Self")] Space relativeTo);

就容易想到的就是transform.Rotate方法:

1 RotationObj.transform.Rotate(Vector3.up * Palstance * Time.deltaTime);

其中Palstance代表角速度。

但很快就會發現這個方法有2個很大的缺陷:

①需要利用cross值(叉積)來手動判斷是繞旋轉軸逆時針還是順時針旋轉

如果叉積為正,說明目標體在旋轉體右側,需順時針旋轉;

如果叉積為負,說明目標體在旋轉體左側,需逆時針旋轉

具體判斷如下:

 1         var cross = Vector3.Cross(RotationObj.transform.forward, offset).y;
 2         if (cross > 0)
 3         {
 4             //
 5             if (Palstance < 0)
 6             {
 7                 Palstance = -Palstance;
 8             }
 9         }
10         else if (cross < 0
) 11 { 12 // 13 if (Palstance > 0) 14 { 15 Palstance = -Palstance; 16 } 17 }

其中offset代表目標體與旋轉體座標間的向量。

②難以判斷何時應該停止旋轉,且角速度過大時很容易造成在到達目標向量附近來回鬼畜旋轉

一般的考慮是,當旋轉體的前方向向量transform.forward與offset小於一定閾值時停止旋轉,例如:

1         var angle = Vector3.Angle(RotationObj.transform.forward, offset);
2 if (angle < .1f) 3 return ;

但當角速度過快時,很容易錯過[0,0.1]這一角度範圍,但如果把範圍設定過大,有沒辦法精準對齊,於是就造成了在目標向量附近來回鬼畜旋轉的狀況;

當然了,也可以用一種非常生硬的方式來解決:

1         //基於當前角速度一幀內最大的旋轉角度
2         if (angle < Palstance * Time.deltaTime)
3         {
4             RotationObj.transform.forward = offset;
5         }

即設定另一個閾值範圍(並且這個閾值範圍最好和當前角速度正相關,可以計算出基於當前角速度一幀內最大的旋轉角度進行設定),當小於該閾值範圍時直接瞬切,因為本來就是在一幀內的角度運動,所以不會有任何違和感。

也可以考慮將判定範圍與該旋轉閾值設定為同一個。完整旋轉方式如下:

 1         //基於當前角速度一幀內最大的旋轉角度
 2         if (angle < Palstance * Time.deltaTime)
 3         {
 4             RotationObj.transform.forward = offset;
 5             return;
 6         }
 7 
 8         var cross = Vector3.Cross(RotationObj.transform.forward, offset).y;
 9         if (cross > 0)
10         {
11             //
12             if (Palstance < 0)
13             {
14                 Palstance = -Palstance;
15             }
16         }
17         else if (cross < 0)
18         {
19             //
20             if (Palstance > 0)
21             {
22                 Palstance = -Palstance;
23             }
24         }
25         RotationObj.transform.Rotate(Vector3.up * Palstance * Time.deltaTime);

上面的方式經過調整後雖然能夠實現準確轉向,但看上去並不簡單直接,那有沒有更簡潔快速的旋轉方式呢。

2.插值旋轉

Lerp(a,b,t);

旋轉朝向實際上可以認為是對transform.forward進行關於角速度的插值變化:

1 RotationObj.transform.forward = Vector3.Lerp(RotationObj.transform.forward, offset, Time.deltaTime * Palstance / angle).normalized;

Time.deltaTime/(angel/Palstance)=Time.deltaTime * Palstance / angle;

利用當前角度與角速度相除計算出當前幀率下的預計旋轉時間,隨後用當前幀率與預計旋轉時間的比值來對兩個向量進行插值。

這種方法非常簡單,但也有一個問題是沒辦法做到勻速旋轉,角色的朝向,當前幀速率和角度可能會隨時發生變化。

3.四元數旋轉

1         Quaternion q = Quaternion.LookRotation(offset);
2         RotationObj.transform.rotation = Quaternion.RotateTowards(RotationObj.transform.rotation, q, Palstance * Time.deltaTime);

四元數類中自帶朝向旋轉的方法,但需要先轉換出目標向量對應的四元數。該方式可以實現勻速率旋轉。