【unity】关于unity3D摄像机视角移动的几种方式详解

news2024/11/24 11:05:13

目录

一、前言

二、Transform基础

1、几种坐标系

2、position和localPosition属性

3、rotation属性

三、摄像机的平移

1、键盘控制平移

2、鼠标控制平移

3、整合 

四、摄像机的旋转

1、绕自身旋转

2、绕目标物体旋转

3、整合 

五、优化功能

1、调整速率

2、切换目标物体

3、设置常用摄像机观察点


一、前言

        在做虚拟仿真或数字孪生等项目中,常常会遇到需要自由移动视角的场景。最近在用unity制作一个有关3D打印机的数字孪生项目时遇到了这种情况,本文将结合unity和blender的视角移动功能,介绍一个类似这两个软件的视角移动的方法。

二、Transform基础

        Unity3D中的Transform组件是每个GameObject对象都有的组件,它控制着对象的位置、旋转和缩放。 在介绍视角移动前,本文将先介绍transform的position、rotation两个属性和unity中的坐标系。

1、几种坐标系

        在unity中常见的有两种坐标系,分别是全局坐标(World Space)和局部坐标(Local Space),在unity场景窗口(Scene)中可以切换这两种坐标系,如图所示:

        全局坐标:也称为世界坐标,表示物体在场景中的绝对位置和方向。全局坐标是相对于场景的原点(0,0,0)而言的。

        局部坐标:也称为相对坐标,表示物体相对于其父物体的位置和方向。局部坐标是相对于父物体的原点(0,0,0)而言的。

2、position和localPosition属性

        position和localPosition都是三维向量(Vector3),前者用于描述物体在场景中的绝对位置,即相对于世界原点的位置;后者描述物体相对于其父级对象的位置,以父物体为原点。

        在没有设置父物体时,Transform.position和Transform.localPosition的值相同,都等于物体的全局坐标。

        当物体P被设为物体Q的子物体时,物体P的Transform.localPosition表示与父物体Q的相对位置,当只改变父物体Q的Transform.position时,子物体P将跟随父物体Q移动,因此子物体P的Transform.position随物体Q改变,然而子物体P相对于父物体Q的相对位置不变,所以子物体P的Transform.localPosition保持不变。

        值得一提的是,在unity的检查器(Inspector)面板的Transform组件中,所显示的position为localPosition,对于无父物体的物体而言,其代表全局(世界)坐标,对于有父物体的物体而言,则表示两者的相对位置。

3、rotation属性

        和描述位置的属性一样,描述旋转的属性也分为rotation和localRotation。它是一个四元数(Quaternion)。rotation与localRotation的关系和position与localPosition一样,这里就不多做赘述。不过值得注意的一点是,在unity中文文档中提到:

        “请勿尝试编辑/修改 rotation。”

        “要旋转 Transform,请使用 Transform.Rotate,它将使用欧拉角。”

三、摄像机的平移

        在unity场景窗口中我们可以按住鼠标滚轮进行平移,而在blender中我们可以按住左Shift+鼠标滚轮平移视角,除此之外,在unity中也可以通过键盘上下左右键控制视角移动。而在项目中,我们将尝试控制摄像机平移模仿上述效果。

1、键盘控制平移

        键盘控制平移的代码非常简单,不过在此之前应该在unity的项目设置(Project Setting)中设置好输入管理器(Input Manager)。如下图所示:

        在脚本分别调用Input.GetAxis("Vertical")和Input.GetAxis("Horizontal")将返回一个float值,由于类型设置为“Key or Mouse Button”,则返回-1、0、1三个值。

        当按下键盘W或者↑键时Input.GetAxis("Vertical")将返回1,按下S或者↓键时将返回-1,不按任何键将返回0;同理当按下D或→键时Input.GetAxis("Horizontal")将返回1,按下A或者←键时将返回-1,不按则返回0。

        根据这个属性可得到控制平移的代码:

float moveZ = Input.GetAxis("Vertical") * moveSpeedWithKeyBoard * Time.deltaTime;
float moveX = Input.GetAxis("Horizontal") * moveSpeedWithKeyBoard * Time.deltaTime;
transform.position += transform.forward *  moveZ + transform.right * moveX;

