iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0

我只能說這一章是我最不熟悉也最無法適應的,所以今天凌晨五點就起來開始寫文章,太有趣了。關於在 Unity 中實現旋轉的方式真的很多種,但怎樣旋轉才是最正確最穩定的一種方式,這我就真的只能說,我也不是很確定。我們知道四元數可以避免掉萬象鎖的問題,但就是難以理解也不好使用與計算,極為複雜,而使用 Euler 歐拉角雖然直觀容易使用與理解,但產生的萬象鎖且在Unity 旋轉有固定的方向性有些困擾,以及圍繞在哪個軸旋轉的問題也都需要經過思考。以下我把我使用過以及自己理解的方式與各位IT高人分享,也希望透過以下文章重新釐清旋轉的問題。之後有機會我會在更加深入討論,今天先針對一些簡單旋轉的問題來做探討。

建置環境

  1. 首先我們新增一個物體叫做 Player ,並且將Main Camera 成為其 Child。同時注意到新增一個Sphere Child obj 來顯示Player 看出的方向。

  2. 這邊我新增一個圓球叫做 TestScene。很簡單目的就是要代表一個場景的感覺,而且上面圍繞許多不同角度的圓球,紅球為前後,藍球為上下,綠球為左右。黃球為該個不同角度看出的結果。上下各30, 60 度、左右各45, 90 度。上下四個左右五個共20個點。

  3. 將新增腳本 RotateObj放置於該 Player 上面。

理解 Transform.Rotate

  1. 第一個參數為 x 軸旋轉度數,第二個為 y 軸旋轉度數,第三個為 z 軸旋轉度數,最後一個為基於自身還是世界座標旋轉。
Transform.Rotate(float angle_x, float angle_y,float angle_z, Space relativeTo = Space.Self);
  1. 第一個參數為沿著哪一軸旋轉,第二個為旋轉多少度,第三個參數式基於自身還是世界坐標系旋轉。
Transform.Rotate(Vector3 axis, float angle, Space relativeTo = Space.Self);
  1. 第一個參數沿著誰旋轉,第二個參數為基於自身還是世界坐標系旋轉
Transform.Rotate(Vector3 axis, Space relativeTo = Space.Self)

實作繞行世界座標的 X軸旋轉

  1. 繞行自身X軸進行旋轉,若沒有Parent 通常就是基於World 坐標系旋轉。 Vector3.right為該 X軸方向,Rotate 若沒有設定第二個參數就是預設為世界坐標系。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateObj : MonoBehaviour
{
    public float rotateSpeed = 10f;

    void Update() 
    {
        transform.Rotate(Vector3.right * rotateSpeed * Time.deltaTime);     // Vector3.right = Vector3(1, 0, 0)
    }
}
  1. 回到 Unity 中執行,並調整該 RotateSpeed 的速度。

  2. 你可以發現到該Player先向下旋轉,這代表說在 Unity Scene 中旋轉X軸數值為正為向下,負為向上。首先看到Forward 的紅色圓球。最後看到Backward 的紅色圓球。

  • 一開始:

  • 最後:

實作繞行世界座標的 Y軸旋轉

  1. 繞行Y軸旋轉就是左右旋轉,這邊我們使用到 Vector3.up 作為該旋轉軸,並且每秒乘上一個速度進行該旋轉量。並且依世界座標系為旋轉軸。若仔細看可以發現到 Y軸旋轉不如同 X軸旋轉一樣,反而正數為向右,負數為向左。首先看到該正向的紅球,之後看到右側的綠球。
  • 一開始:

  • 最後:

理解 Transform.LookAt

通常會需要專注在某一個物品或是方向時,我們會使用到 LookAt 的函數,看來好像很簡單就是看向某一個物件而已,但其實也有一些特殊的情況等,僅一種使用方法。 第一個參數是你想要看到的座標,第二個是你要基於哪個軸旋轉,預設為 Space.up。

