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

2024-03-23 17:36

本文主要是介绍【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
关于向量其实想说的还很多,但是限于篇幅和经历,这次只说这么多,希望对新手有所帮助。

这篇关于【Godot4.2】基础知识 - Godot中的2D向量的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/839052

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

计组基础知识

操作系统的特征 并发共享虚拟异步 操作系统的功能 1、资源分配,资源回收硬件资源 CPU、内存、硬盘、I/O设备。2、为应⽤程序提供服务操作系统将硬件资源的操作封装起来,提供相对统⼀的接⼝(系统调⽤)供开发者调⽤。3、管理应⽤程序即控制进程的⽣命周期:进程开始时的环境配置和资源分配、进程结束后的资源回收、进程调度等。4、操作系统内核的功能(1)进程调度能⼒: 管理进程、线

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

go基础知识归纳总结

无缓冲的 channel 和有缓冲的 channel 的区别? 在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。 无缓冲的 channel 行为:无缓冲的 channel 是一种同步的通信方式,发送和接收必须同时发生。如果一个 goroutine 试图通过无缓冲 channel

java常用面试题-基础知识分享

什么是Java? Java是一种高级编程语言,旨在提供跨平台的解决方案。它是一种面向对象的语言,具有简单、结构化、可移植、可靠、安全等特点。 Java的主要特点是什么? Java的主要特点包括: 简单性:Java的语法相对简单,易于学习和使用。面向对象:Java是一种完全面向对象的语言,支持封装、继承和多态。跨平台性:Java的程序可以在不同的操作系统上运行,称为"Write once,

Vector3 三维向量

Vector3 三维向量 Struct Representation of 3D vectors and points. 表示3D的向量和点。 This structure is used throughout Unity to pass 3D positions and directions around. It also contains functions for doin

8. 自然语言处理中的深度学习:从词向量到BERT

引言 深度学习在自然语言处理(NLP)领域的应用极大地推动了语言理解和生成技术的发展。通过从词向量到预训练模型(如BERT)的演进,NLP技术在机器翻译、情感分析、问答系统等任务中取得了显著成果。本篇博文将探讨深度学习在NLP中的核心技术,包括词向量、序列模型(如RNN、LSTM),以及BERT等预训练模型的崛起及其实际应用。 1. 词向量的生成与应用 词向量(Word Embedding)

关于回调函数和钩子函数基础知识的整理

回调函数:Callback Function 什么是回调函数? 首先做一个形象的比喻:   你有一个任务,但是有一部分你不会做,或者说不愿做,所以我来帮你做这部分,你做你其它的任务工作或者等着我的消息,但是当我完成的时候我要通知你我做好了,你可以用了,我怎么通知你呢?你给我一部手机,让我做完后给你打电话,我就打给你了,你拿到我的成果加到你的工作中,继续完成其它的工作.这就叫回叫,手机

用Python实现时间序列模型实战——Day 14: 向量自回归模型 (VAR) 与向量误差修正模型 (VECM)

一、学习内容 1. 向量自回归模型 (VAR) 的基本概念与应用 向量自回归模型 (VAR) 是多元时间序列分析中的一种模型,用于捕捉多个变量之间的相互依赖关系。与单变量自回归模型不同,VAR 模型将多个时间序列作为向量输入,同时对这些变量进行回归分析。 VAR 模型的一般形式为: 其中: ​ 是时间  的变量向量。 是常数向量。​ 是每个时间滞后的回归系数矩阵。​ 是误差项向量,假

Matter.js:Web开发者的2D物理引擎

Matter.js:Web开发者的2D物理引擎 前言 在现代网页开发中,交互性和动态效果是提升用户体验的关键因素。 Matter.js,一个专为网页设计的2D物理引擎,为开发者提供了一种简单而强大的方式,来实现复杂的物理交互效果。 无论是模拟重力、碰撞还是复杂的物体运动,Matter.js 都能轻松应对。 本文将带你深入了解 Matter.js ,并提供实际的代码示例,让你一窥其强大功能