Unity笔记:相机移动

基础知识

鼠标输入

在Unity中,开发者在“Edit” > “Project Settings” > “Input Manager”中设置输入,如下图所示:

在设置了Mouse X后,Input.GetAxis("Mouse X")返回的是鼠标在X轴上的增量值。这意味着它会返回鼠标在上一帧和当前帧之间的变化量。如果鼠标在X轴上向右移动,返回值将是正数;如果鼠标向左移动,返回值将是负数。

根据Unity官方文档 - Input.GetAxis的说明,这里要强调的是:Input.GetAxis返回由axisName标识的虚拟轴的值(没说是增量)

对于键盘和操纵杆输入设备,该值将在 [ − 1 , 1 ] [-1,1] [1,1]的范围内。

如果将轴映射到鼠标,则值会有所不同,并且不会在 [ − 1 , 1 ] [-1,1] [1,1]的范围内。相反,它将是当前鼠标增量乘以轴灵敏度。通常,正值表示鼠标向右/向下移动,负值表示鼠标向左/向上移动。

这与帧速率无关;使用此值时,不需要担心帧速率的变化。

相机代码及解析

基本代码

using UnityEngine;

public class CameraController: MonoBehaviour
{
    [Tooltip("the object that camera look at")]
    public Transform targetObject;  // 指定的物体
    public float distance = 5.0f;   // 相机距离

    public float rotationSpeed = 2.0f;  // 旋转速度

    private float currentRotationX = 0.0f;
    private float currentRotationY = 0.0f;

    void Start()
    {
        if (targetObject == null)
        {
            Debug.LogError("Please assign a target object to the CameraManager script.");
        }
    }

    void Update()
    {
        // 获取鼠标输入
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        // 计算旋转角度,单位是度
        currentRotationX -= mouseY * rotationSpeed;
        currentRotationY += mouseX * rotationSpeed;

        // 限制垂直旋转角度在-90到90度之间
        currentRotationX = Mathf.Clamp(currentRotationX, -90.0f, 90.0f);

        // 计算相机位置
        Vector3 direction = new Vector3(0, 0, -distance);
        Quaternion rotation = Quaternion.Euler(currentRotationX, currentRotationY, 0);
        Vector3 cameraPosition = targetObject.position + rotation * direction;

        // 应用相机位置和旋转
        transform.position = cameraPosition;
        transform.LookAt(targetObject.position);
    }
}

主要先旋转相机,再通过LookAt方法使之朝向人物就完事了。所以核心就是计算相机位置的三行代码(如下)。

// 计算相机位置
Vector3 direction = new Vector3(0, 0, -distance);
Quaternion rotation = Quaternion.Euler(currentRotationX, currentRotationY, 0);
Vector3 cameraPosition = targetObject.position + rotation * direction;

Quaternion×Vector3的意义:四元数×三维向量会得到一个新的Vector3,表示旋转后的新方向。

首先定义了一个叫做direction的三维向量,这里说实话我本身没想明白,困扰了很久。
在这里插入图片描述
(上图略去了y轴)

但是我查了一下,一个局部坐标系的Vector3变量乘以一个世界坐标系表示旋转操作的四元数,其结果是世界坐标系的Vector3变量

那这样就好理解了,这个direction不是世界坐标系的(不是图中红色箭头),而是上图那个局部坐标系的,它代表了一个从相机朝向目标物体的一个向量,尽管相机是时刻移动的(相应地这个局部坐标系在全局坐标系的位置和方向也在变),但是乘完之后总是能得到一个全局的、经过旋转变换之后的量。

延伸1:Unity默认局部坐标系的Z轴是对象的前方(朝向),X轴是右方,Y轴是上方。

延伸2:在Quaternion乘法中,顺序很重要,因为乘法是不符合交换律的。

相比之下一种看似正确实则错误的写法是如下这种(显然是没理解direction到底是啥):

// 计算相机位置
Vector3 direction = new Vector3(0, 0, -distance);
Quaternion rotation = Quaternion.Euler(currentRotationX, currentRotationY, 0);
Vector3 globalOffset = camTransform.TransformPoint(rotation * direction);
Vector3 cameraPosition = targetObject.position + globalOffset;

