使用方法:
將要控制的角色拖到TargetBody,將相機的焦點拖到CamerPivot,,建議CameraPivot是一個放在TargetBody下的子物體,並且位置應該是在TargetBody的頭部.
注意:此腳本自動忽略"Ignore Raycast"層和"Mob"對相機視野的遮擋,也就是說,當相機被帶有這兩個層之一的物體遮擋時,相機不會自動移動到遮擋物之前,這是用於設置一些不應該觸發相機防遮擋的物體用的,比如說怪物和玻璃等.
包含的功能有:
1.控制人物轉向,按下WASD按鍵時可直接控制目標物體(角色)插值轉向.
2.垂直視角限制,避免發生相機翻轉的問題
3.相機防遮擋
原理說明:
1.通過四元數插值控制人物轉向
2.在unity中,水平視角的x歐拉角爲0,向上直至垂直的歐拉角實際上爲(0-90),而向下直至垂直的歐拉角實際上是(360-270).腳本中maxEvelation爲最大仰角,maxDepression爲最大俯角,假設此處最大俯角和最大仰角都爲80,則在最大仰角達到80以上時,判斷鼠標的輸入,只有當鼠標輸入爲向下滑動時(此時鼠標的輸入Input.getAxis("Mouse Y")>0)才允許滑動.同理,當最大俯角爲80(360-80=280)時,此時實際上的歐拉角爲280-270,所以當x<280時,則只允許向上滑動(此時鼠標的輸入Input.getAxis("Mouse Y")<0))
如圖所示:
代碼:
float eulerX = transform.localEulerAngles.x;//相機的x歐拉角,也就是垂直方向.
float inputY = Input.GetAxis("Mouse Y");
//垂直視野限制
transform.RotateAround(CameraPivot.transform.position, CameraPivot.transform.up, rotateSpeed * Input.GetAxis("Mouse X"));
if (eulerX > maxEvelation && eulerX < 90)
{
if (inputY > 0)
transform.RotateAround(CameraPivot.transform.position, transform.right, -rotateSpeed * inputY);
}
else if (eulerX < 360-maxDepression && eulerX > 270)
{
if (inputY < 0)
transform.RotateAround(CameraPivot.transform.position, transform.right, -rotateSpeed * inputY);
}
else
{
transform.RotateAround(CameraPivot.transform.position, transform.right, -rotateSpeed * inputY);
}
3.相機防遮擋
先提前檢測(自己)相機的應該在位置,從相機目標向相機發射一條射線,如果碰撞了,說明即將碰撞.則直接把位置調到碰撞點位置,但此時的位置正好在平面裏,相機的一部分視野會透視,所以應該在再向相機靠近一點.
transform.position = CameraPivot.transform.position + (wallHit - CameraPivot.transform.position) * 0.8f;
注意,這裏是提前判斷,如果是直接更新相機位置後發現被遮擋再把位置移動到碰撞點,相機就會在碰撞點和應在位置來回切換.效果自然是有問題的.
最後說明相機的位置更新方法:
相機的旋轉通過RotateAround()來以目標相機焦點爲中心進行旋轉.旋轉後根據當前位置和相機焦點位置相減得到方向向量,然後用相機焦點位置+方向向量的單位向量*設置的距相機焦點的距離.
代碼表示:
offset = transform.position - CameraPivot.transform.position;
offset = offset.normalized * freeDistance;
transform.position = CameraPivot.transform.position + offset;
完整代碼:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FreeTPSCamera : MonoBehaviour {
[Header("相機距離")]
public float freeDistance = 2;
[Header("相機最近距離")]
public float minDistance = 0.5f;
[Header("相機最遠距離")]
public float maxDistance = 20;
[Header("是否可控制相機距離(鼠標中鍵)")]
public bool canControlDistance=true;
[Header("更改相機距離的速度")]
public float distanceSpeed = 1;
[Header("視角靈敏度")]
public float rotateSpeed = 1;
[Header("物體轉向插值(靈敏度,取值爲0到1)")]
public float TargetBodyRotateLerp = 0.3f;
[Header("需要轉向的物體")]
public GameObject TargetBody;//此腳本能操作轉向的物體
[Header("相機焦點物體")]
public GameObject CameraPivot;//相機焦點物體
[Header("===鎖敵===")]
public GameObject lockTarget=null;
public float lockSlerp=1;
public GameObject lockMark;
private bool marked;
[Header("是否可控制物體轉向")]
public bool CanControlDirection = true;
[Header("俯角(0-89)")]
public float maxDepression=80;
[Header("仰角(0-89)")]
public float maxEvelation=80;
private Vector3 PredictCameraPosition;
private Vector3 offset;
private Vector3 wallHit;
private GameObject tmpMark;
// Use this for initialization
void Start () {
offset = transform.position - CameraPivot.transform.position;
if (TargetBody == null)
{
TargetBody = GameObject.FindGameObjectWithTag("Player");
Debug.Log("未綁定目標物體,默認替換爲Player標籤的物體");
}
if (!CameraPivot)
{
Debug.LogError("未綁定相機焦點物體");
}
}
void LockTarget()
{
if(lockTarget)
{
lockTarget = null;
marked = false;
Destroy(tmpMark);
return;
}
Vector3 top = transform.position + new Vector3(0, 1, 0)+transform.forward*5;
LayerMask mask = (1 << LayerMask.NameToLayer("Mob")); //將物體的Layer設置爲Ignore Raycast,Player和Mob來忽略相機的射線,不然相機將跳到某些物體前,比如怪物,玩家等,
Collider[] cols = Physics.OverlapBox(top, new Vector3(0.5f,0.5f,5),transform.rotation,mask);
foreach (var col in cols)
{
lockTarget = col.gameObject;
}
}
bool Inwall()
{
RaycastHit hit;
LayerMask mask = (1 << LayerMask.NameToLayer("Player")) | (1 << LayerMask.NameToLayer("Ignore Raycast"))| (1 << LayerMask.NameToLayer("Mob"))|(1<<LayerMask.NameToLayer("Weapon")); //將物體的Layer設置爲Ignore Raycast,Player和Mob來忽略相機的射線,不然相機將跳到某些物體前,比如怪物,玩家等,
mask = ~mask;//將以上的mask取反,表示射線將會忽略以上的層
//Debug.DrawLine(CameraPivot.transform.position, transform.position - transform.forward, Color.red);
PredictCameraPosition = CameraPivot.transform.position + offset.normalized * freeDistance ;//預測的相機位置
if (Physics.Linecast(CameraPivot.transform.position, PredictCameraPosition, out hit, mask))//碰撞到任意碰撞體,注意,因爲相機沒有碰撞器,所以是不會碰撞到相機的,也就是沒有碰撞物時說明沒有遮擋
{//也就是說,這個if就是指被遮擋的情況
wallHit = hit.point;//碰撞點位置
//Debug.DrawLine(transform.position, wallHit, Color.green);
return true;
}
else//沒碰撞到,也就是說沒有障礙物
{
return false;
}
}
void FreeCamera()
{
offset = offset.normalized * freeDistance;
transform.position = CameraPivot.transform.position + offset;//更新位置
if (CanControlDirection)//控制角色方向開關
{
Quaternion TargetBodyCurrentRotation = TargetBody.transform.rotation;
if (Input.GetKey(KeyCode.A))
{
if (Input.GetKey(KeyCode.W))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 45, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
else if (Input.GetKey(KeyCode.S))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 135, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
else if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 90, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
}
else if (Input.GetKey(KeyCode.D))
{
if (Input.GetKey(KeyCode.W))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y + 45, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
else if (Input.GetKey(KeyCode.S))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y + 135, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
else if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y + 90, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
}
else if (Input.GetKey(KeyCode.W))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
else if (Input.GetKey(KeyCode.S))
{
TargetBody.transform.rotation = Quaternion.Lerp(TargetBodyCurrentRotation, Quaternion.Euler(new Vector3(TargetBody.transform.localEulerAngles.x, transform.localEulerAngles.y - 180, TargetBody.transform.localEulerAngles.z)), TargetBodyRotateLerp);
}
}
if(canControlDistance)//控制距離開關
{
freeDistance -= Input.GetAxis("Mouse ScrollWheel") * distanceSpeed;
}
freeDistance = Mathf.Clamp(freeDistance, minDistance, maxDistance);
if(!lockTarget)
{
transform.LookAt(lockTarget ? (lockTarget.transform.position ): CameraPivot.transform.position);
}
else
{
Quaternion tmp = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(lockTarget.transform.position - transform.position), lockSlerp*Time.fixedDeltaTime);
transform.rotation = tmp;
}
float eulerX = transform.localEulerAngles.x;//相機的x歐拉角,也就是垂直方向.
float inputY = Input.GetAxis("Mouse Y");
if (!lockTarget)
{
//垂直視野限制
if (!lockTarget)
{
transform.RotateAround(CameraPivot.transform.position, Vector3.up, rotateSpeed * Input.GetAxis("Mouse X"));//x不用限制
}
if (eulerX > maxDepression && eulerX < 90)//當向上角度越界時
{
if (inputY > 0)//如果鼠標時在向下滑動
transform.RotateAround(CameraPivot.transform.position, Vector3.right, -rotateSpeed * inputY);//允許滑動
}
else if (eulerX < 360-maxEvelation && eulerX > 270)
{
if (inputY < 0)
transform.RotateAround(CameraPivot.transform.position, Vector3.right, -rotateSpeed * inputY);
}
else//角度正常時
{
transform.RotateAround(CameraPivot.transform.position, Vector3.right, -rotateSpeed * inputY);
}
}
if (lockTarget)
{
offset = CameraPivot.transform.position - (lockTarget.transform.position);
}
else
{
offset = transform.position - CameraPivot.transform.position;//以上方向發生了變化,記錄新的方向向量
}
offset = offset.normalized * freeDistance;
///在一次FixedUpdate中,隨時記錄新的旋轉後的位置,然後得到方向,然後判斷是否即將被遮擋,如果要被遮擋,將相機移動到計算後的不會被遮擋的位置
///如果不會被遮擋,則更新位置爲相機焦點位置+方向的單位向量*距離
///
if (Inwall())//預測會被遮擋
{
//print("Inwall");
transform.position = CameraPivot.transform.position + (wallHit - CameraPivot.transform.position) * 0.8f;
return;
}
else
{
transform.position = CameraPivot.transform.position + offset;
}
}
// Update is called once per frame
void FixedUpdate () {
FreeCamera();
if(lockTarget)
{
if (!marked)
{
tmpMark = Instantiate(lockMark, lockTarget.transform.position + new Vector3(0, 2.5f, 0), transform.rotation);
tmpMark.transform.forward = -Vector3.up;
marked = true;
}
else
{
tmpMark.transform.position = lockTarget.transform.position + new Vector3(0, 2.5f, 0);
//tmpMark.transform.forward= -transform.up;
tmpMark.transform.Rotate(Vector3.up *30* Time.fixedDeltaTime,Space.World);
}
}
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.F))
{
LockTarget();
}
}
}