可直接使用的unity第三人稱自由視角相機指令碼
使用方法:
將要控制的角色拖到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();
}
}
}