碰撞检测与SpringArm

碰撞检测和弹簧臂是相辅相成的。

如果游戏环境中有障碍物,就得考虑实现碰撞检测,以防止相机穿过物体。(通常需要使用Raycast来检查相机和目标之间的障碍物)。但是硬生生直接变化相机和人物的距离有些生硬,所以还要实现弹簧臂。

弹簧臂的基本原理是将相机连接到一个虚拟的弹簧臂结构上,这个结构允许相机在跟随目标的同时保持一定的弹性。弹簧臂可以有一定的长度,角度和阻尼参数,使相机能够在目标移动时更加平滑地跟随。

在Unity中,可以使用SpringJoint或编写自定义的弹簧臂逻辑来实现这一点。弹簧臂常见的实现及其优缺点如下:

  1. Transform.Lerp:

    • 优点:
      • 实现简单,易于理解。
      • 不需要额外的组件或配置。
    • 缺点:
      • 在快速移动的情况下,可能会感觉到迟滞,因为它只是简单地进行线性插值,可能无法捕捉到加速和减速的变化。
  2. SpringJoint组件:

    • 优点:
      • 使用物理引擎,可以产生更真实的弹簧效果。
      • 不需要手动编写平滑插值逻辑。
    • 缺点:
      • 对性能的影响较大,特别是在移动的物体较多的情况下。
      • 需要调整SpringJoint的参数来获取理想的效果,这可能需要一些调试工作。
  3. Vector3.SmoothDamp:

    • 优点:
      • 提供更高级的平滑插值算法,能够更好地处理加速和减速。
      • 相比Lerp,更适合处理相机跟随玩家运动的场景。
    • 缺点:
      • 可能需要调整参数以适应不同的场景。
      • 对于初学者来说,可能需要一些时间来理解其工作原理。

有时候也可以结合多个方法,根据不同的情况使用不同的技术。例如,在高速移动时使用Vector3.SmoothDamp,在需要物理效果时使用SpringJoint

碰撞检测实现

using UnityEngine;

public class CameraController: MonoBehaviour
{
    [Tooltip("the object that camera look at")]
    public Transform targetObject;  // 指定的物体
    [Tooltip("the distance that from the camera to the target")]
    public float distance = 5.0f;   // 相机距离
    [Tooltip("碰撞检测平滑过渡的时间")]
    public float smoothTime = 0.3f;
    [Tooltip("相机碰撞检测的层")]
    public LayerMask obstacleLayer;
    public float rotationSpeed = 2.0f;  // 旋转速度

    // camera rotate
    private float currentRotationX = 0.0f;
    private float currentRotationY = 0.0f;
    private Transform camTransform;
    // SpringArm
    private Vector3 velocity = Vector3.zero;

    void Start()
    {
        if (targetObject == null)
        {
            Debug.LogError("Please assign a target object to the CameraManager script.");
        }
        camTransform = GetComponent<Transform>();
    }

    void Update()
    {
        // 获取鼠标输入
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        // 计算旋转角度,单位是度
        currentRotationX -= mouseY * rotationSpeed;
        currentRotationY += mouseX * rotationSpeed;

        // 限制垂直旋转角度在-85到85度之间
        currentRotationX = Mathf.Clamp(currentRotationX, -85.0f, 85.0f);

        // 计算相机位置
        Vector3 direction = new Vector3(0, 0, -distance);
        Quaternion rotation = Quaternion.Euler(currentRotationX, currentRotationY, 0);
        Vector3 desiredCameraPosition = targetObject.position + rotation * direction;

        // 进行碰撞检测并调整相机位置
        RaycastHit hit;
        if (Physics.Raycast(targetObject.position, desiredCameraPosition - targetObject.position, out hit, distance, obstacleLayer))
        {
            // 如果碰撞到障碍物,调整相机位置
            Vector3 adjustedCameraPosition = hit.point;
            transform.position = Vector3.SmoothDamp(transform.position, adjustedCameraPosition, ref velocity, smoothTime);
        }
        else
        {
            // 如果没有碰撞,直接移动相机到目标位置
            // 这里也可以使用SmoothDamp,但是效果有些怪
            transform.position = desiredCameraPosition;
        }
        // 应用相机旋转
        transform.LookAt(targetObject.position);
    }
}

