鏡頭跟隨
在實現(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測試了半天,還是沒理解具體是啥意思,而height和distance屬性也沒有起到我所需要的效果。
于是自己動手,直接把所有注釋掉,僅需要:
[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);
}
}
}