2、鼠标控制平移

        我将用按下滚轮拖动鼠标的方式控制摄像机上下左右平移,滚动滚轮控制前后平移。

        首先是前后移动,确保你的输入管理器(Input Manager)设置如图所示:

        通过调用Input.GetAxis("Mouse ScrollWheel")获取前后移动的值,同样这个函数将返回一个float值,滚轮向前滚为正,向后为负。代码如下:

float MouseScroll = Input.GetAxis("Mouse ScrollWheel") * moveSpeedWithScroll * Time.deltaTime;
transform.position += transform.forward * MouseScroll;

       

        然后是上下左右移动 ,当鼠标滚轮按下时获取鼠标在xy轴移动的值:

if (Input.GetMouseButton(2))
{
    float moveX = -Input.GetAxis("Mouse X") * moveSpeedWithMouse * Time.deltaTime;
    float moveY = -Input.GetAxis("Mouse Y") * moveSpeedWithMouse * Time.deltaTime;

    transform.position += transform.right * moveX + transform.up * moveY;
}

3、整合 

        综合上述,不难发现这几段代码的相似之处:接收来自鼠标或键盘的输入并算出摄像机在forward、right、up三个方向上的移动值,再乘以对应的方向向量并加到相机的位置上,因此可以整合成以下函数:

/// <summary>
/// 相机移动
/// </summary>
public void Move()
{

    float moveX = 0;
    float moveY = 0;
    float moveZ = 0;

    // 接收键盘输入
    moveZ = Input.GetAxis("Vertical") * moveSpeedWithKeyBoard * Time.deltaTime;
    moveX = Input.GetAxis("Horizontal") * moveSpeedWithKeyBoard * Time.deltaTime;

    // 接收滚轮输入
    moveZ = Input.GetAxis("Mouse ScrollWheel") * moveSpeedWithScroll * Time.deltaTime;

    // 接收鼠标输入
    if (Input.GetMouseButton(2))
    {
        moveX = -Input.GetAxis("Mouse X") * moveSpeedWithMouse * Time.deltaTime;
        moveY = -Input.GetAxis("Mouse Y") * moveSpeedWithMouse * Time.deltaTime;
    }

    // 控制移动
    transform.position += transform.forward * moveZ + transform.right * moveX + transform.up * moveY;

}

四、摄像机的旋转

         常见的摄像机旋转有两种,即摄像机自身旋转和摄像机绕目标物体旋转。在unity的场景窗口中按住鼠标滚轮为绕自身旋转,按住左Alt+鼠标左键时绕选中物体旋转。下面我们将尝试做出上述效果。

1、绕自身旋转

        绕自身旋转的思路很简单,只需要获取鼠标输入并更改摄像机自身的旋转。代码如下:

if (Input.GetMouseButton(0))
{
    float rotateX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
    float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;

    Vector3 eulerAngles = transform.eulerAngles;

    eulerAngles.y += rotateX;
    eulerAngles.x -= rotateY;

    transform.eulerAngles = eulerAngles;
}
2、绕目标物体旋转

        绕目标物体转动较为复杂,需要同时改变position和rotation的值。首先确定目标物体,再改变position值,这个过程和上文的鼠标控制平移一样,接下来需要改变rotation的值确保摄像机始终面向目标物体,只需调用transform.LookAt()函数即可。

        当然使用上述方法可以粗略实现围绕旋转,但是依旧有个小问题,就是摄像机会在旋转的过程中逐渐远离目标物体,原因是我们每次改变position的值都将增加一点二者之间的距离(根据三角形的斜边大于直角边的原理),而后面的步骤没有抵消掉这个距离变化。

        所有在以上步骤之后还应矫正一下距离,将摄像机的位置在目标物体的方向上移动至距离不变的位置,最终的代码如下:

if (Input.GetMouseButton(1))
{
    float rotateX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
    float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;

    float distance = Vector3.Distance(target.transform.position, transform.position);

    transform.position += transform.right * -rotateX + transform.up * -rotateY;

    transform.LookAt(target.transform);

    transform.position = target.transform.position + -transform.forward * distance;
}
3、整合 

        同样的,我们将以上两段代码整合封装成函数,可以得到以下代码:

/// <summary>
/// 相机旋转
/// </summary>
public void Rotate()
{
    //接收输入
    float rotateX = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
    float rotateY = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;

    // 绕自身旋转
    if (Input.GetMouseButton(0))
    {
        Vector3 eulerAngles = transform.eulerAngles;

        eulerAngles.y += rotateX;
        eulerAngles.x -= rotateY;

        transform.eulerAngles = eulerAngles;
    }


    // 绕喷头旋转
    if (Input.GetMouseButton(1))
    {
        float distance = Vector3.Distance(target.transform.position, transform.position);

        transform.position += transform.right * -rotateX + transform.up * -rotateY;

        transform.LookAt(target.transform);

        transform.position = target.transform.position + -transform.forward * distance;
    }
}

五、优化功能

1、调整速率

        上述方法中,移动的速率是不变的,这样可能会导致当摄像机离物体很远时会显得移动很慢,离物体很近时会显得太快,可以写一个函数,根据距离目标物体的远近自动调整速率或许可以获得更好的手感。 

2、切换目标物体

        上述方法中没有提到目标物体的切换与选择,可以添加一个功能,通过鼠标点击选中游戏中的对象作为目标物体围绕旋转,切换目标只需选择其他对象即可。

3、设置常用摄像机观察点

        在实际项目中,可设置几个常用的摄像机机位,用户可通过快捷键快速切换到该位置观察。例如在3D打印机的仿真中,可以在打印机窗口前设置一个观察点来模拟实际中用户观察3D打印机的视角。

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

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

相关文章

【数据结构】布隆过滤器原理详解及其代码实现

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

差生文具多之(二): perf

栈回溯和符号解析是使用 perf 的两大阻力&#xff0c;本文以应用程序 fio 的观测为例子&#xff0c;提供一些处理它们的经验法则&#xff0c;希望帮助大家无痛使用 perf。 前言 系统级性能优化通常包括两个阶段&#xff1a;性能剖析和代码优化&#xff1a; 性能剖析的目标是寻…

【期末考试】计算机网络、网络及其计算 考试重点

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 计算机网络及其计算 期末考点 &#x1f680;数…

Netty Review - 深入理解Netty: ChannelHandler的生命周期与事件处理机制

文章目录 概述CodeLifeCycleInBoundHandler 验证 概述 Netty的ChannelHandler是处理网络事件&#xff08;如数据读取、数据写入、连接建立、连接关闭等&#xff09;的核心组件。 在Netty中&#xff0c;ChannelHandler的生命周期与Channel的状态紧密相关&#xff0c;主要涉及到…

从mice到missForest:常用数据插值方法优缺点

一、引言 数据插值方法在数据处理和分析中扮演着至关重要的角色。它们可以帮助我们处理缺失数据&#xff0c;使得数据分析更加准确和可靠。数据插值方法被广泛应用于金融、医疗、社会科学等领域&#xff0c;以及工程和环境监测等实际应用中。 在本文中&#xff0c;我们将探讨三…

制作小米导航实验

一、实验题目 制作一个小米"手机"的标签&#xff0c;当鼠标移动到"手机"上后&#xff0c;显示右边的白色菜单&#xff0c;当鼠标离开"手机"时&#xff0c;菜单消失。 二、实验代码 <!DOCTYPE html> <html lang"en"> <…

高级人工智能之群体智能:蚁群算法

群体智能 鸟群&#xff1a; 鱼群&#xff1a; 1.基本介绍 蚁群算法&#xff08;Ant Colony Optimization, ACO&#xff09;是一种模拟自然界蚂蚁觅食行为的优化算法。它通常用于解决路径优化问题&#xff0c;如旅行商问题&#xff08;TSP&#xff09;。 蚁群算法的基本步骤…

python分割字符串 split函数

split函数用于字符串的分割&#xff0c;可以完成基于特定字符将字符串分割成若干部分并存于列表中 url "http://localhost:5000/static/images/DICOMDIR 5.26-6.1/10283674_GOUT_5_0_2.png" # 获取最后一个_的前一个数字 parts url.split(/) print(parts)在这里讲…

Kali Linux—借助 SET+MSF 进行网络钓鱼、生成木马、获主机shell、权限提升、远程监控、钓鱼邮件等完整渗透测试(二)

远控木马 SET 同时集成了木马生成工具&#xff0c;可以生成木马并调用MSF框架对远程主机进行控制。直接使用MSF生成木马并控制主机的可参考之前另一篇博文&#xff1a;渗透测试-Kali入侵Win7主机。 控制主机 1、运行 SET&#xff0c;选择创建攻击载荷和监听器&#xff1a; 2…