主要就是在应用transform.position的时候前进行碰撞检测并查看其结果,若有碰撞结果就把相机位置改到那里。
当然这样的话碰到障碍物变化起来也有些生硬,可以再观察别的游戏细化,加更多的逻辑。而且这里的碰撞点是很不合适的(随着人物靠近障碍物,相机总是靠近人物脚,所以还得改y轴)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/373362.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arm 汇编积累

C语言函数与汇编对应关系 一、MOV 系列指令 1、指令格式 MOV{条件}{S} 目的寄存器&#xff0c;源操作数 2、含义解析&#xff1a; &#xff08;1&#xff09;&#xff1a;mov 指令传送数据 案例&#xff1a; MOV R0,R1 ; R0 R1; MOV PC,R14 ;PC R14; MOV R0,R…

大型语言模型(LLM)的优势、劣势和风险

最近关于大型语言模型的奇迹&#xff08;&#xff09;已经说了很多LLMs。这些荣誉大多是当之无愧的。让 ChatGPT 描述广义相对论&#xff0c;你会得到一个非常好&#xff08;且准确&#xff09;的答案。然而&#xff0c;归根结底&#xff0c;ChatGPT 仍然是一个盲目执行其指令集…

N-143基于springboot博客系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 前端技术&#xff1a;AdminLTEHTML 服务端技术&#xff1a;springbootmybatis-plusthymeleaf 本项目分前台和后台&#xff0c;主要有普…

annaconda如何切换当前python环境

annaconda默认的python环境是base&#xff1a; 把各种项目的依赖都安装到base环境中不是一个好的习惯&#xff0c;比如说我们做爬虫项目和做自动化测试项目等所需要的依赖是不一样的&#xff0c;我们可以将为每个项目创建自己的环境&#xff0c;在各自的环境中安装自己的依赖&…

博途PLC报警字FC(字寄存器按位访问)

博途PLC的字寄存器按位访问和拆分,请查看下面文章链接: https://rxxw-control.blog.csdn.net/article/details/121727057https://rxxw-control.blog.csdn.net/article/details/121727057西门子触摸屏报警都是以字为地址访问,所以离散报警信号我们需要将其组合为报警字输出,…

问题排查利器 - 分布式 trace

在分布式系统开发中&#xff0c;系统间的调用往往会横跨多个应用之间的接口。负责的调用链路也导致了&#xff0c;当线上环境出现问题时&#xff0c;例如请求失败、延迟增加或错误发生&#xff0c;我们无法第一时间确定是哪个环节出了问题&#xff0c;这给故障排查和修复带来了…

Java-冒泡排序

签名&#xff1a;但行好事&#xff0c;莫问前程。 文章目录 前言一、什么是冒泡排序二、手写冒泡排序总结 前言 记录一下经典算法&#xff1a;冒泡排序。 一、什么是冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;是一种基本的排序算法。其原理是通过相邻元素之间的比…

【多模态MLLMs+图像编辑】MGIE:苹果开源基于大语言模型的图片编辑神器(24.02.03开源)

项目主页&#xff1a;https://mllm-ie.github.io/ 论文2309.Guiding Instruction-based Image Editing via Multimodal Large Language Models 代码&#xff1a;https://github.com/apple/ml-mgie 媒体&#xff1a;机器之心的解析https://mp.weixin.qq.com/s/c87cUuyz4bUgfW2_m…

机器学习系列——(十一)回归

引言 在机器学习领域&#xff0c;回归是一种常见的监督学习任务&#xff0c;它主要用于预测数值型目标变量。回归分析能够通过对输入特征与目标变量之间的关系建模&#xff0c;从而对未知数据做出预测。 概念 回归是机器学习中的一种监督学习方法&#xff0c;用于预测数值型目…

