【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 Sprite


func _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 Line2D

var subdivision = 6 # 细分数
var r = 100 # 旋转半径
var center = Vector2(400,200) # 旋转中心


func _ready():
	width = 2
	
	var 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 = pots
	pass

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

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

绘制圆弧和扇形

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

extends Line2D

var subdivision = 5 # 细分数
var start_angle = deg2rad(45) # 起始角度
var end_angle = deg2rad(90) # 起始角度
var r = 100 # 旋转半径
var center = Vector2(400,200) # 选中中心


func _ready():
	width = 2
	
	var pots:PoolVector2Array = []
	var uint = Vector2.RIGHT # 向右的单位向量
	var d_angle = end_angle - start_angle if end_angle - start_angle >=0 else start_angle - end_angle
	var 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 = pots
	pass

在这里插入图片描述

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

螺旋线

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

三角函数

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ROS机器人入门第二课:ROS集成开发环境搭建

文章目录 ROS机器人入门第二课:ROS集成开发环境搭建一、安装终端(一)安装Terminator(二)添加到收藏夹(三)Terminator 常用快捷键第一部份:关于在同一个标签内的操作第二部份&#xf…

【开发环境搭建篇】Nacos的安装和配置

作者介绍:本人笔名姑苏老陈,从事JAVA开发工作十多年了,带过大学刚毕业的实习生,也带过技术团队。最近有个朋友的表弟,马上要大学毕业了,想从事JAVA开发工作,但不知道从何处入手。于是&#xff0…

01-机器学习概述

机器学习的定义 机器学习是一门从数据中研究算法的科学学科。 机器学习直白来讲, 就是根据已有的数据,进行算法选择,并基于算法和数据 构建模型,最终对未来进行预测。 机器学习就是一个模拟人决策过程的一种程序结构。 机器学…

系统大屏可视化展示平台解决方案(原件)

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.7.系统权限设计 3.8.数据查询过程设…

隐私计算实训营学习三:隐私计算框架的架构和技术要点

文章目录 一、隐语架构二、产品层三、算法层3.1 PSI与PIR3.2 Data Analysis-SCQL3.3 Federated Learning 四、计算层4.1 混合调度编译-RayFed4.2 密态引擎4.3 密码原语YACL 五、资源管理层六、互联互通七、跨域管控 一、隐语架构 1、完备性:支持多种技术&#xff0…

QT:QTableWidget表格中加入勾选框

1.新建QTableWidget控件:tableWidget_TestResult 2.举例:在第一行第一列添加一个勾选框 //添加选择框QTableWidgetItem* check0 new QTableWidgetItem();check0->setCheckState(Qt::Checked);ui->tableWidget_TestResult->setItem(0, 0, chec…

Redis如何删除大key

参考阿里云Redis规范 查找大key: redis-cli --bigkeys 1、String类型: Redis 4.0及以后版本提供了UNLINK命令,该命令与DEL命令类似,但它会在后台异步删除key,不会阻塞当前客户端,也不会阻塞Redis服务器的…

C语言函数和数组

目录 一.数组 一.一维数组: 1.一维数组的创建: 2.一维数组的初始化: 3.一维数组的使用 4.一维数组在内存中的存储: 二.二维数组: 三.数组越界: 四.数组作为函数参数: 二.函数 一.函数是什么&…

NVIDIA最新 Blackwell架构简介

NVIDIA Blackwell架构简介 在AI和大型语言模型(LLMs)迅速发展的领域中,追求实时性能和可扩展性至关重要。从医疗保健到汽车行业,组织正深入探索生成性AI和加速计算解决方案的领域。对生成性AI解决方案的需求激增,促使企…

代码随想录算法训练营第二十八天|● 93.复原IP地址 ● 78.子集 ● 90.子集II (JS写法)

93 复原IP地址 题目链接/文章讲解:https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html 视频讲解:https://www.bilibili.com/video/BV1XP4y1U73i/ 思路: /*** param {string} s* return {string[]}*/ var resto…

Java数据结构-顺序表

目录 1. 顺序表的相关概念1.1 线性表1.2 顺序表2. 功能实现2.1 整体框架2.2 乱七八糟的功能(bushi)2.2.1 判断容量是否满2.2.2 返回顺序表当前长度2.2.3 扩容2.2.4 清空整个顺序表 2.3 插入数据2.3.1 头插数据2.3.2 尾插数据2.3.3 指定位置插入 2.4 删除数据2.4.1 删除第一次出…

中国贸易金融跨行交易区块链平台CTFU、区块链福费廷交易平台BCFT、中国人民银行贸易金融区块链平台CTFP、银行函证区块链服务平台BPBC

中国人民银行贸易金融区块链平台CTFP介绍 贸易金融的发展概况及存在的问题 1.1 贸易金融的概况 贸易金融是指商业银行在贸易双方债权债务关系的基础上,为国内或跨国的商品和服务贸易提供的贯穿贸易活动整个价值链、全程全面性的综合金融服务。伴随全球化的进程&a…

基于java+springboot+vue实现的宿舍管理系统(文末源码+Lw+ppt)23-597

摘 要 随着信息时代的来临,过去的传统管理方式缺点逐渐暴露,对过去的传统管理方式的缺点进行分析,采取计算机方式构建宿舍管理系统。本文通过课题背景、课题目的及意义相关技术,提出了一种楼宇信息、宿舍信息、宿舍安排、缺勤信…

使用Python进行自动化测试Selenium与PyTest的结合【第150篇—自动化测试】

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行自动化测试:Selenium与PyTest的结合 在软件开发中,自…

数据结构:图的最短路径

目录 一、最短路径的基本概念 二、无权图单源最短路径 三、Dijkstra算法(正权图单源) 3.1、算法的基本步骤 3.2、算法的实现 3.3、习题思考 3.3.1、网络延迟时间 四、A*算法(正权图单源单目标点) 4.1、算法的基本概念 4…

web自动化--元素定位之xpath和css

元素定位 xpath绝对路径相对路径案例xpath策略(路径)案例xpath策略(层级、扩展)属性层级与属性层级与属性拓展层级与属性综合 csscss选择器(id、类、标签、属性)id选择器类选择器标签选择器属性选择器案例-…

黄衣老太怒骂,难阻年轻人的热情,苹果CEO库克笑开怀

苹果在中国最大的专卖店在上海静安开业,壮观的排队场景--特别的是排队的大多都是年轻人,凸显出国内消费者对苹果的热情,苹果CEO库克亲自在专门店门口迎客,还与消费者热情拥抱。 不过在苹果静安店开业的当天出现了一个插曲&#xf…

E3S独立出版 | 2024年第二届绿色建筑国际会议(ICoGB 2024)

会议简介 Brief Introduction 2024年第二届绿色建筑国际会议(ICoGB 2024) 会议时间:2024年5月22日-24日 召开地点:意大利米兰 大会官网:www.icogb.org ICoGB 2024由意大利米兰理工大学主办,西安交通大学,葡萄牙米尼奥大…

ubuntu20.04安装 ffmpeg 开发环境

参考:参考1 一些相关软件包,已打包整理好,如下 源码包 1、安装步骤 创建安装目录 sudo mkdir -p /usr/local/ffmpeg/lib 解压源码 tar -jxf ffmpeg-4.3.2.tar.bz2 到指定ffmpeg目录进行配置 cd ffmpeg-4.3.2/ 配置:会报错很多…

对BOM的理解,常见的BOM对象有哪些?(非常详细)

文章目录 一、是什么二、window三、location四、navigator五、screen六、history 一、是什么 BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象 其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退&…
最新文章