Tomcat日志乱码了怎么处理?

【前言】 tomacat日志有三个地方&#xff0c;分别是Output(控制台)、Tomcat Localhost Log(tomcat本地日志)、Tomcat Catalina Log。 启动日志和大部分报错日志、普通日志都在output打印;有些错误日志&#xff0c;在Tomcat Localhost Log。 三个日志显示区&#xff0c;都可能…

ETN21与CJ2M-CPU33通讯

实验设备:CJ2M-CPU33,CJ1W-EIP21,交叉网线 实验目的:手动建立数据链接表,建立TAG通讯。 实验步骤: IP地址设置:①usb线连上电脑,打开I/O表,将ETN21模块的ip地址与CJ2M-CPU33设置为同一个网段不同节点,节点号跟硬件上的node number一样,下载重启模块,如下: 配置n…

百分百能遇到的接口自动化测试面试题,看完的现在已经在办理入职了...

1. 什么是接口自动化测试&#xff1f; 答&#xff1a;接口自动化测试是指使用自动化工具对接口进行测试&#xff0c;验证接口的正确性、稳定性和性能等方面的指标。 2. 为什么要进行接口自动化测试&#xff1f; 答&#xff1a;接口自动化测试可以提高测试效率&#xff0c;减…

Redis源码精读:准备工作

文章目录 前言拉取源码项目结构源码阅读技巧最后 前言 我是醉墨居士&#xff0c;未来的一段时间里面我准备写一些关于Redis源码的文章&#xff0c;来帮助大家深入浅出Redis&#xff0c;希望大家多多支持&#x1fae0; 拉取源码 git clone https://github.com/redis/redis项目…

Spring5底层原理之BeanFactory与ApplicationContext

目录 BeanFactory与ApplicationContext BeanFactory ApplicationContext 容器实现 BeanFactory实现 ApplicationContext实现 ClassPathXmlApplicationContext的实现 AnnotationConfigApplicationContext的实现 AnnotationConfigServletWebServerApplicationContext的实…

驱动开发-1

一、驱动课程大纲 内核模块字符设备驱动中断 二、ARM裸机代码和驱动有什么区别&#xff1f; 1、共同点&#xff1a; 都能够操作硬件 2、不同点&#xff1a; 1&#xff09;裸机就是用C语言给对应的寄存器里面写值&#xff0c;驱动是按照一定的套路往寄存器里面写值 2&#xff09…

为什么有的开关电源需要加自举电容?

一、什么是自举电路&#xff1f; 1.1 自举的概念 首先&#xff0c;自举电路也叫升压电路&#xff0c;是利用自举升压二极管&#xff0c;自举升压电容等电子元件&#xff0c;使电容放电电压和电源电压叠加&#xff0c;从而使电压升高。有的电路升高的电压能达到数倍电源电压。…

阶段十-物业项目

可能遇到的错误&#xff1a; 解决jdk17javax.xml.bind.DatatypeConverter错误 <!--解决jdk17javax.xml.bind.DatatypeConverter错误--><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>…

git 使用方法自用(勿进)本地开发分支推上线上开发分支

一、//查看状态 1.git status 二、//查看改了哪个文件夹 1.git diff 2.//会出现改了哪个文件夹src/components/partials/Slider.js 三、//查看改了的文件夹里面具体改了啥内容 1.git diff src/components/partials/Slider.js 四、提交所有 1. git add . 五、写备注…

连锁便利店管理系统有什么用

连锁便利店管理系统对于连锁便利店的运营和管理非常有用。以下是一些常见的用途&#xff1a; 1. 库存管理&#xff1a;连锁便利店通常需要管理多个门店的库存&#xff0c;管理系统可以帮助实时掌握各个门店的库存情况&#xff0c;包括商品数量、进货记录、库存调拨等。这样可以…

【Linux系统基础】(2)在Linux上部署MySQL、RabbitMQ、ElasticSearch、Zookeeper、Kafka、NoSQL等各类软件

实战章节&#xff1a;在Linux上部署各类软件 前言 为什么学习各类软件在Linux上的部署 在前面&#xff0c;我们学习了许多的Linux命令和高级技巧&#xff0c;这些知识点比较零散&#xff0c;同学们跟随着课程的内容进行练习虽然可以基础掌握这些命令和技巧的使用&#xff0c;…