新增同步管理、操作日志模块,支持公共链接分享,DataEase开源数据可视化分析平台v2.3.0发布

2024年2月5日&#xff0c;DataEase开源数据可视化分析平台正式发布v2.3.0版本。 这一版本的功能升级包括&#xff1a;新增“同步管理”功能模块&#xff0c;用户可通过此模块&#xff0c;将传统数据库中的数据定时同步到Apache Doris中&#xff0c;让数据分析更快速&#xff1…

怎么用postman调用webservice(反推SoapUI)

<soapenv:Envelope xmlns:soapenv“http://schemas.xmlsoap.org/soap/envelope/” xmlns:lis“LisDataTrasen”> soapenv:Header/ soapenv:Body lis:Test lis:test111111111</lis:test> </lis:Test> </soapenv:Body> </soapenv:Envelope> Conten…

最简单的基于 FFmpeg 的图像编码器(YUV 编码为 JPEG)

最简单的基于 FFmpeg 的图像编码器&#xff08;YUV 编码为 JPEG&#xff09; 最简单的基于 FFmpeg 的图像编码器&#xff08;YUV 编码为 JPEG&#xff09;正文结果工程文件下载 最简单的基于 FFmpeg 的图像编码器&#xff08;YUV 编码为 JPEG&#xff09; 参考雷霄骅博士的文章…

环境配置:Ubuntu18.04 ROS Melodic安装

前言 不同版本的Ubuntu与ROS存在对应关系。 ROS作为目前最受欢迎的机器人操作系统&#xff0c;其核心代码采用C编写&#xff0c;并以BSD许可发布。ROS起源于2007年&#xff0c;是由斯坦福大学与机器人技术公司Willow Garage合作的Switchyard项目。2012年&#xff0c;ROS团队从…

【DDD】学习笔记-数据模型与对象模型

在建立数据设计模型时&#xff0c;我们需要注意表设计与类设计之间的差别&#xff0c;这事实上是数据模型与对象模型之间的差别。 数据模型与对象模型 我们首先来分析在设计时对冗余的考虑。前面在讲解数据分析模型时就提及&#xff0c;在确定数据项模型时&#xff0c;需要遵…

网络协议与攻击模拟_15FTP协议

了解FTP协议 在Windows操作系统上使用serv-U软件搭建FTP服务 分析FTP流量 一、FTP协议 1、FTP概念 FTP&#xff08;文件传输协议&#xff09;由两部分组成&#xff1a;客户端/服务端&#xff08;C/S架构&#xff09; 应用场景&#xff1a;企业内部存放公司文件、开发网站时利…

js中this对象的理解(深度解析)

文章目录 一、定义二、绑定规则默认绑定隐式绑定new绑定显示修改 三、箭头函数四、优先级隐式绑定 VS 显式绑定new绑定 VS 隐式绑定new绑定 VS 显式绑定 参考文献 一、定义 函数的 this 关键字在 JavaScript 中的表现略有不同&#xff0c;此外&#xff0c;在严格模式和非严格模…

【精选】java继承进阶,子类继承父类(内存图、内存分析工具)

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

openssl3.2 - exp - buffer to BIO

文章目录 openssl3.2 - exp - buffer to BIO概述笔记END openssl3.2 - exp - buffer to BIO 概述 openssl的资料看的差不多了, 准备将工程中用到的知识点整理一下. openssl中很多API是以操作文件作为输入的, 也有很多API是以BIO作为输入的. 不管文件是不是受保护的, 如果有可…

Linux【docker 设置阿里源】

文章目录 一、查看本地docker的镜像配置二、配置阿里镜像三、检查配置 一、查看本地docker的镜像配置 docker info一般没有配置过是不会出现Registry字段的 二、配置阿里镜像 直接执行下面代码即可&#xff0c;安装1.10.0以上版本的Docker客户端都会有/etc/docker 1.建立配置…

离散数学——图论(笔记及思维导图)

离散数学——图论&#xff08;笔记及思维导图&#xff09; 目录 大纲 内容 参考 大纲 内容 参考 笔记来自【电子科大】离散数学 王丽杰