iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

這兩天我希望研究在 Unity 中控制 Camera 的方式,主要是我們通常在環境中視角幾乎都是讓Camera 移動來讓我們觀看環鏡中的位置。有時候我們會希望Camera 能跟隨物件、旋轉Camera 自身、根據與物件的距離產生不同的移動速度、鏡頭的拉伸、切換不同鏡頭等等。所以我希望在這兩天好好的熟悉一下使用Unity Camera ,並把這個苦惱我很久的鏡頭控制給釐清,未來或許要常使用。拆成兩天是因為可能這次的學習量很大故拆成兩天。

建置環境

  1. 在 Hierarchy 中新增之前使用的小雞 Prefab。

  2. 接下來在Main Camera的身上新增一個文本,取名為 CameraControl1.cs

鎖定 Camera 位置

  1. 首先我們點開 CameraControl.cs 的腳本撰寫以下程式
# region Fixed Camera 
   public Transform chickenModel;
   float offsetZ = 5f, offsetY = 1f;

   void Start() 
   {
    transform.rotation = Quaternion.identity;
    Vector3 pos = chickenModel.position;
    pos.z -= offsetZ;
    transform.position = pos;
   }

# endregion

程式中 transform 為該Camera 的位置,offsetZ , offsetY 是 Camera 與物件的距離,首先旋轉 Rotation 歸零,Camera的位置是由鎖定物件上的位置基礎進行減去或增加默認的 offset 數值,這邊要鎖定在公雞後方與稍微上面一點俯瞰該公雞。

  1. 運行一下結果,可以發現Camera 果然會移動到該 物件的後方,仔細觀察一下該Camera Transform 內部 Position 的 Y, Z軸數值,果然會去更新該 offset 數值。

根據滑鼠上下移動 Camera 視角方向

  1. 現在我們撰寫如何圍繞該物件進行旋轉,我們根據滑鼠右鍵來移動使其Camera 隨著滑鼠的 X軸方向進行旋轉。 請注意到該 Rotate Camera region 下方的程式碼。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraControl1 : MonoBehaviour
{


# region Fixed Camera 
   public Transform chickenModel;
   float offsetZ = 5f, offsetY = 1f;

   void Start() 
   {
    transform.rotation = Quaternion.identity;
    Vector3 pos = chickenModel.position;
    pos.z -= offsetZ;
    transform.position = pos;
   }

# endregion

#region Rotate Camera

    void Update() 
    {
        
        float mouse_dx = Input.GetAxis("Mouse X");
        float mouse_dy = Input.GetAxis("Mouse Y");
        if(Input.GetMouseButton(1))
        {
            if(Mathf.Abs(mouse_dx) > 0 || Mathf.Abs(mouse_dy) > 0)
            {
                // get the camera eular angle 
                Vector3 currentCameraAngle = transform.rotation.eulerAngles;

                currentCameraAngle.x = Mathf.Repeat(currentCameraAngle.x + 180f, 360f) -180f;       // always true 0 ~ 180
                currentCameraAngle.y += mouse_dx;                       // unity yaw Y, right is positive, left i negative
                currentCameraAngle.x -= mouse_dy;                       // unity pitch X, up is negative, down is positive

                Quaternion cameraRotation = Quaternion.identity;
                cameraRotation.eulerAngles = new Vector3(currentCameraAngle.x  , currentCameraAngle.y , 0);
                transform.rotation = cameraRotation;

                
            }
        }
        
    }


#endregion

}

關於 Rotate 這邊的說明,我想先釐清一個觀念就是,在 Unity 中環境的坐標系為左手坐標系,所以說當旋轉物體中 Rotation 座標 X 的時候為 pitch,旋轉 Y 軸時為 yaw, 旋轉 Z 軸時為 roll 。因此當我們取得該滑鼠的X, Y 軸移動時的數值,請記得滑鼠X軸移動對應到 yaw,故為 Unity 的 Y軸,右正左負。滑鼠 Y軸移動對應到 pitch,故為 Unity 的 X軸,上負下正。這邊的觀念很容易搞混,所以要先有先輩知識會比較好理解。

