第十三章 移动和旋转(上)

news2024/11/26 23:34:05

移动和旋转是游戏对象最频繁地操作。我们上个章节简单介绍了Cube的移动和旋转。移动是修改transform的position属性,旋转是修改transform的eulerAngles(欧拉角)属性,两者属性值均可以使用Vector3向量来实现。需要大家注意的是,transform.forward和Vector3.forward的区别(参考坐标系是不一样的)。接下来,我们使用transform的Translate方法来进行移动。首先,我在重新创建一个“SampleScene4”场景。我们在该创建中同样创建一个“Cube”游戏对象和“CubeTranslate.cs”脚本文件,并附加两者在一起。

 

以下是脚本代码内容

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CubeTranslate : MonoBehaviour
{

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            transform.Translate(new Vector3(0, 0, 1));
        }

        // 向左旋转45度
        if (Input.GetKeyDown(KeyCode.Q))
        {
            transform.eulerAngles += new Vector3(0, -45, 0);
        }

        // 向右旋转45度
        if (Input.GetKeyDown(KeyCode.E))
        {
            transform.eulerAngles += new Vector3(0, 45, 0);
        }
    }
}

我们实现的功能还是之前的一样,按下“W”键向前移动。为了方便观察Cube的移动,我们需要将相机放置Cube的正上方“俯视”Cube。摄像机修改参数如下:

为什么要修改成这些数值,我们上一个章节介绍过了,修改后效果如下:

然后,我们Play工程,查看运行结果。

测试结果就是相对于本地坐标系的移动,也就是游戏对象的正前方移动。

 

 

很明显,默认情况下,Translate(Vector3(0, 0, 1))方法与transform.forward是等效的,也就是相对于本地坐标系而言。那么,如何改成世界坐标系的移动呢,不受游戏物体旋转影响呢?

其实该方法还有第二个参数,也就是Space.Self本地坐标系和Space.World世界坐标系。

显然,如果我们不填写第二个参数的话,默认就是Space.Self本地坐标系,我们改一下

        if (Input.GetKeyDown(KeyCode.W))
        {
            //transform.Translate(new Vector3(0, 0, 1));
            transform.Translate(new Vector3(0, 0, 1), Space.World);
        }

接下来,我们在重新测试,发现现在的移动就是相对于世界坐标系的啦,截图就不添加了。总结:游戏对象的移动可以通过在transform.translate()方法实现,也可以直接修改transform.position属性。这里需要区分的是世界坐标系还是自身坐标系。如下:

世界坐标系z轴前进:transform.position += vector3.forward
自身坐标系z轴前进:transform.position += transform.forward
世界坐标系z轴前进:transform.translate(vector3.forward, space.world)
自身坐标系z轴前进:transform.translate(vector3.forward, space.self)

如果是space.world的话,还可以使用transform.forward;但是最好不要将transform.forward应用到space.self上。因此还是建议translate方法+ vector3向量搭配使用进行移动。

接下来,我们来说一说旋转,这个是比较复杂的。我们之前的旋转是通过修改transform的eulerAngles(欧拉角)实现的。但是,在我们的印象中,游戏对象的旋转应该是rotation属性,尤其是在Inspector检视面板中,我们也能够看到这个名字的属性。例如下面的截图就是我们对摄像机对象做的旋转设置,也就是在X轴方向上面旋转90度。请注意,此时的参考坐标系是父对象,如果没有父对象就是世界坐标系。

因此,显而易见这是欧拉角数值。但是在Unity API中,这个rotation属性是一个四元数(Quaternion)。关于四元数的概念以及它相对于欧拉角的优势,我们这里不在介绍,它唯一的缺点就是不如欧拉角简单直观。因此,我们往往借助欧拉角转换成四元数再控制游戏对象旋转。接下来,我们修改一下代码,如下所示:

        // 向左旋转45度
        if (Input.GetKeyDown(KeyCode.Q))
        {
            //transform.eulerAngles += new Vector3(0, -45, 0);
            transform.rotation = Quaternion.Euler(new Vector3(0, -45, 0));
        }

        // 向右旋转45度
        if (Input.GetKeyDown(KeyCode.E))
        {
            //transform.eulerAngles += new Vector3(0, 45, 0);
            transform.rotation = Quaternion.Euler(new Vector3(0, 45, 0));
        }

