【Godot4.2】基础知识 - Godot中的2D向量

概述

在Godot中,乃至一切游戏编程中,你应该都躲不开向量。这是每一个初学者都应该知道和掌握的内容,否则你将很难理解和实现某些其实原理非常简单的东西。

估计很多刚入坑Godot的小伙伴和我一样,不一定是计算机专业或编程相关专业从业人员。英语、数学、算法、设计模式以及Shader方面都是拦路虎。尤其数学,当初稀里糊涂,现在也早还给老师了。我本人就是个数学学渣,所以也是一路学引擎,一路补课数学、英语和编程知识。

本篇尽量由浅入深,让新手们不再像我当初初学时那样迷茫。


说明:本文写于2022年11月26日,基于的Godot版本是3.5,与目前Godot4.2在某些语法和API上可能会有差异,后续会基于4.2进行改写和拓展。


概念

二维向量,是指有两个分量的向量。在Godot的内置脚本语言GDScript中,用Vector2D类型表示二维向量。

万能的二维向量

二维向量可以表示屏幕二维坐标系上的点的位置。可以表示方向,还可以存储矩形的尺寸。

也可以用一堆二维向量表示平面上的一条折线路径。

Node2D的朝向

在这里插入图片描述

每一个2D物体其实有两个方向

  • 第一个是它自身的朝向,也就是由它的rotation_degree属性所定义的方向
  • 第二个是在移动过程中,从自己的位置到目标位置的方向,也就是移动方向

不能只知道移动的方向,却忽略物体自身的朝向。在实际的设计中两者配合,才能做出更好的效果。

在Godot中确实没有“朝向”这个概念,只有rotation_degree属性,但是却的的确确向我们展示了“朝向”的存在。look_at()方法和经典的“Sprite随鼠标定位旋转”示例就表明了这一点。

Sprite随鼠标定位旋转

创建如下的场景,将icon.png拖进来,放到视口矩形范围的中间位置。
在这里插入图片描述

Icon节点添加如下代码:

extends Spritefunc _process(delta):look_at(get_global_mouse_position())

运行后就可以看到一个始终朝向鼠标位置的效果。而这里可以看到,始终是右边朝向鼠标位置。

Sprite节点始终“看”向鼠标位置

如果不够直观,我们可以给Sprite节点添加一个箭头,与其“朝向”保持一致。
image.png
你就可以看到更清晰的效果。
用红色箭头指示Sprite节点的朝向
如果看到上面的示例,你能够想到经典游戏《祖玛》,说明你很聪明。

经典游戏《祖玛》中随鼠标定位旋转的效果

这里我们不做《祖玛》的示例,换为两张坦克素材图片,组成一个坦克。

在这里插入图片描述

我们将之前的代码放到坦克的“炮身”也就是cannon节点上。

你立马就得到了一个可以随鼠标位置瞄准的坦克。

坦克随鼠标定位瞄准

向量与位置

屏幕坐标系

在二维平面上表示一个点,最简单的方式就是定义一个平面坐标系,然后用一对(x,y)坐标来表示。Godot采用的坐标系与大多数计算机可视化编程所采用的是一致的,也就是以左上角为原点(0,0),X轴正方向向右,Y轴正方向向下的“屏幕坐标系”。
Godot的屏幕坐标系
在Godot的2D工作区视口中,你可以看到Y轴用绿色,X轴用紫色,左上角原点也会有特殊的标记。

实际视口中的XY轴、原点和视口可视区域矩形

视口矩形

游戏窗口的尺寸是有限的,但是游戏地图或者游戏的世界可以比游戏窗口大得多。我们运行场景或项目时,窗口首先展示的屏幕范围,我们可以将其称为“第一屏”,这和网页设计中的“第一屏”概念是类似的。

“第一屏”是一个矩形区域,你可以用get_tree().root.get_visible_rect()获得包含其信息的Rect2数据。它与我们在项目设置中设置的窗口大小可能一致,也可能不一致,因为游戏窗口可以在运行过程中改变大小,并且还受缩放模式等设置的影响。

