Unity第三人稱視角解決方案

鏡頭跟隨

在實現(xiàn)第三人稱時,鏡頭問題困擾了我一整天,參考了官方的腳本 SmoothFollow,雖然能實現(xiàn)鏡頭跟在人物身后,但是發(fā)現(xiàn)幾個問題。

  • 腳本實現(xiàn)太繁瑣,有幾個屬性目前根本就用不到。
  • 人物旋轉(zhuǎn)時不能控制攝像機跟著旋轉(zhuǎn),也就是說,不能讓鏡頭一直跟在人物身后。

腳本代碼如下:

    public class SmoothFollow : MonoBehaviour
    {

        // The target we are following
        [SerializeField]
        private Transform target;
        // The distance in the x-z plane to the target
        [SerializeField]
        private float distance = 10.0f;
        // the height we want the camera to be above the target
        [SerializeField]
        private float height = 5.0f;

        [SerializeField]
        private float rotationDamping;
        [SerializeField]
        private float heightDamping;

        // Use this for initialization
        void Start() { }

        // Update is called once per frame
        void LateUpdate()
        {
            // Early out if we don't have a target
            if (!target)
                return;

            // Calculate the current rotation angles
            var wantedRotationAngle = target.eulerAngles.y;
            var wantedHeight = target.position.y + height;

            var currentRotationAngle = transform.eulerAngles.y;
            var currentHeight = transform.position.y;

            // Damp the rotation around the y-axis
            currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);

            // Damp the height
            currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime);

            // Convert the angle into a rotation
            var currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);

            // Set the position of the camera on the x-z plane to:
            // distance meters behind the target
            transform.position = target.position;
            transform.position -= currentRotation * Vector3.forward * distance;

            // Set the height of the camera
            transform.position = new Vector3(transform.position.x ,currentHeight , transform.position.z);

            // Always look at the target
            transform.LookAt(target);
        }
    }

其中的[SerializeField]可以實現(xiàn)private屬性在inspect面板中出現(xiàn),雖然目前不知道有什么好處,但是在安全性應(yīng)該會有所提升,所以我也就這么用了。
其中的兩個屬性damping測試了半天,還是沒理解具體是啥意思,而heightdistance屬性也沒有起到我所需要的效果。
于是自己動手,直接把所有注釋掉,僅需要:

        [SerializeField]
        private Transform lookAtTarget;
                //transfrom.lookAt的距離差
        [SerializeField]
        private Vector3 lookAtOffset;
        //初始向量差 用來保持距離不變
        private Vector3 originVector;

inspect面板是這樣的:


實現(xiàn)遠離非常簡單,只要在初始場景里設(shè)置好初始相機和target的相對位置,像這樣。

start方法里設(shè)置originVector:

            originVector =new Vector3(target.position.x-transform.position.x,target.position.y-transform.position.y,target.position.z-transform.position.z);

Update方法里實現(xiàn):

            transform.position = target.position - originVector;
                        transform.LookAt (target.position+lookAtOffset);

大功告成。已經(jīng)實現(xiàn)了原版的SmoothFollow的功能,這時候問題又來了,相機一直跟在人物的身后,不會旋轉(zhuǎn)。


旋轉(zhuǎn)是個大麻煩。
涉及到歐拉角或者四元數(shù),研究了半天,想到幾個解決方案。

  • 對originVector做旋轉(zhuǎn):重新規(guī)劃坐標(biāo),獲得每一次旋轉(zhuǎn)以后的坐標(biāo)點,而不是直接使用兩個向量的向量差。
  • 先正常旋轉(zhuǎn),然后利用RotateAround方法完成繞人物旋轉(zhuǎn)。

其實仔細觀察會發(fā)現(xiàn),人物的旋轉(zhuǎn),只有Vector3.up,也就是y方向上的旋轉(zhuǎn),旋轉(zhuǎn)角度就是每次前后的歐拉角y方向之差,只需要進行四元數(shù)和歐拉角的轉(zhuǎn)換,而第一種解決方案,涉及三角函數(shù)和坐標(biāo)系變換,非常困難,試了很久依舊沒有搞定,于是用第二種解決方案。
添加屬性:

        //記錄上一frame的旋轉(zhuǎn)角度
        private Vector3 lastFrameTargetRoation;

