使用方法:
将要控制的角色拖到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();
}
}
}