项目设置中的窗口大小

游戏中,我们通常不会始终待在“第一屏”,除非你的设计就是每个关卡地图就是一屏的大小。通过摄像机我们可以看到关卡地图的其他地方。

位置

好了,讲了必须讲的前置知识后,回到正题——“向量与位置”。在平面坐标系中,表示一个点的位置,就是用它在X轴和Y轴上的投影处的值组成的坐标。

点的坐标表示
那这又怎么跟向量扯上关系了呢?

向量其实可以理解为“相对移动”或“相对位置”,这种“相对性”其实很让人迷糊。但是举个例子你就明白了:

已知我们在地球上,已经通过地磁场确定了东西南北(废话),假设你的家乡在某个小镇A,但是你大学毕业后到了某个省的省会城市B工作,那么描述你的家乡小镇A和你工作的城市B之间的相对位置关系,你会怎么描述呢?

image.png
你会说你的家乡小镇A在你工作所在城市B的西北方向1200公里(数据胡诌),而你工作的城市B在你老家小镇A的东南方向1200公里公里。

你会发现两个地点的相对位置,可以用一个“方向+距离”的形式轻松的描述清楚。而“方向+距离”就是向量

在相对中引入绝对

世上本没有东西南北,人类用某种科学规律和约定俗成规定了东西南北的方向,地球也本没有经纬,同样是人类用某种科学规律和科学家之间的约定俗成创建了一个可以定位地球某个点的方法。

同样的在二维平面上,本没有坐标系,为了方便描述位置,才引入了平面坐标系。类比《圣经·创世纪》中:世上本没有光,“神说“要有光”,就有了光”,科学家们何尝不是分开混沌,理清世界的“神”。

在屏幕坐标系中,左上角是坐标系原点(0,0),那么屏幕上的任何一点都可以被理解为“基于坐标系原点(0,0)在某个方向上偏移多少距离”,或者“相对坐标系原点的某个方向和距离的点”。

这也就与我们上面所说的两个地点的相对位置描述一致了,只不过其中的一个点固定成了坐标系原点(0,0)

那么以下图为例,点(120,80)的向量含义就是“相对坐标系原点(0,0)的XX方向上移动YY距离的点”。而这就是屏幕坐标点与向量关系的由来。

平面坐标点的向量表示

那么这里的方向和距离到底是什么呢,如何计算?

从平面坐标点的向量表示到直角三角形

我们先说距离,平面坐标系上的任何一个点,它在X轴、Y轴投影与向量之间围成一个直角三角形(如上图右)。那么根据勾股定律,向量的长度 d = x 2 + y 2 d=\sqrt{x^2 + y^2} d=x2+y2

在GDScript中我们可以直接使用Vector2length()方法获取向量的长度,如果是屏幕点的位置的话,它所求的就是从屏幕坐标系原点到这个点的距离。

get_global_mouse_position().length()

除此之外,你可以用Vector2distance_to()求屏幕上任意两个点之间的距离

Vector2(100,100).distance_to(Vector2(200,200))

那么方向呢?方向我们单位向量来表示,单位向量是指长度为1的向量,计算的话就是**单位向量 = **向量/向量长度。在Godot中你同样可以省下这种计算,只用一个方法搞定,Vector2normalized()就是求一个向量的单位向量。

Vector2(100,100).normalized()

单位向量 = 长度为1的向量

单位向量的好处是,它的长度是1,1乘以任何数就是这个数本身,同时它又保存了向量的“方向”。那么我们就可以用单位向量乘以任何标量来“缩放向量”,同时也可以用**单位向量×长度(或叫距离)**来表示一个向量。也就回到了“在某个方向上偏移多少距离”的含义。

进一步的考虑在一个二维平面上,所有可能的方向都包含在一个中心点为坐标原点,半径为1的圆里。想想游戏手柄的摇杆和手机游戏的虚拟摇杆,你就应该明白,为什么它们可以控制你的游戏角色或视角移动了吧。

