【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.mzph.cn/news/766871.shtml

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

相关文章

利用sealos安装k8s集群

1. 环境准备 准备三台干净(未安装过k8s环境)的虚拟机 # 所有的主机都要配置主机名和域名映射 # 设置主机名 hostnamectl set-hostname k8s-master01 # vim /etc/hosts 192.168.59.201 k8s-master01 192.168.59.202 k8s-worker01 192.168.59.203 k8…

基于ssm停车场管理系统(程序+文档+数据库)

** 🍅点赞收藏关注 → 私信领取本源代码、数据库🍅 本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路。🍅关注我不迷路🍅** 一、项目概述…

腾讯云GPU云服务器_并行计算_弹性计算_AI_深度学习

腾讯云GPU服务器是提供GPU算力的弹性计算服务,腾讯云GPU服务器具有超强的并行计算能力,可用于深度学习训练、科学计算、图形图像处理、视频编解码等场景,腾讯云百科txybk.com整理腾讯云GPU服务器租用价格表、GPU实例优势、GPU解决方案、GPU软…

java数据结构与算法基础-----字符串------正则表达式的练习案例---持续补充中

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846 正则表达式基础:https://blog.csdn.net/grd_java/article/det…

xercesc库中文保存XML功能实现

目录 一 参考链接 二 运行结果 三 代码 一 参考链接 DOM Programming Guide (apache.org) Xerces-c DOM XML文件的构造_xerces-c domimplementation-CSDN博客 Xerces-c库的使用-CSDN博客 二 运行结果 三 代码 #if 1//参考链接: https://blog.csdn.net/RGBMa…

2020年黑龙江省水稻种植分布数据

黑龙江省,位于中国最东北部,是我国位置最北、最东,纬度最高,经度最东的省份,气候为温带大陆性季风气候。黑龙江省土地总面积为47.3万平方公里(含加格达奇和松岭区),占全国土地总面积…

HTML元素语义化补充之css函数(三)

文章目录 CSS中的函数css函数–varcss函数–calccss函数–blurcss函数–gradientlinear-gradient的使用 CSS中的函数 ◼ 在前面我们有使用过很多个CSS函数: 比如rgb/rgba/translate/rotate/scale等; CSS函数通常可以帮助我们更加灵活的来编写样式的值; ◼ 下面有几…

Nature:“量子龙卷风”首次模拟黑洞

科学家们在超流体氦气中首次创造出了一个巨大的“量子漩涡”(quantum vortex),用以模拟黑洞。这一成就不仅使他们能够更加细致地观察模拟黑洞的行为,还能探究其与周围环境的交互作用。 诺丁汉大学的研究团队与伦敦国王学院和纽卡斯…

春天到了,颈椎病容易复发和加重,怎么回事?

即将进入四月,气温回暖,大家的着装明显轻便了。实际上,四时之气均有诱发颈椎病复发的因素,春天也不例外。 对于颈椎病人群来说,他们的颈部状态较差,遇到“无孔不入”、挟裹着湿气的风邪,便十分容…

操作系统原理-模拟进程创建、终止、阻塞、唤醒原语——沐雨先生

一、实验题目: 模拟进程创建、终止、阻塞、唤醒原语 二、实验目的: 通过设计并调试创建、终止、阻塞、唤醒原语功能,有助于对操作系统中进程控制功能的理解,掌握操作系统模块的设计方法和工作原理。 三、实验环境: …

【算法分析与设计】翻转二叉树

题目 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1]示例 2: 输入:root [2,1,3] 输出:[2,3,1]示例…

本地运行环境工具UPUPWANK(win)和Navicat数据库管理工具

UPUPWANK安装地址:https://www.upupw.net 1.进入UPUPWANK后点击一键开启 2.新增项目 这里请千万注意80端口,如果80端口被占用了,请记住去任务管理器关闭占用80端口的进程。不然就不会成功显示。(笔者含泪警告,一晚上的…

Spring中的OAuth2

一. 什么是OAuth2 “Auth” 表示 “授权” Authorization “O” 是 Open 的简称,表示 “开放” 连在一起就表示 “开放授权”,OAuth2是一种开放授权协议。 二. OAuth2是什么 怎么用 OAuth2是目前最流行的授权协议,用来授权第三方应用&am…

信号的小波包能量谱计算(以轴承振动信号为例,Python环境)

小波分析是近30年来发展起来的数学分支,是Fourier分析划时代发展的结果,由法国工程师Morlet首先提出,后广泛应用于信号处理、图像处理与分析、地震勘探、故障诊断、自动控制等领域,小波就是小的波形,所谓“小”是指它具…

QT文件读写操作和内容提取

访问IO设备,需要先调用open()来设置正确的OpenMode(例如ReadOnly或ReadWrite) 打开设备后后,使用write() 或putChar() 写入数据到文件和设备,并通过调用read(),readLine() 或readAll() 进行读取;使用完设备后&#xf…

3、Jenkins持续集成-Jenkins安装和插件管理

文章目录 一、Jenkins安装1. 安装JDK2. 获取jenkins安装包3. 安装包上传到服务器,进行安装4. 修改Jenkins配置(1)低版本Jenkins的rpm包(2)高版本Jenkins的rpm包 5. 启动Jenkins6. 打开浏览器访问7. 获取并输入admin账户…

【漏洞复现】netgear路由器 boarddataww 存在RCE漏洞

免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…

FDM3D打印系列——美纹纸遮挡喷漆

大家好,我是阿赵。   自从上次尝试了水补土和喷漆,发现效果不错之后,我就接着进行第二次的尝试了。   这次打印的模型是这个拳皇里面的卢卡尔,别看拍照好像很高大,其实这个模型很小的,只有10cm左右的高…

PySide6-YOLO8目标检测、追踪可视化界面

目录 项目地址实现效果DetectTrack 项目地址 https://github.com/zhengjie9510/pyside-yolo 实现效果 Detect Track

windows安装ssh

一、下载ssh https://github.com/PowerShell/Win32-OpenSSH/releases/download/v8.1.0.0p1-Beta/OpenSSH-Win64.zip 二、安装ssh 解压到C:\Program Files\OpenSSH-Win64 配置环境变量 把 C:\Program Files\OpenSSH-Win64 加到path环境变量里面 C:\Program Files\OpenSSH-Win64&…