start方法里:

            lastFrameTargetRoation = target.rotation.eulerAngles;

update方法的完整代碼:

            transform.position = target.position - originVector;
            transform.RotateAround(target.position,Vector3.up,target.rotation.eulerAngles.y -  lastFrameTargetRoation.y);

            lastFrameTargetRoation = target.rotation.eulerAngles;
            originVector =new Vector3(target.position.x-transform.position.x,target.position.y-transform.position.y,target.position.z-transform.position.z);

就搞定了。

鼠標(biāo)右鍵控制鏡頭

首先當(dāng)然是要捕捉鼠標(biāo)右鍵的狀態(tài)了,只有長按并且拖動才是對視角的控制,官方的鼠標(biāo)事件并沒有這樣的事件。于是用一個變量來記錄。

        //鼠標(biāo)旋轉(zhuǎn)視野的速度
        [SerializeField]
        private float mouseTurnedSpeed = 0.3f;
        //鼠標(biāo)右鍵控制鏡頭旋轉(zhuǎn)的代碼
        private bool rightButtonDonwed;

一開始想要使用onMouseXXX方法來監(jiān)控鼠標(biāo)的點擊操作,官方文檔是這么說的。試了發(fā)現(xiàn)并沒有調(diào)用這些方法。


也就是說這些方法只有當(dāng)鼠標(biāo)在相應(yīng)的物體上點擊才有效,而鏡頭視角顯然是對著空氣,總不能把空氣作為一個GameObject,因此就在Update方法里修改。

            //記錄鼠標(biāo)右鍵是否按下的狀態(tài)
            if (Input.GetMouseButton (1) && Input.GetMouseButtonDown (1)) {
                rightButtonDonwed = true;
            }
            if (Input.GetMouseButtonUp (1)) {
                rightButtonDonwed = false;  
            }

這樣就能得到鼠標(biāo)右鍵是否是按下的狀態(tài),接著用如下代碼控制X軸的旋轉(zhuǎn)。(X代表左右,Z代表前后,Y代表上下)

                //獲取鼠標(biāo)旋轉(zhuǎn)的度數(shù) 橫軸
                float rotationAmount = Input.GetAxis ("Mouse X") * mouseTurnedSpeed * Time.deltaTime;
                //最終的旋轉(zhuǎn)讀書
                transform.RotateAround (target.position, Vector3.up, rotationAmount*360);
                //人物也旋轉(zhuǎn) 保證鏡頭始終對著人物背面
                target.RotateAround(target.position, Vector3.up, rotationAmount*360);

X軸的旋轉(zhuǎn)十分簡單,接下來是Y軸的上下鏡頭旋轉(zhuǎn),參考了第一人稱視角的解決方案,發(fā)現(xiàn)第三人稱跟第一人稱完全不一樣,需要獲取一個旋轉(zhuǎn)軸,也就是平行于當(dāng)前平面,垂直于Y軸的向量,如下圖。



旋轉(zhuǎn)軸利用兩向量垂直的乘積等于0的公式算出來。以下是y軸旋轉(zhuǎn)代碼。

                //縱軸
                float rotationAmountY = Input.GetAxis ("Mouse Y") * mouseTurnedSpeed * Time.deltaTime;
                Vector3 yCenter = new Vector3 (-originVector.z / originVector.x, target.position.y, 1);
                transform.RotateAround (target.position,yCenter,rotationAmountY*360);
鼠標(biāo)滾輪拉近/遠

右鍵的問題解決了,接下來就是鼠標(biāo)滾輪的問題了。原理很簡單,只要改變差值向量就可以,直接附上代碼。

                //變動的距離
            float changeDistance = Input.GetAxis ("Mouse ScrollWheel") ;
                //單前的距離
            float currentDistance = originVector.magnitude;
                //單位向量
            Vector3 miniVector = originVector.normalized;
                //獲取新的向量
            originVector = miniVector*(currentDistance-changeDistance*Time.deltaTime*100);

至此第三人稱視角初步完美解決。(極限情況暫未考慮)
最后附上完整的代碼,綁定到攝像機就可以了。

using UnityEngine;

namespace UnityStandardAssets.Utility
{
    public class SmoothFollow : MonoBehaviour
    {

        // The target we are following
        [SerializeField]
        private Transform target;
        // The distance in the x-z plane to the target