image.png
当然手柄的摇杆和手机游戏的虚拟摇杆还进一步检测了你拖动摇杆的力度,所以它不再是只包含圆周上的那些点,而是包含了圆周和圆内所有的点。

向量与移动

有了上面的基础,那么就应该研究物体在二维平面上的移动了。从A点到B点的移动可以理解为单纯的距离缩短。也可以描述为“A不停的向B移动,直到A和B重合(距离=0)”。
基于向量移动的基础示意图

那么体现在代码上就是需要知道A到B的方向和距离,然后定义单位时间内移动的速度,然后就可以移动了。

A到B的方向direction可以用A.direction_to(B)求得,A到B的距离distance可以用A.distance_to(B)
定义单位时间的移动距离,也就是速度speed,那么速度向量velocity = direction * speed,也就是方向*移动距离

不要懵了,directiondistancevelocity是初学者学习基础移动必须学会的三个单词:

  • direction:方向,申明变量时可以简写为dir
  • distance:距离,申明变量时可以简写为disd
  • velocity:(沿某一方向的)速度,申明变量时可以简写为vecv

无论是申明变量还是使用Vector2的方法你都会遇到这三个单词。

整个原理就是先判断起始点到目标点的距离是否大于0,然后将A的位置加上速度向量velocity,移动一段距离,然后循环,直到距离=0。

具体可以参阅相关的示例。

这是用纯向量方法移动物体的形式,Godot中移动物体和实现碰撞需要用物理体那套,但是基础的基于向量的移动是必学的基础,它在某些时候会有用处。

局部坐标系

就像人类曾经经历了地心说和日心说,再到现在的宏大宇宙观,中心与原点有相似性,它也可以因为不同的认识和参照定义而发生变化。

你可以将二维平面的任意一点作为原点构造一个平面坐标系。但是你或者别人完全可以选择除了这一个点之外的任何一个点的位置重新建立一个坐标系。同一个点会因为你设立的坐标系不同而有不同的坐标值表示。

同时在二维平面的某个局部,你又可以创建一个局部坐标系。局部坐标系的好处是,它可以只描述局部范围内的内容,而忽略其他的东西。

相对的你可以将它的上一级坐标系称为“全局坐标系”,当然这很违心,因为“全局坐标系”本质上也是一个“局部坐标系”,因为它的外面可以有更大的坐标系。

就像地球的经纬度系统就可以看做是地球的全局坐标系,但是在太阳系乃至更大的银河系和宇宙来说,坐标系还可以随着讨论范围逐渐扩大。

但是在Godot里,一般情况下你就可以认为屏幕坐标系就是“最大”的坐标系,是“全局坐标系”,一个场景的根节点其“局部坐标系”默认与“全局坐标系”重合,除非你不移动它。而每一层的子节点,都有一个自己的“局部坐标系”。

在Godot的API中也体现了全局位置、缩放、旋转和局部位置、缩放、旋转的概念。
全局坐标系与局部坐标系

极坐标系

大致说完平面坐标系和二维向量,再加入一点极坐标和三角函数的内容。在一个平面上表示一个点的位置,不止有平面坐标系法和向量法,还有极坐标系法。

极坐标系(polar coordinates)是指在平面内由极点、极轴和极径组成的坐标系。在平面上取定一点O,称为极点。从O出发引一条射线Ox,称为极轴。再取定一个单位长度,通常规定角度取逆时针方向为正。这样,平面上任一点P的位置就可以用线段OP的长度ρ以及从Ox到OP的角度θ来确定,有序数对(ρ,θ)就称为P点的极坐标,记为P(ρ,θ);ρ称为P点的极径,θ称为P点的极角。
点的极坐标表示
它的本质是规定一个正方向,表示0度,然后平面内的某个点就可以表示为方向为与这个正方向的夹角,距离为原点到这个点的距离,两个参数确定一个点。依然是方向和距离,但是用了角度和长度。
在Godot中可以理解为,2D节点的朝向,也就是rotation_degree属性,遵循了这种坐标系,但是依然是反的,它的角度顺时针方向取正,逆时针方向取负。并且rotation_degree属性的单位是度,而不是弧度。