圍繞模型 360 度旋轉的 Camera

  1. 剛剛我們只是改變了Camera 本身的旋轉方向,這邊若要達到Camera 圍繞物件旋轉,必須要保證在旋轉的過程中與物件的距離、方向不能改變。 修改一下程式碼。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraControl1 : MonoBehaviour
{


# region Fixed Camera 
   public Transform chickenModel;
   float offsetZ = 2f, offsetY = 1f;

   void Start() 
   {
    transform.rotation = Quaternion.identity;
    Vector3 pos = chickenModel.position;
    pos.z -= offsetZ;
    transform.position = pos;
   }

# endregion

#region Rotate Camera

    void Update() 
    {
        
        float mouse_dx = Input.GetAxis("Mouse X");
        float mouse_dy = Input.GetAxis("Mouse Y");
        if(Input.GetMouseButton(1))
        {
            if(Mathf.Abs(mouse_dx) > 0 || Mathf.Abs(mouse_dy) > 0)
            {
                // get the camera eular angle 
                Vector3 currentCameraAngle = transform.rotation.eulerAngles;

                currentCameraAngle.x = Mathf.Repeat(currentCameraAngle.x + 180f, 360f) -180f;       // always true 0 ~ 180
                currentCameraAngle.y += mouse_dx;                       // unity yaw Y, right is positive, left i negative
                currentCameraAngle.x -= mouse_dy;                       // unity pitch X, up is negative, down is positive

                Quaternion cameraRotation = Quaternion.identity;
                cameraRotation.eulerAngles = new Vector3(currentCameraAngle.x  , currentCameraAngle.y , 0);
                transform.rotation = cameraRotation;

                // reset the camera position
                 Vector3 modelPosition = chickenModel.position;
                 Vector3 distance = cameraRotation * new Vector3(0f, 0f, offsetZ);
                 transform.position = modelPosition - distance;

            }
        }
        
    }
#endregion


}

最後添加的這邊要注意到若要鎖定在旋轉的時候持續注視某一物件,該物件(modelPosition)的位置取得後,將Camera 的旋轉角度,也就是旋轉方向,乘上一個向量,也就是我們的 new Vector3(0f, 0f ,offsetZ)後再作相減。該旋轉方向和向量相乘也就是相當於在後退了 offsetZ 的距離,接著進行旋轉。 角度乘上向量會有距離,有物件的距離後再減去該方向上的距離(distance) 就會是該 Camera 的位置。

Vector3 modelPosition = chickenModel.position;
Vector3 distance = cameraRotation * new Vector3(0f, 0f, offsetZ);
transform.position = modelPosition - distance;
  1. 最後執行的結果如下,可以看到該移動滑鼠的位置,Camera 圍繞且注視著該物件。

四元數

  1. 首先我們要知道 Unity 的 Transform 的 Rotation 對應的歐拉角,一共有三個軸,每個對應的數值是繞對應軸的旋轉度數。 我們按照座標順序旋轉, X 軸旋轉該30 度, Y軸旋轉90 度,Z軸旋轉 10 度。
  2. 首先歐拉角的優點就是只使用三個數值,即是三個座標軸的旋轉角度,缺點就是必須要按照順序進行旋轉 (順序不同會有不同的效果,注意到有萬象鎖的現象),歐拉角旋轉的方式並非同時旋轉,所以當軸度重疊就會造成萬象鎖的問題,丟失一個軸度的旋轉量,歐拉姊也不可以使用球面平滑插值。
  3. 而今天的四元數表示旋轉的一種方式,transform 中的rotation 屬性就是以四元數。四元數是一個高階複數,具四維空間。將有兩個部分,虛部與實部, xi,yj, zk, w。
  4. 四元數的優點就是可以避免萬象鎖的問題,可透過四元數來任意繞行各種軸度。
  5. 缺點就是較為複雜且不直觀。
  • 獲取 Camera 的歐拉角
Vector3 currentCameraAngle = transform.rotation.eulerAngles;
  • 更新 Camera 的歐拉角
cameraRotation.eulerAngles = new Vector3(currentCameraAngle.x  , currentCameraAngle.y , 0);

