此摄像机功能:鼠标滑轮控制摄像机可视范围大小,按住鼠标右键水平滑动使摄像机围绕目标Y轴旋转,按住鼠标右键垂直滑动使摄像机围绕目标(虚拟)X轴旋转。
虚拟X轴:摄像机局部坐标系的XY平面映射到目标身上后的X轴。如下图所示:
(请自行脑补在XY轴平面映射到小球身上后的虚拟X轴)
为什么要提出这个虚拟X轴概念?后面会解释,或者不用解释就有人立马懂了,下面直接贴代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public GameObject followTarget;//玩家物体
public float scrollWheelWeight = 1f;//滑轮控制权重(控制摄像机fieldOfView)
public int minFieldOfView = 25;//摄像机可视区域最小值
public int maxFieldOfView = 80;//摄像机可视区域最大值
public float rotateYRate = 1f;//水平控制摄像机绕玩家Y轴旋转
public float rotateXRate = 1f;//垂直控制摄像机绕自身X轴旋转
public float rotateXMin = 6; // 最小X轴旋转角度
public float rotateXMax = 80; // 最大X轴旋转角度(若要大于90度,请将Update的lookAt朝向目标代码删掉)
private Vector3 offset;
private float fieldOfView;
void Start()
{
offset = transform.position - followTarget.transform.position;
fieldOfView = Camera.main.fieldOfView;
}
void Update()
{
float mouseScrollWheel = Input.GetAxis("Mouse ScrollWheel");
if (mouseScrollWheel != 0)
{
Camera.main.fieldOfView += mouseScrollWheel * scrollWheelWeight;
if (Camera.main.fieldOfView <= minFieldOfView)
{
Camera.main.fieldOfView = minFieldOfView;
}
if (Camera.main.fieldOfView >= maxFieldOfView)
{
Camera.main.fieldOfView = maxFieldOfView;
}
}
if (Input.GetMouseButton(1))
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
if (mouseX != 0 && Mathf.Abs(mouseX) > Mathf.Abs(mouseY))
{
transform.RotateAround(followTarget.transform.position, followTarget.transform.up, mouseX * rotateYRate * Time.deltaTime);
//朝向目标点
transform.LookAt(followTarget.transform);
offset = transform.position - followTarget.transform.position;
}
if (mouseY != 0 && Mathf.Abs(mouseX) < Mathf.Abs(mouseY))
{
//旋转轴(摄像机局部坐标系XY平面映射到目标同一XY平面的虚拟X轴) 绕的就是这个虚拟X轴旋转
Vector3 axis = Vector3.right;
//摄像机偏移正X轴(1,0,0)一单位的世界坐标点
Vector3 worldPositionRightPoint = transform.position + transform.right;
//获取摄像机与目标之间的距离
float distance = Vector3.Distance(transform.position, followTarget.transform.position);
//映射到目标同一平面(平面是摄像机自身坐标系XY面)
worldPositionRightPoint = worldPositionRightPoint + (transform.forward).normalized * distance;
//求出目标坐标点与 映射过去的点的向量 作为旋转轴
axis = worldPositionRightPoint - followTarget.transform.position;
//记录原始旋转、位置、欧拉角
Quaternion oldRotation = transform.rotation;
Vector3 oldEulerAngles = transform.eulerAngles;
Vector3 oldPosition = transform.position;
//绕目标的虚拟X轴旋转
transform.RotateAround(followTarget.transform.position, axis, mouseY * rotateXRate * Time.deltaTime);
//*反射调用transform的私有成员和私有方法获取原生eulerAngles(transform.eulerAngle是非原生的,会将大于90的数值用180减于大于90的数并返回出来,I don't know why!)*
float eulerAngleX = GetInspectorRotationValue.Instance.GetInspectorRotationValueMethod(transform).x;
if ((eulerAngleX < rotateXMin || eulerAngleX > rotateXMax))
{
transform.position = oldPosition;
transform.rotation = oldRotation;
}
//朝向目标点(这句代码实际上是会诱发BUG的,因为当X轴旋转超出90度后,摄像机就会去
//到Y轴的另一面,这样再lookAt一下,导致rotation的Y值反转180度。
transform.LookAt(followTarget.transform);
offset = transform.position - followTarget.transform.position;
//下面代码仅仅是围绕自身X轴进行的旋转(不够好。。)
//transform.RotateAround(transform.position, transform.right, mouseY * rotateXRate * Time.deltaTime);
//float rotationX = transform.rotation.x;
////if (rotationZ < rotateZMin)
////{
//// rotationZ = rotateZMin;
////}
////if (rotationZ > rotateZMax)
////{
//// rotationZ = rotateZMax;
////}
//Quaternion quaternion = transform.rotation;
//quaternion.x = rotationX;
//transform.rotation = quaternion;
}
}
transform.position = offset + followTarget.transform.position;
}
}
如果看完上面还不是能够理解,为什么要有虚拟X轴的话,假设:鼠标垂直移动控制摄像机围绕目标的X轴旋转,这时恰巧是正常的,因为目标X轴与摄像机XY轴屏幕映射的虚拟X轴是一样的。但是,当摄像机围绕目标Y轴旋转后,你再围绕目标的X轴旋转,就不是那种“正常”的旋转,因为旋转轴与摄像机局部坐标XY平面并不水平,不水平导致的后果就是(你自己测试下,把那个旋转轴改为目标X轴)。
把该脚本挂到摄像机上,然后设置参数即可。摄像机初始状态它的Z轴指向目标(最好与我保持一致,可能有BUG)
注意:transform.eulerAngles返回的数值是已经经过底层修改的,测试发现当值是0~90时返回0~90正常,当90~180之间时返回的是 (180 - X),也就是你永远获取到的只都是0~90,而不是Inspector面板上的值,假设我Inspector面板上rotation X是150,那么eulerAngles.x是30. 只有使用如下方法才可以获取到真正的值!(下面这篇文章有说,使用到了反射的)
https://blog.youkuaiyun.com/qq_34444468/article/details/83929841(若链接无效,可评论留言Call我)
这个文章的代码是单例,所以上面的2个代码都用到了这个单例(GetInspectorRotationValue)获取真正的值。