向量的夹角

在GDScript中,你可以用Vector2angle()求某个向量与X轴正方向的夹角。

print(Vector2(100,100).angle()) # 0.785398,单位:弧度

因此你完全可以将一个二维坐标系点的位置,通过封装(到原点的距离,与X轴正方向夹角(弧度))来获得其极坐标表示。

你也可以用A.angle_to(B)求两个向量之间的夹角。
angle()和angle_to()
A.angle_to_point(B)由B到A的向量与X轴正方向的夹角。

A.angle_to_point(B)
B.angle_to_point(A)求由A到B的向量与X轴正方向的夹角。

B.angle_to_point(A)
在三者中,angle_to_point()应该是最让人费解的,但是平常也用不到,初学时不必深究。

向量的旋转

说完夹角再说旋转。向量除了加减乘除运算之外,还可以旋转。通过将一个向量旋转一定角度,可以获得一个新的向量。

向量的旋转有时候也很有用,比如在用Line2D节点绘制多边形或圆时,就会用到。

Line2D和PoolVector2Array

Line2D节点用于绘制路径,它的points属性存储构成路径的所有顶点,是PoolVector2Array类型。
PoolVector2Array你可以简单理解为只存储Vector2类型的特殊数组。
image.png
你可以直接在编辑器中手动绘制路径的顶点。注意它记录的是全局坐标,也就是基于屏幕左上角的位置,与自身的层级和局部坐标无关。
在编辑器中手动绘制Line2D的路径顶点
绘制后你可以在“检视器”面板展开points属性,看到其中记录的顶点坐标。

points中记录的顶点坐标
同样的你可以用代码生成这些顶点坐标,而通过旋转向量的方法,我们可以用Line2D绘制圆、圆弧等等。

绘制多边形和圆

策略很简单:

  • 指定细分数,或者多边形的边数,然后我们用360/边数获得单次要旋转的角度
  • 指定旋转半径,我们将X轴正方向的单位向量Vector2.RIGHT乘以旋转半径就获得了我们初始要旋转的向量
  • 然后我们将它旋转指定的角度,并将旋转得到的新的点的位置加入到一个PoolVector2Array中,通过多次旋转就得到了多个点
  • 最后我们赋值Line2Dpoints属性为这个PoolVector2Array,搞定!
extends Line2Dvar subdivision = 6 # 细分数
var r = 100 # 旋转半径
var center = Vector2(400,200) # 旋转中心func _ready():width = 2var pots:PoolVector2Array = []var uint = Vector2.RIGHT # 向右的单位向量var per_angle = (2 * PI) / subdivision # 单次旋转角度var basic_vec = uint * r  # 要旋转基础向量pots.append(basic_vec + center)for i in range(1,subdivision+1):pots.append(basic_vec.rotated(per_angle * i)  + center)pots.append(basic_vec + center) # 回到第一个点,闭合points = potspass

上面的代码运行后绘制的就是一个中心点在(400,200),中心点到每个顶点的距离是100像素的正六边形。
在这里插入图片描述

圆和正多边形的唯一区别就是细分数,只要细分数达到一定的数量,就会“以直求曲”,从折线变成近似曲线的效果,这也是很多计算机软件里绘制圆形的奥秘。
在这里插入图片描述

绘制圆弧和扇形

学会了画圆,那么圆弧和扇形也就没有什么困难了。