Transform.LookAt(Vector3 position, Space relativeTo = Space.up)

實作緩慢移動面向某個目標

  1. 首先我們要記錄我們轉身前的角度。一開始在 Start的時候我們就會記錄起點與終點的角度。終點就是我們最終要看到的位置上的角度。透過 Quaternion.Angle 就可以知道兩個位置的角度差,最後我們將設定的 lerp_Speed 除以 rotateAngle 來區分該等等線性插值要使用的數值。
		public Transform targetObj;
    private Quaternion originRotate;
    private Quaternion getEndRotate;
    private float lerpSubSpeed = 20.0f;
    float lerp_speed = 0.0f;
    float lerp_tm = 0.0f;

    void Start() 
    {
        originRotate = transform.rotation;
        transform.LookAt(targetObj.position);
        getEndRotate = transform.rotation;
        transform.rotation = originRotate;

        float rotateAngle = Quaternion.Angle(originRotate, getEndRotate);
        lerp_speed = lerpSubSpeed / rotateAngle;                                                       
        Debug.Log("Angle: " + rotateAngle.ToString() + ", speed: " + lerp_speed.ToString());
        lerp_tm = 0.0f;
    }

    
  1. 到 Update 就是去套用每秒更新一次開 kerp_tm,知道 lerp_tm 就是線性插值裡面 0~1的數值變化後的結果,最後當 lerp_tm ≥ 1 則就將該最後的角度設定在最終Target 上的角度。
void Update() 
    {
        lerp_tm += Time.deltaTime * lerp_speed;
        transform.rotation = Quaternion.Lerp(originRotate, getEndRotate, lerp_tm);
        if(lerp_tm >= 1)                    // need to know that Lerp is 0 ~ 1
        {
            transform.rotation = getEndRotate;
        }
        
    }
  1. 回到 Unity 我們將旋轉該各不同角度的 Target。
  • 首先 x = 0, y = -30 度

  • x = 90, y = -30 度

  • x = -45, y = -30 度

以上都會緩慢的移動到目標,看來是沒有任何問題XDD
但請注意到自動讓系統使用 LookAt 會有最後一個參數調整的問題,也就是基於旋轉軸旋轉無法明確的決定。

四元數旋轉 Transform.rotation = Quaternion.Euler

  1. 首先我們要知道 Transform 本身的旋轉角度 rotation 本身就是該四元數的Euler。以下兩種宣告方式相同僅長相不相似。
public static Quaternion Euler(float x, float y, float z);
public static Quaternion Euler(Vector3 euler);

實作讓物件繞Y軸旋轉 45 度,X軸旋轉 -60 度

  1. 我們可以直接將想要旋轉的角度先儲存到 Vector3 上,後續我們利用一個四元數來儲存其EulerAngle,接下來將該四元數套進transform 自身的rotation 中。
void Update() 
    {
        Vector3 rotationVector = new Vector3(-60, 45, 0);
        Quaternion rotation = Quaternion.Euler(rotationVector);
       transform.rotation = rotation;
        
    }
  1. 回到 Unity 執行後會看到 Player 的確會正視該我要看到的物件上。

理解 Quaternion.Slerp

  1. 我們通過一個數值來表示該球形插值,這邊Slerp 的參數會在 0, 1 的範圍內
transform.rotation = Quaternion.Slerp();
  1. 這邊很好理解,要注意到說該地一個參數為 rotation 的起點,第二個參數為該rotation 的終點。最後第三個參數就是 0 ~ 1線性插值的數值。
Quaternion.Slerp(Vector3 StartRotation, Vector3 EndRotation, float t);
  1. 讓我們來實作一下! 首先就宣告兩個不同的物件,該兩個物件就是起點與終點。接著透過使用 time 來當作我們的 0 ~ 1中間累積的間距。 這邊有個很有趣的地方是我們知道 Time.deltaTime 一開始累加是由 0 開始,一開始會小於 1 所以time 在遞增個時候會比較慢,所以放進Quaternion.Slerp 就會發現到說移動會有漸進變快的感覺。
