镜头控制对于游戏开发而言必不可少。以下是第三人称镜头控制知识记录。
脚本中涉及的注意点:
一、让镜头跟随角色背后,面向角色比并保持一定距离。
1、相机观看的点:
由于角色的物体原点是固定的,在实际使用中并不灵活。所以需要根据角色原点变换出一个Vector3并实例化一个Vector3变量target用于保存相机观测的点,通过transform.LookAt(target)来使镜头面相目标点。
2、相机的位置:
如果实时赋值给相机位置,相机的动作会特别生硬,所以也需要一个cam_target来保存相机最终移动的位置,通过插值的方式让相机移动到cam_target,这样可以解决相机位置即变即停的问题。
m_camera.position = Vector3.Slerp(m_camera.position, cam_target, moveSpeed * Time.deltaTime);
3、距离限制:
通过观测点减去距离乘以观测点到相机的向量计算出相机位置来控制距离。
cam_target = target - (target-m_camera.position).normalized * distance;
二、以观测点为中心,相机围绕旋转观测。
1、镜头变换的鼠标输入:
通过Input.GetAxis(“Mouse X”)来获取鼠标在水平方向的坐标偏移,通过Input.GetAxis(“Mouse Y”)来获取鼠标在垂直方向的坐标偏移。
H = Input.GetAxis("Mouse X");//水平方向
V = Input.GetAxis("Mouse Y");//垂直方向
2、镜头的旋转:
通过transform.RotateAround(Vector3 point, Vector3 axis, float angle)来旋转镜头,参数中point为要围绕旋转的点。axis为绕哪个轴旋转,在水平围绕旋转中,以世界竖直向上的方向为轴,即Vector3.up;而垂直方向的旋转要以相机的水平方向作为围绕旋转的轴,即m_camera.right或m_camera.left。最后的参数为旋转速度,再根据上一点获取到的鼠标坐标变化值H和V来判断水平方向和垂直方向是否需要旋转,在鼠标水平或垂直方向没变化时,H或V值为0。
//鼠标输入
if (H != 0 || V != 0)
{
//水平方向围绕旋转
m_camera.RotateAround(target, Vector3.up, H * rotateSpeed);
//垂直
m_camera.RotateAround(target, m_camera.right, -V * rotateSpeed);
}
三、角度限制,限制相机与观测点之间的角度。同时通过鼠标滚轮控制镜头与角色的距离
1、角度限制:
水平旋转不受限制,主要限制垂直方向的旋转,由于transform.eulerAngles.x的范围为0-360度,在以自身m_camera.right作为旋转轴的情况下,相机处于水平方向时为0度,往上到Vector3.up为90度,往下到Vector3.down为270度,例如限制活动范围为120度,则范围表示为:m_camera.eulerAngles.x > limit_angle / 2
&& m_camera.eulerAngles.x < 360f - limit_angle / 2。
2、实现方式:
实现第一点的相关措施则是保存上一帧的角色和相机的位置以及相机的角度,所以这里用到三个临时记录的变量。处理时判断m_camera.eulerAngles.x的大小,若当前在合规范围内就将角度和位置保存到临时变量中,若超过合规范围就将临时值重新赋予到相机中,由于人物是会移动的,上一帧保存的相机有效位置,就会可能在当前帧变为非有效(角色向相机方向移动导致上一帧位置与相机之间角度超出合规范围),这样会出现,角色走着走着,相机就跑到了角色的头顶上了。所以也要保存上一帧观测点target的位置,通过上一帧的相机与观测点的位移偏差去运算出基于当前帧的观测点的相机位置。
//保存上一帧的位置和角度(限制角度时用到)
Vector3 temp_pos;
Vector3 temp_rot;
Vector3 temp_targetpos;//观察点
//限制旋转角度,60>rotation.x>-60
if (m_camera.eulerAngles.x > limit_angle / 2
&& m_camera.eulerAngles.x < 360f - limit_angle / 2)
{
//恢复上一帧的位置
m_camera.position = target + (temp_pos - temp_targetpos).normalized * distance;//重新计算位置
cam_target = m_camera.position;
m_camera.eulerAngles = temp_rot;
}
else
{
//保存范围内的位置和角度
temp_pos = m_camera.position;
temp_rot = m_camera.eulerAngles;
temp_targetpos = target;
}
3、通过滚轮来控制距离:
获取鼠标的滚动通过Input.GetAxis(“Mouse ScrollWheel”)的方式获取。由于也有最大距离与最短距离的限制,所以可以通过Mathf.Clamp方法去限制最大距离与最小距离。
//鼠标滚动调整距离
distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * scrollwheel_speed,
min_distance, max_distance);
四、完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public Transform m_camera;//相机
public Transform player;//玩家
Vector3 target;//相机摄影目标
Vector3 cam_target;//相机位置
public float distance = 2f;//当前相机与对象的距离
public float default_distance = 2f;//默认距离
public float max_distance = 5f;//相机与目标最大距离
public float min_distance = 1f;//相机与目标最短距离
public float limit_angle = 120f;//角度限制(最高与最低之间的角度)
public float scrollwheel_speed = 5f;//鼠标滚动调整距离的速度
public float moveSpeed = 2f;//跟随速度
public float rotateSpeed = 4f;//旋转速度
//目标偏移量
public float offsetX = 0f;
public float offsetY = 1.4f;
public float offsetZ = 0f;
//输入偏移量
float H = 0f;//水平偏移量
float V = 0f;//垂直偏移量
//保存上一帧的位置和角度(限制角度时用到)
Vector3 temp_pos;
Vector3 temp_rot;
Vector3 temp_targetpos;//观察点
private void Start()
{
temp_pos = m_camera.position;
temp_rot = m_camera.eulerAngles;
temp_targetpos = Vector3.zero;
}
private void Update()
{
//代码处理前首先让镜头面向角色,好处理往后的角度问题
m_camera.LookAt(target);
//鼠标滚动调整距离
distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * scrollwheel_speed,
min_distance, max_distance);
//获取鼠标移动
H = Input.GetAxis("Mouse X");//水平方向
V = Input.GetAxis("Mouse Y");//垂直方向
//计算观测对象的坐标
target = player.position + Vector3.up * offsetY;
//计算摄像机的目标位置
cam_target = target - (target-m_camera.position).normalized * distance;
//鼠标输入
if (H != 0 || V != 0)
{
//水平方向围绕旋转
m_camera.RotateAround(target, Vector3.up, H * rotateSpeed);
//垂直
m_camera.RotateAround(target, m_camera.right, -V * rotateSpeed);
}
//限制旋转角度,60>rotation.x>-60
if (m_camera.eulerAngles.x > limit_angle / 2
&& m_camera.eulerAngles.x < 360f - limit_angle / 2)
{
//恢复上一帧的位置
m_camera.position = target + (temp_pos - temp_targetpos).normalized * distance;//重新计算位置
cam_target = m_camera.position;
m_camera.eulerAngles = temp_rot;
}
else
{
//保存范围内的位置和角度
temp_pos = m_camera.position;
temp_rot = m_camera.eulerAngles;
temp_targetpos = target;
}
m_camera.position = Vector3.Slerp(m_camera.position, cam_target, moveSpeed * Time.deltaTime);
}
}