extends Line2Dvar subdivision = 5 # 细分数
var start_angle = deg2rad(45) # 起始角度
var end_angle = deg2rad(90) # 起始角度
var r = 100 # 旋转半径
var center = Vector2(400,200) # 选中中心func _ready():width = 2var pots:PoolVector2Array = []var uint = Vector2.RIGHT # 向右的单位向量var d_angle = end_angle - start_angle if end_angle - start_angle >=0 else start_angle - end_anglevar per_angle = d_angle / subdivision # 单次旋转角度var basic_vec = uint * r  # 要旋转基础向量print(basic_vec.rotated(start_angle))pots.append(center) # 添加中心点var start_point = basic_vec.rotated(start_angle) + center # 起始角度点pots.append(start_point) # 添加起始点for i in range(1,subdivision+1):pots.append(basic_vec.rotated(start_angle + per_angle * i)  + center)pots.append(center) # 回到中心点,闭合points = potspass

在这里插入图片描述

上面的代码如果首尾都不加中心点,就变成了圆弧。

螺旋线

上面举例的都是只旋转,但是旋转半径不变的情况,但你完全可以尝试一下按参数规律变化半径的螺旋线之类的。

三角函数

涉及平面坐标系、位置和角度,那么三角函数也是躲不过去的一个话题,这里我只简单的说一下,知道半径和角度,如何计算坐标。其实一张图就够了,你可以自己思考一下为什么。
image.png
关于向量其实想说的还很多,但是限于篇幅和经历,这次只说这么多,希望对新手有所帮助。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorch 实现多层神经网络MLP(Pytorch 05)

一 多层感知机 最简单的深度网络称为多层感知机。多层感知机由 多层神经元 组成,每一层与它的上一层相连,从中接收输入;同时每一层也与它的下一层相连,影响当前层的神经元。 softmax 实现了 如何处理数据,如何将 输出…

SpringAOP+自定义注解实现限制接口访问频率,利用滑动窗口思想Redis的ZSet(附带整个Demo)

目录 1.创建切面 2.创建自定义注解 3.自定义异常类 4.全局异常捕获 5.Controller层 demo的地址,自行获取《《—————————————————————————— Spring Boot整合Aop面向切面编程实现权限校验,SpringAop自定义注解自定义异常全局…

【微服务】Gateway服务网关

📝个人主页:五敷有你 🔥系列专栏:微服务 ⛺️稳中求进,晒太阳 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响…

Windows 设置多显示器显示

Windows 设置多显示器显示 1. Windows 7 设置 HDMI 输出2. Windows 11 设置多显示器显示References 1. Windows 7 设置 HDMI 输出 2. Windows 11 设置多显示器显示 ​​​ References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

Ubuntu Desktop 安装谷歌拼音输入法

Ubuntu Desktop 安装谷歌拼音输入法 1. Installation1.1. 汉语语言包​1.2. 谷歌拼音输入法1.3. 安装语言包1.4. 键盘输入方式系统1.5. 重启电脑1.6. 输入法配置 2. configuration2.1. Text Entry Settings… 3. ExecutionReferences 1. Installation 1.1. 汉语语言包 strong…

odoo扩展导出pdf功能

1. 说明: odoo原生导出功能扩展导出pdf文件功能, 如有额外需求请联系博主 2. 版本说明: odoo版本: odoo15 其他odoo版本未进行测试,如有需要自行测试 3. 地址: 该补丁代码放在github仓库, 地址: https://github.com/YSL-Alpaca/odoo_export_pdf 4. 改补丁依赖于第三方软件wkh…

网盘——数据库操作

关于网盘的数据库模块,主要有以下几个内容:定义数据库操作类、将数据库操作类定义成单例模式、数据库操作 数据库是在Qt里面,定义成操作类,专门用这个类产生对象,对数据库实现操作,那么我们在产生对象的时…

音视频领域首个,阿里云推出华为鸿蒙 HarmonyOS NEXT 版音视频 SDK

近日,阿里云在官网音视频终端 SDK 栏目发布适配 HarmonyOS NEXT 的操作文档和 SDK,官宣 MediaBox 音视频终端 SDK 全面适配 HarmonyOS NEXT。 此外,阿里云播放器 SDK 也在华为开发者联盟官网鸿蒙生态伙伴 SDK 专区同步上线,面向所…

