【Unity】初级版摄像机

本文详细介绍了在Unity中如何通过鼠标操作控制摄像机的可视范围、旋转及围绕目标旋转的方法,特别强调了虚拟X轴的概念及其在摄像机控制中的应用,通过代码示例展示了具体的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此摄像机功能:鼠标滑轮控制摄像机可视范围大小,按住鼠标右键水平滑动使摄像机围绕目标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)获取真正的值。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值