public Transform startRotate, endRotate;
    public float time = 0.0f;

    void Start() 
    {

    }
        

    void Update() 
    {
        transform.rotation = Quaternion.Slerp(startRotate.rotation, endRotate.rotation, time);
        time += Time.deltaTime/Quaternion.Angle(startRotate.rotation, endRotate.rotation);
    }
  1. 回到 Unity 將我的起點與終點設定好,StartRotate and EndRotate。y最後執行就會看到一個漸進變快移動的感覺,很有趣XDD。

理解 Transform.RotateAround

  1. 如同上面 RotateAround 所介紹,為繞著某一物體旋轉。
transform.RotateAround();
  1. 回調函數,第一個參數是繞著某一物體旋轉,第二個是轉動旋轉軸向,第三個是旋轉的速度。
public void RotateAround(Vector3 point, Vector3 axis, float angle)
  1. 讓我們來簡單實作一下吧。順便感受一下該控制第二個轉動量的差異性。首先我們將其宣告一個SceneObj 代表我們整個場景。接下來RotateAround以原點,也就是我們場景的原點,並且以Vector3.right 作為我們的旋轉軸,旋轉軸以Vector3.right 最後的結果就是整個場景透過X軸旋轉。
public Transform SceneObj;

    void Update() 
    {
       SceneObj.RotateAround(Vector3.zero, Vector3.right, 20 * Time.deltaTime);
    }

回到 Unity 執行後就會看到差異。可以看到左右綠球的位置不變,也可以透過右邊的 TestScene 看到僅X軸的數值在變動。注意到那是因為中心點為原點所以才可以有效的行程自體旋轉。

  1. 下來我們將第二個參數設定為 Vector3.up 看會有什麼變化。
public Transform SceneObj;
void Update() 
    {
       SceneObj.RotateAround(Vector3.zero, Vector3.up, 20 * Time.deltaTime);
    }

回到Unity 就會看到這次以 Y 軸進行旋轉。上下藍球位置不變。可以透過右邊的 TestScene 看到僅Y軸的數值在變動。

  1. 我們也可以把中心設定成 transform.position
void Update() 
    {
       SceneObj.RotateAround(transform.position, Vector3.up, 20 * Time.deltaTime);
    }

理解 EulerAngle 旋轉

  1. 歐拉角旋轉有旋轉的最大數值,還有萬象鎖的問題,基本上我使用都會有一些問題,所以這邊還是建議四元數的旋轉會比較適當,但還是有像一開始對兩個方式說明的好處,因此適合使用哪一個都可以。

  2. 接下來我們實際透過 w, a, s, d 來使物體根據鍵盤案下的鍵來決定旋轉幾度。

float yRotation = 1f, xRotation = 1f;


    void Start() 
    {
        print("transform.eulerAngles.x: " + transform.eulerAngles.x);
        print("transform.eulerAngles.y: " + transform.eulerAngles.y);
        print("transform.eulerAngles.z: " + transform.eulerAngles.z);
    }
        

    void Update() 
    {
       yRotation += Input.GetAxis("Horizontal");
       xRotation -= Input.GetAxis("Vertical");
       transform.eulerAngles = new Vector3(xRotation, yRotation, 0f);
		}
  1. 我們回到 Unity 中去執行發現User 會根據我的鍵盤按下的左右上下鍵旋轉該角度。


結論:

  1. 今天我們使用到非常多讓物件旋轉的方式。
  2. 旋轉時最重要的還是要知道你基於甚麼軸旋轉,旋轉的方向在哪裡。
  3. 四元數與歐拉腳有各自的優勢,任何方法都可以使用。

上一篇
Day17: Audio Listener Setting
下一篇
Day19: Health Bar
系列文
Unity 基本功能實作與日常紀錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言