        [SerializeField]
        private Transform lookAtTarget;
        [SerializeField]
        private Vector3 lookAtOffset;
        //初始向量差 用來保持距離不變
        private Vector3 originVector;
        //記錄上一frame的旋轉(zhuǎn)叫
        private Vector3 lastFrameTargetRoation;
        //鼠標(biāo)旋轉(zhuǎn)視野的速度
        [SerializeField]
        private float mouseTurnedSpeed = 0.3f;
        //鼠標(biāo)右鍵控制鏡頭旋轉(zhuǎn)的代碼
        private bool rightButtonDonwed;

        // Use this for initialization
        void Start() {
        //獲取當(dāng)前的distance和height
            originVector =new Vector3(target.position.x-transform.position.x,target.position.y-transform.position.y,target.position.z-transform.position.z);
            rightButtonDonwed = false;
            lastFrameTargetRoation = target.rotation.eulerAngles;
        }

        // Update is called once per frame
        void Update()
        {           

                //變動的距離
            float changeDistance = Input.GetAxis ("Mouse ScrollWheel") ;
                //單前的距離
            float currentDistance = originVector.magnitude;
                //單位向量
            Vector3 miniVector = originVector.normalized;
                //獲取新的向量
            originVector = miniVector*(currentDistance-changeDistance*Time.deltaTime*100);


            //記錄鼠標(biāo)右鍵是否按下的狀態(tài)
            if (Input.GetMouseButton (1) && Input.GetMouseButtonDown (1)) {
                rightButtonDonwed = true;
            }
            if (Input.GetMouseButtonUp (1)) {
                rightButtonDonwed = false;  
            }
            transform.position = target.position - originVector;
            print (rightButtonDonwed);
            if (rightButtonDonwed) {
                //獲取鼠標(biāo)旋轉(zhuǎn)的度數(shù) 橫軸
                float rotationAmount = Input.GetAxis ("Mouse X") * mouseTurnedSpeed * Time.deltaTime;
                //最終的旋轉(zhuǎn)讀書
                transform.RotateAround (target.position, Vector3.up, rotationAmount*360);
                //人物也旋轉(zhuǎn) 保證鏡頭始終對著人物背面
                target.RotateAround(target.position, Vector3.up, rotationAmount*360);

                //縱軸
                float rotationAmountY = Input.GetAxis ("Mouse Y") * mouseTurnedSpeed * Time.deltaTime;
                Vector3 yCenter = new Vector3 (-originVector.z / originVector.x, target.position.y, 1);
                transform.RotateAround (target.position,yCenter,rotationAmountY*360);
            } else {
                transform.RotateAround(target.position,Vector3.up,target.rotation.eulerAngles.y -  lastFrameTargetRoation.y);

            }



            lastFrameTargetRoation = target.rotation.eulerAngles;
            originVector =new Vector3(target.position.x-transform.position.x,target.position.y-transform.position.y,target.position.z-transform.position.z);


        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • OpenGL本身沒有攝像機的概念,但我們可以通過把場景中的所有物體往相反方向移動的方式來模擬出攝像機,這樣感覺就像...
    IceMJ閱讀 2,783評論 0 7
  • 1 前言 OpenGL渲染3D模型離不開空間幾何的數(shù)學(xué)理論知識,而本篇文章的目的就是對空間幾何進行簡單的介紹,并對...
    RichardJieChen閱讀 7,557評論 1 11
  • 一、實驗?zāi)康?學(xué)習(xí)使用 weka 中的常用分類器,完成數(shù)據(jù)分類任務(wù)。 二、實驗內(nèi)容 了解 weka 中 explo...
    yigoh閱讀 8,870評論 5 4
  • 歐拉角的定義 在寫這篇博客之前,我搜索了網(wǎng)上很多關(guān)于歐拉角的定義,發(fā)現(xiàn)大部分引用自維基百科的定義,我這里也引述一下...
    AndrewFan閱讀 3,016評論 3 12
  • 無論你在哪里,我都在等你 就算我忘記你,你也不要忘記我 好嗎 不論風(fēng)吹,雨打 都不褪 午后陽光 多甜蜜 不反駁 是...
    mo女閱讀 241評論 1 2

友情鏈接更多精彩內(nèi)容