上面的代码实现了“Q”和“E”键分别向左右两方向按照45度单位的旋转。但是当我们运行当前工程的时候,发现它并不能连续旋转,而只是旋转一下就停止了。我们很容易想到使用“+=”操作符,但是很遗憾的是,transform.rotation并不支持这样的运算。因此,我们只能换一种方式来解决这个问题,我们可以使用一个类全局变量来存储游戏对象的旋转数据。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CubeTranslate : MonoBehaviour
{
    // Y轴旋转角度值
    private float y = 0.0f;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            //transform.Translate(new Vector3(0, 0, 1));
            transform.Translate(new Vector3(0, 0, 1), Space.World);
        }

        // 向左旋转45度
        if (Input.GetKeyDown(KeyCode.Q))
        {
            //transform.eulerAngles += new Vector3(0, -45, 0);
            //transform.rotation = Quaternion.Euler(new Vector3(0, -45, 0));

            y -= 45;
            transform.rotation = Quaternion.Euler(new Vector3(0, y, 0));
        }

        // 向右旋转45度
        if (Input.GetKeyDown(KeyCode.E))
        {
            //transform.eulerAngles += new Vector3(0, 45, 0);
            //transform.rotation = Quaternion.Euler(new Vector3(0, 45, 0));

            y += 45;
            transform.rotation = Quaternion.Euler(new Vector3(0, y, 0));
        }
    }
}

重新运行后,我们发现Cube可以进行“连续性”的旋转了,如下Gif图所示:

我们发现使用transform.rotation进行旋转的话,还是比较费劲的。但是,有一点大家要明白,这里的旋转和上面的移动是相同的道理,同样存在世界坐标系和局部坐标系的前提条件。这个当然取决于我们使用Vector3类还是使用Transform类。接下来我们就使用transform提供的Rotate方法来进行旋转,它的参数是欧拉角,比较容易让人直观的理解,而且也提供了坐标系参数的指定(Space.Self或者Space.World,当然默认还是Space.Self)。

        // 向左旋转45度
        if (Input.GetKeyDown(KeyCode.Q))
        {
            //transform.eulerAngles += new Vector3(0, -45, 0);
            //transform.rotation = Quaternion.Euler(new Vector3(0, -45, 0));

            //y -= 45;
            //transform.rotation = Quaternion.Euler(new Vector3(0, y, 0));

            transform.Rotate(new Vector3(0, -45, 0));
        }

        // 向右旋转45度
        if (Input.GetKeyDown(KeyCode.E))
        {
            //transform.eulerAngles += new Vector3(0, 45, 0);
            //transform.rotation = Quaternion.Euler(new Vector3(0, 45, 0));

            //y += 45;
            //transform.rotation = Quaternion.Euler(new Vector3(0, y, 0));

            transform.Rotate(new Vector3(0, 45, 0));
        }

我们发现transform.Rotate使用起来就比较简单了。当我们Play当前工程之后,我们就发现效果和之前是一样的(可以连续的旋转,而不是只旋转一下)。但是,请大家明白的是,之前的旋转是相对于世界坐标系的Y轴,现在的旋转是相对于游戏对象本地坐标系的Y轴,之所以效果一样是,是因为两者坐标系重合了而已。

从Scene视图中我们可以看到,Cube对象坐标系和世界坐标系是一致的。接下来,我们调整Cube,让两个坐标系不一致,我们让Cube沿着Z轴顺时针旋转90度,也就是修改Cube的Inspector视图中的Rotation中的Z值为“-90”,效果如下所示:

 