Linux系统——硬件命令

目录 一.网卡带宽 1.查看网卡速率——ethtool 网卡名 2.查看mac地址——ethtool -P 网卡名 二、内存相关 1.显示系统中内存使用情况——free -h 2.显示内存模块的详细信息——dmidecode -t memory 三、CPU相关 1.查看CPU架构信息——lscpu 2.性能模式 四、其他硬件命…

Java微服务分布式分库分表ShardingSphere - ShardingSphere-JDBC

🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 往期热门专栏回顾 专栏…

个人博客系列-后端项目-系统角色配置(8)

系统角色配置需要设置的接口 用户可以绑定多个角色,角色对应有多个路由权限。用户绑定角色后,可以访问当前角色下的各个api路由和菜单路由。 用户注册时设置用户角色修改用户角色(同时对应用户可以访问的路由将会同步变更)添加修…

python写爬虫爬取京东商品信息

工具库 爬虫有两种方案: 第一种方式是使用request模拟请求,并使用bs4解析respond得到数据。第二种是使用selenium和无头浏览器,selenium自动化操作无头浏览器,由无头浏览器实现请求,对得到的数据进行解析。 第一种方…

[Java基础揉碎]单例模式

目录 什么是设计模式 什么是单例模式 饿汉式与懒汉式 饿汉式vs懒汉式 懒汉式存在线程安全问题 什么是设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模式就像是经典的棋谱&am…

数据分析和机器学习库Pandas的使用

Pandas 库是一个免费、开源的第三方 Python 库,是 Python 数据分析和机器学习的工具之一。Pandas 提供了两种数据结构,分别是 Series(一维数组结构)与 DataFrame(二维数组结构),极大地增强的了 …

STM32微控制器的中断优先级设置对系统性能有何影响?

STM32微控制器的中断优先级设置对系统性能有着显著的影响。正确配置中断优先级可以确保关键任务得到及时响应,提高系统的实时性和可靠性。相反,如果中断优先级设置不当,可能会导致系统响应延迟,甚至出现死锁等问题。本文将详细探讨…

边缘计算【智能+安全检测】系列教程-- Jeton Agx Orin 基础环境搭建

1 .前期准备 Jetson Agx Orin 比Jetson Agx Orin Xavier的算力要高,性能要好通常用来做自动驾驶的AI推理,具体外观如下图 1.刷机软件sdkmanager:下载链接 NVIDIA账号需要注册,正常一步一步往下走就行。在ubuntu18以上的系统安…

pycharm搭建新的解释器及删除处理

目录 1.创建虚拟环境 个人实际操作: 对于“继承全局站点包”: 2.创建一个新项目 3.删除操作 (1)删除解释器 (2)删除新建项目 1.创建虚拟环境 Pycharm官方文档说明网址: Configure a virt…

C语言 数组指针 指针数组

指针数组 什么是指针数组&#xff0c;他是一个数组&#xff0c;数组的元素是指针。但是指针也有多种数据类型&#xff0c;有数组指针、函数指针、整形指针、字符串指针。 现在我就使用函数指针来写代码&#xff0c;也就是函数指针数组的应用代码&#xff1a; #include <s…

Qt实现简易的多线程TCP服务器(支持多个客户端连接)附源码

目录 一.UI界面的设计 二.服务器的启动 三.实现自定义的TcpServer类 1.在widget中声明自定义TcpServer类的成员变量 2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化&#xff0c;m_widget我们用于后续的显示消息等&#xff0c;说白了就是主界面的更新显示等 …

自注意力机制的理解

一、自注意力要解决什么问题 循环神经网络由于信息传递的容量以及梯度消失问题&#xff0c;只能建立短距离依赖关系。为了建立长距离的依赖关系&#xff0c;可以增加网络的层数或者使用全连接网络。但是全连接网络无法处理变长的输入序列&#xff0c;另外&#xff0c;不同的输…