優化Camera 的表現

  1. 我們將該Camera 的旋轉和位置設定到我們的 FixedUpdate 中。 在FixedUpdate 函數內部設置 Camera 的最終的旋轉與位置。我們可以看到說該 Update 與 FixedUpdate 的區別,這邊 Update 跟平台的Frame 有關,但是 FixedUpdate 使仿造 Real time,動作會表現得更加精細。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraControl1 : MonoBehaviour
{


# region Fixed Camera 
   public Transform chickenModel;
   float offsetZ = 2f, offsetY = 1f;
   public Vector3 getPosition;                 // store the position
   private Quaternion getRotation;             // store the rotation

   void Start() 
   {
    transform.rotation = Quaternion.identity;
    Vector3 pos = chickenModel.position;
    pos.z -= offsetZ;
    transform.position = pos;

    getPosition = chickenModel.position;
   }

# endregion

#region Rotate Camera

    void Update() 
    {
        
        float mouse_dx = Input.GetAxis("Mouse X");
        float mouse_dy = Input.GetAxis("Mouse Y");
        if(Input.GetMouseButton(1))
        {
            if(Mathf.Abs(mouse_dx) > 0 || Mathf.Abs(mouse_dy) > 0)
            {
                // get the camera eular angle 
                Vector3 currentCameraAngle = transform.rotation.eulerAngles;

                currentCameraAngle.x = Mathf.Repeat(currentCameraAngle.x + 180f, 360f) -180f;       // always true 0 ~ 180
                currentCameraAngle.y += mouse_dx;                       // unity yaw Y, right is positive, left i negative
                currentCameraAngle.x -= mouse_dy;                       // unity pitch X, up is negative, down is positive

                Quaternion cameraRotation = Quaternion.identity;
                getRotation.eulerAngles = new Vector3(currentCameraAngle.x  , currentCameraAngle.y , 0);               // count the camera rotation
            }
        }
        
    }

    void FixedUpdate() {
        transform.rotation = getRotation;                                                      // set the camera rotation 
        transform.position = getPosition - getRotation * new Vector3(0, 0, offsetZ);              // set the position
    }
#endregion


}
  1. 執行後會發現有點卡卡的我們這邊在加上 一些速度以及阻尼感。這樣用滑鼠拓移時會產生一種慢慢滑行的效果。前天有說明到 Lerp 線性插值的方式,今天我們使用 Slerp 球形插值來使其移動過程中有個過渡。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraControl1 : MonoBehaviour
{


# region Fixed Camera 
   public Transform chickenModel;
   float offsetZ = 2f, offsetY = 1f;
   public Vector3 getPosition;                 // store the position
   private Quaternion rotationSlerp, getRotation;             // store the rotation

   public float rotateSpeed = 32f, rotateLerp = 8f;

   void Start() 
   {
    transform.rotation = Quaternion.identity;
    Vector3 pos = chickenModel.position;
    pos.z -= offsetZ;
    transform.position = pos;

    getPosition = chickenModel.position;
   }

# endregion

#region Rotate Camera

    void Update() 
    {
        
        float mouse_dx = Input.GetAxis("Mouse X");
        float mouse_dy = Input.GetAxis("Mouse Y");
        if(Input.GetMouseButton(1))
        {
            mouse_dx *= rotateSpeed;
            mouse_dy *= rotateSpeed;
            if(Mathf.Abs(mouse_dx) > 0 || Mathf.Abs(mouse_dy) > 0)
            {
                // get the camera eular angle 
                Vector3 currentCameraAngle = transform.rotation.eulerAngles;

                currentCameraAngle.x = Mathf.Repeat(currentCameraAngle.x + 180f, 360f) -180f;       // always true 0 ~ 180
                currentCameraAngle.y += mouse_dx;                       // unity yaw Y, right is positive, left i negative
                currentCameraAngle.x -= mouse_dy;                       // unity pitch X, up is negative, down is positive

                getRotation.eulerAngles = new Vector3(currentCameraAngle.x  , currentCameraAngle.y , 0);              
            }
        }
        
    }

    void FixedUpdate() {
        rotationSlerp = Quaternion.Slerp(rotationSlerp, getRotation, Time.deltaTime * rotateLerp);      // use Slerp to calculate

        transform.rotation = rotationSlerp;                                                     
        transform.position = getPosition - rotationSlerp * new Vector3(0, 0, offsetZ);          
    }


#endregion


}
  1. 執行結果如下,這邊顯示不太明顯,可以自己試試看每個情況下的區別。 明天我們會再繼續介紹 ^^

結論:

  1. 今天了解到如何將我們的 Camera 固定到 Player 上,研究在場景中能追蹤Player 的位置。
  2. 了解滑鼠移動的 XY 位置來改變 Camera 畫面移動的視角,讓 Camera 可以上下移動。
  3. 讓Camera 能夠圍繞著物體採用第三人稱的方式。
  4. 之後透過 FixedUpdate 來優化我們 Camera 移動的方式。並且理解 FixedUpdate 與 Update 的差別。

上一篇
Day12: Use Lerp move A to B by Time or Speed!
下一篇
Day14: Unity Camera Control 2
系列文
Unity 基本功能實作與日常紀錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言