为什么“-90”度是顺时针旋转呢?这个取决于我们的观察方向。在默认的左手坐标系下,负值就是顺时针旋转;当然右手坐标系下,正值是顺时针旋转。此时世界坐标系的Y轴仍然是向上的方向,而游戏对象Cube的Y轴是向右的。那么,此时如果我们让Cube在Y轴上按照两个不同坐标系进行旋转的话,那肯定是不一样了。为了能够看到这个效果,我们修改摄像机的位置参数,从侧面观察cube的旋转。

然后(保持相机选中状态)我们点击菜单栏“GameObject”->“Align View to Selected”

此时,我们的Scene视角与Game视角一致。如果是本地坐标系的话,Y轴是向里的。因此围绕它旋转的话,Cube应该是类似“车轮滚动”的方式进行旋转。

// 向左旋转45度
transform.Rotate(new Vector3(0, -45, 0));

// 向右旋转45度
transform.Rotate(new Vector3(0, 45, 0));

然后我们Play工程,查看运行结果,Gif图如下

如果是世界坐标系的话,Y轴是向上的。如果它应该是“陀螺”旋转的方式。

//transform.Rotate(new Vector3(0, -45, 0));
transform.Rotate(new Vector3(0, -45, 0), Space.World);

//transform.Rotate(new Vector3(0, 45, 0));
transform.Rotate(new Vector3(0, 45, 0),Space.World);

我们直接Play工程,查看Gif效果图吧

 

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

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

相关文章

C#窗体将DGV控件中数据导入导出Excel表

目录 界面图: 效果视频: 一:将DGV数据导出到Excel表中 二:将Excel表数据导入到DGV中 三:界面全部代码 界面图: 效果视频: DGV数据导入导出Excel 一:将DGV数据导出到Excel表中 p…

私人影院 - 本地搭建Emby媒体库服务器并在外远程访问 「无需公网IP」

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中,观看视频绝对是主力应用场景之一&…

用ChatGPT问DotNet的相关问题,发现DotNet工程师的前景还不错

本人最近费了九牛二虎之力注册了一个ChatGPT账号,现在就给大家分享一下,问一下关于.NET的问题,看看ChatGPT的AI功能具体如何? 一、C#跟其它语言比较的优势 回答: C#是一门编程语言,它是为 Microsoft 的 …

【Linux】动态库与静态库

目录 一、前言 二、静态库与动态库 三、生成静态库 1、生成原理 2、完整过程 3、总结 四、生成动态库 1、环境变量 2、建立软链接 3、配置文件 五、动态库的加载 1、动态库加载的过程 2、动态库地址的理解 3、补充内容 一、前言 关于动态库与静态库的一小部分前置…

TCP的粘包和拆包

UDP有数据边界,TCP是没有数据边界,是流协议。如何拆包,就要靠应用层来处理。 四层网络模型,消息在进入每一层时都会多加一个报头。mac头部记录的是硬件的唯一地址,IP头记录的是从哪来和到哪去,传输层头记录…

GPT 护理机器人 - 让护士的工作变简单

引子    书接上文《GPT接入企微应用 - 让工作快乐起来》,我把GPT接入了企微应用,不少同事都开始尝试起来了。有的浅尝辄止,有的刨根问底,五花八门,无所不有。这里摘抄几份: “帮我写一份表白信&#xff…

Github 的使用

3. Github 在版本控制系统中,大约90%的操作都是在本地仓库中进行的:暂存,提交,查看状态或者历史记录等等。除此之外,如果仅仅只有你一个人在这个项目里工作,你永远没有机会需要设置一个远程仓库。只有当你…

嗯,这个树怎么和往常不一样?

