Unity笔记:相机移动

news2025/7/8 6:30:13

基础知识

鼠标输入

在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轴)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

考勤|基于Springboot的大学生考勤系统设计与实现(源码+数据库+文档)

大学生考勤系统目录 目录 基于Springboot的大学生考勤系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、系统登录注册 2、管理员功能模块 3、教师功能模块 4、学生功能模块 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码…

【CSS + ElementUI】更改 el-carousel 指示器样式且隐藏左右箭头

需求 前三条数据以走马灯形式展现&#xff0c;指示器 hover 时可以切换到对应内容 实现 <template><div v-loading"latestLoading"><div class"upload-first" v-show"latestThreeList.length > 0"><el-carousel ind…

@所有人 您需要的 幻兽帕鲁服务器搭建教程 已上线

所有人 您需要的 幻兽帕鲁服务器搭建教程 已上线 幻兽帕鲁一键购买及部署体验购买及部署购买云服务器ECS部署幻兽帕鲁 创建账户并登录Steam其他操作更新服务器修改游戏参数其他操作释放资源 一直拖到今天才来写这篇幻兽帕鲁服务器搭建教程&#xff0c;确实是因为前段时间有事耽…

【Rust】——rust前言与安装rust

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

机器学习系列5-特征组合、简化正则化

1.特征组合 1.1特征组合&#xff1a;编码非线性规律 我们做出如下假设&#xff1a;蓝点代表生病的树。橙色的点代表健康的树。 您可以绘制一条直线将生病的树与健康的树清晰地分开吗&#xff1f;不可以。这是一个非线性问题。您绘制的任何线条都无法很好地预测树的健康状况…

R语言学习case12:ggplot 置信区间(多线型)

接上文&#xff1a;多条曲线 R语言学习case11&#xff1a;ggplot 置信区间&#xff08;包含多子图&#xff09; 在ggplot2中&#xff0c;每个geom函数都接受一个映射参数。然而&#xff0c;并非每个美学属性都适用于每个geom。你可以设置点的形状&#xff0c;但不能设置线的“…

群晖NAS开启FTP服务结合内网穿透实现公网远程访问本地服务

⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 文章目录 ⛳️ 推荐1. 群晖安装Cpolar2. 创建FTP公网地址3. 开启群晖FTP服务4. 群晖FTP远程连接5. 固定FTP公网地址6. 固定FTP…

产品经理学习-产品运营《如何策划一场活动》

互联网活动怎么玩 最常听到的有&#xff1a; 注册有奖、拉新有奖 签到积分 秒杀、大促、神券 和过去相比&#xff0c;现在活动的特征变化&#xff1a; 线上化、形式丰富、覆盖人群广、即时性、效果可控 什么是活动运营 通过策划不同形式的活动&#xff0c;进行有效的资源和…

LFU缓存(Leetcode460)

例题&#xff1a; 分析&#xff1a; 这道题可以用两个哈希表来实现&#xff0c;一个hash表&#xff08;kvMap&#xff09;用来存储节点&#xff0c;另一个hash表&#xff08;freqMap&#xff09;用来存储双向链表&#xff0c;链表的头节点代表最近使用的元素&#xff0c;离头节…

SpringBoot注解--02---常用注解汇总

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.SpringBoot 配置启动注解SpringBootApplication 2.Bean处理注解2.1 依赖注入AutoWired、Qualifier、Resource 2.2 类被 Spring 容器创建&#xff0c;管理 iocComp…

基于ESP8266 开发板(MCU)遥控小车

遥控小车 ​ 遥控界面 ​ 【项目源码】 第一版ESP8266 https://github.com/liyinchigithub/esp8266_car_webServerhttps://github.com/liyinchigithub/esp8266_car_webServer 第二版ESP32 GitHub - liyinchigithub/esp32-wroom-car: 嵌入式单片机 ESP32 Arduino 遥控小车&a…

第16届大广赛命题详情它来啦!

“中国大学生创造力”全国大学生广告艺术竞赛&#xff08;以下简称&#xff1a;广播竞赛&#xff09;作为高水平三维生产教育一体化、科学教育一体化竞争平台&#xff0c;坚持高地位&#xff0c;基于大模式&#xff0c;在19年的发展过程中&#xff0c;坚持道德培养人才的基础&a…

MySQL温故篇(一)SQL语句基础

一、SQL语句基础 1、SQL语言分类 DDL&#xff1a;数据定义语言 DCL&#xff1a;数据控制语言 DML&#xff1a;数据操作语言 DQL&#xff1a;数据的查询语言 2、数据类型 3、字符类型 char(11) &#xff1a; 定长 的字符串类型,在存储字符串时&#xff0c;最大字符长度11个&a…

TypeError: wave.ensureState is not a function 水球图引入报错问题

TypeError: wave.ensureState is not a function 水球图引入报错问题 什么问题&#xff1f; 版本问题 echarts4.x 版本 适用于 echarts-liquidfill2.x.x版本 echarts5.x 版本 适用于 echarts-liquidfill3.x.x版本 完美解决

使用PDFBox实现pdf转其他图片格式

最近在做一个小项目&#xff0c;项目中有一个功能要把pdf格式的图片转换为其它格式&#xff0c;接下来看看用pdfbox来如何实现吧。 首先导入pdfbox相关依赖&#xff1a; <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</a…

【FPGA】快速学习路径

FPGA学习教程、功利式学习路径、以找工作为目的&#xff0c;早日入门FPGA_哔哩哔哩_bilibili

解决git切换分支导致代码丢失的问题

问题描述&#xff1a; 最近写项目时&#xff0c;我在主分支&#xff08;master分支&#xff09;上面写的代码&#xff0c;但是我没有提交&#xff08;Commit&#xff09;到Git上。但是又碰到一个新的需求&#xff0c;所以需要去新建一个分支&#xff0c;当我切换到新建的分支&a…

【C语言初阶-const作用详解】const修饰变量、const修饰指针(图文详解版)

少年&#xff0c;做你认为对的事 目录 少年&#xff0c;做你认为对的事 1.const修饰变量 2.const修饰指针&#xff08;重要&#xff09; 代码1&#xff1a; 代码2&#xff1a; 代码3&#xff1a; ​编辑 3.结论 1.const修饰变量 const修饰变量将变量赋予了常量属性…

SpringBoot Security安全认证框架初始化流程认证流程之源码分析

SpringBoot Security安全认证框架初始化流程&认证流程之源码分析 以RuoYi-Vue前后端分离版本为例分析SpringBoot Security安全认证框架初始化流程&认证流程的源码分析 目录 SpringBoot Security安全认证框架初始化流程&认证流程之源码分析一、SpringBoot Security安…

(源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模

本篇文章是: 2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模的源码版本,包含具体建模代码到生成模型步骤。那么废话不多说直接开始展示建模过程建模: 数据预处理 之前我给大家提供的一年的风暴数据是远远不够的,要做时…