文章目录 前言一、二叉树的链式存储二、二叉树链式结构的实现二叉树的结构设计手动构建二叉树二叉树的前序遍历二叉树的中序遍历二叉树的后序遍历二叉树的层序遍历计算二叉树大小计算叶子节点个数计算二叉树高度计算第K层的节点个数查找某个值对应的节点二叉树的销毁 三、完整代…

全球首个存量手机直连卫星天地语音通话,打通了!

4月25日,美国卫星通信初创公司——AST SpaceMobile,宣布打通了全球首个天基蜂窝语音通话。 对于卫星通信乃至整个通信行业来说,这是一个重大新闻,非常值得关注。 去年,我们还只是实现了手机和卫星之间的双向短消息通信…

Page管理机制

Page页分类 Buffer Pool 的底层采用链表数据结构管理Page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率 Page根据状态可以分为三种类型: - free page : 空闲page,未被使用 - …

1.3 HBase 基本架构

架构角色: 1)Master 实现类为 HMaster,负责监控集群中所有的 RegionServer 实例。主要作用如下: (1)管理元数据表格 hbase:meta,接收用户对表格创建修改删除的命令并执行 (2&#x…

【C++ Primer(第5版) 课后习题题目及答案 第一章】

C Primer5th 课后习题答案 第一章 1.1:查阅你使用的编译器的文档,确定它所使用的文件命名约定。编译并运行main程序。1.2:改写程序,让它返回-1。返回值-1通常被当作程序错误的标识。重新编译并运行你的程序,观察你的系…

C#弹出消息对话框,增加输入框,接受输入信息

效果图: 代码: using Microsoft.VisualBasic;string intext Interaction.InputBox("请输入密码","输入密码","",Screen.PrimaryScreen.Bounds.Width/4,Screen.PrimaryScreen.Bounds.Height/4);MessageBox.Show("in…

多商户商城系统开发功能有哪些?

多商户商城系统开发功能有哪些? 1、商品管理。商品管理是多商户商城系统的必备功能。商家需要能够轻松地添加、修改和删除商品。这个功能还应该包括商品图片、价格、描述和库存等信息的管理。消费者也需要能够通过小程序浏览商品并下订单。 2、订单管…

图形编辑器:历史记录设计

大家好,我是前端西瓜哥。今天讲一下图形编辑器如何实现历史记录,做到撤销重做。 其实就是版本号的更替。每个版本保存一个状态。 数据结构 要记录图形编辑器的历史记录,支持撤销重做功能,需要两个栈:撤销&#xff0…

【计算机网络】学习笔记:第三章 数据链路层【王道考研】持续更新中....

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…

《编码——隐匿在计算机软硬件背后的语言》精炼——第14章(边沿触发器,计数器)

学习不是一次性的投资&#xff0c;而是一份长期稳定的收益。 文章目录 8位锁存器边沿触发器计数器改进的边沿触发器 8位锁存器 上篇文章讲到了1位存储器的组成&#xff0c;将8个1位存储器的时钟端连在一起就形成了一个8位锁存器&#xff0c;如下所示&#xff1a; 这个锁存器一…

vbscript+asp编写接口

1、前言 因为目前工作在对内网老系统用reactjava微服务进行升级改造&#xff0c;因为一些老的业务逻辑都是用vbscript编写的&#xff0c;很复杂&#xff0c;因此持久层和业务层代码不能动&#xff0c;以asp接口的形式给到数据。java接口调用asp接口&#xff0c;然后前端再调用j…

Elasticsearch --- DSL、RestClient查询文档、搜索结果处理

一、DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1、DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0c…

他工作10年,老板却让他走人

大家好&#xff0c;我是五月&#xff0c;一个编程街溜子。 二狗被裁了&#xff0c;他在公司待了快十年&#xff0c;他想留下来&#xff0c;老板却让他走。 我和他一样困惑。 他985毕业&#xff0c;工作中有从0开始一个项目直到日活过千万&#xff0c;也有过参与顶级产品核心…