Godot Matrices and transforms(矩阵和变换)
介绍
在阅读本教程之前,我们建议您彻底阅读并理解矢量数学教程,因为本教程要求您具备矢量知识。
本教程是关于变换以及我们如何在Godot中使用矩阵来表示它们。它不是一个完整深入的矩阵指南。变换大多数时候是以平移、旋转和缩放的方式应用的,所以我们将重点介绍如何用矩阵来表示这些变换。
本指南的大部分内容集中在2D,使用Transform2D和Vector2,但在3D中的工作方式非常相似。
强调文本 强调文本
正如在前面的教程中所提到的,重要的是要记住,在Godot中,Y轴在二维中是指向下方的。这与大多数学校教授线性代数的方式相反,Y轴指向上。
惯例是 X 轴为红色,Y 轴为绿色,Z 轴为蓝色。本教程的颜色编码与这些约定相符,但我们也会用蓝色来表示原点向量。
矩阵分量和单位矩阵
单位矩阵表示一个没有平移、没有旋转、没有缩放的变换。让我们先看看单位矩阵以及它的分量是如何与它的外观相联系的
矩阵有行和列,而变换矩阵对于每个矩阵的作用有特定的约定。
在上图中,我们可以看到红色的X向量由矩阵的第一列表示,绿色的Y向量同样由第二列表示。 更改列将更改这些向量。 在接下来的几个示例中,我们将看到如何对其进行操作。
您不应该担心直接操作行,因为我们通常使用列。但是,你可以把矩阵的行看作是表示哪些向量有助于在给定的方向上移动。
当我们提到t.x.y这样的值时,那就是X列向量的Y分量。换句话说,就是矩阵的左下角。同理,t.x.x是左上角,t.y.x是右上角,t.y.y是右下角,其中t是Transform2D。
现在,为了缩放矩阵,我们所要做的就是把每个分量乘以我们想要的缩放。把它放大2。1乘以2等于2,0乘以2等于0,所以我们得到这个
为了在代码中做到这一点,我们可以简单地乘以每个向量:
var t = Transform2D()
# Scale
t.x *= 2
t.y *= 2
transform = t # Change the node's transform to what we just calculated.
如果我们想把它恢复到原来的比例,我们可以把每个分量乘以0.5。这几乎就是缩放变换矩阵的全部内容。
要从现有的变换矩阵计算对象的比例,可以在每个列向量上使用length()。
在实际项目中,你可以使用scaled()方法来执行缩放。
旋转变换矩阵
我们将以与前面相同的方式开始,在单位矩阵下面使用Godot标识:
例如,我们想要顺时针旋转Godot标志90度。现在X轴向右,Y轴向下。如果我们在脑海中旋转这些,我们会逻辑地看到新的X轴应该指向下面,新的Y轴应该指向左边。
可以想象,同时抓取Godot标识及其矢量,然后围绕中心旋转它。无论你在哪里结束旋转,向量的方向决定了矩阵是什么。
我们需要在法线坐标中表示“下”和“左”,所以这意味着我们将X设置为(0,1),将Y设置为(-1,0)。 这些也是Vector2.DOWN和Vector2.LEFT的值。 当我们这样做时,我们得到旋转对象的预期结果:
如果你理解上面的问题有困难,试试这个练习:剪一张正方形的纸,在上面画出X和Y向量,把它放在图形纸上,然后旋转它并注意端点。
要在代码中执行旋转,我们需要能够以编程的方式计算值。该图像显示了从旋转角度计算变换矩阵所需的公式。如果这部分看起来很复杂,请不要担心,我保证这是您需要知道的最困难的事情。
戈多用弧度表示所有的旋转,而不是角度。一个完整的旋转是TAU 或 PI*2,90度的四分之一旋转是 TAU/4 o或PI/2 。使用TAU通常会产生更易读的代码。
有趣的是:除了在Godot中Y是向下的,旋转是顺时针方向的。这意味着所有的数学和三角函数都与Y向上的CCW系统表现相同,因为这些差异 "抵消 "了。你可以认为两个系统中的旋转都是 “从X到Y”。
为了执行一个0.5弧度(约28.65度)的旋转,我们只需在上面的公式中插入一个0.5的值,然后计算出实际值应该是多少:
下面是如何在代码中完成(将脚本放在一个Node2D):
var rot = 0.5 # The rotation to apply.
var t = Transform2D()
t.x.x = cos(rot)
t.y.y = cos(rot)
t.x.y = sin(rot)
t.y.x = -sin(rot)
transform = t # Change the node's transform to what we just calculated.
要从现有的变换矩阵计算对象的旋转,可以使用atan2(t.x.y,t.x.x),其中t是Transform2D。
tanθ=对边/邻边,要计算旋转的弧度,用atan2(对边,邻边)即atan2(t.x.y,t.x.x)
在实际项目中,可以使用rotate()方法执行旋转。
变换矩阵的基础
到目前为止,我们只对x和y两个向量进行了研究,它们负责表示旋转、缩放和/或剪切(高级的,在最后介绍)。X和Y向量一起被称为变换矩阵的基础。"基础 "和 "基础向量 "这两个术语很重要,要知道。
你可能已经注意到,Transform2D实际上有三个Vector2值:x、y和origin。原点值不是基础的一部分,但它是变换的一部分,我们需要它来表示位置。从现在开始,我们将在所有的例子中跟踪原点向量。您可以将原点视为另一列,但通常最好将它完全分开。
请注意,在3D中,Godot具有用于保存基础的三个Vector3值的单独的Basis结构,因为代码可能变得复杂,并且将其与Transform分开是有意义的(Transform由一个Basis和一个额外的Vector3组成) )。
平移变换矩阵
改变原点向量称为平移变换矩阵。平移基本上是一个技术术语,用于“移动”物体,但它明确地不涉及任何旋转。
让我们通过一个示例来帮助理解这一点。和上次一样,我们从单位变换开始,只是这次我们要跟踪原点向量。
如果我们想让物体移动到(1,2)的位置,我们只需要将它的原点向量设置为(1,2):
还有一个translation()方法,该方法执行与直接添加或更改原点不同的操作。 translation()方法将使对象相对于其自身的旋转进行平移。 例如,使用Vector2.UP转换时,顺时针旋转90度的对象将向右移动。
Godot的2D使用的是基于像素的坐标,所以在实际的项目中,你会希望按几百个单位进行平移。
把它们放在一起
我们将把到目前为止提到的所有内容应用到一个转换中。 接下来,创建一个带有Sprite节点的简单项目,并使用Godot徽标作为纹理资源。
让我们将平移设置为(350,150),旋转-0.5 rad,缩放3。我已经发布了一个截图,以及重现它的代码,但是我鼓励您在不查看代码的情况下尝试重现这个截图!
var t = Transform2D()
# Translation
t.origin = Vector2(350, 150)
# Rotation
var rot = -0.5 # The rotation to apply.
t.x.x = cos(rot)
t.y.y = cos(rot)
t.x.y = sin(rot)
t.y.x = -sin(rot)
# Scale
t.x *= 3
t.y *= 3
transform = t # Change the node's transform to what we just calculated.
变换的实际应用
在实际项目中,通常将拥有多个Node2D或空间节点的父节点的transform中使用transform。
然而,有时手动计算我们需要的值是非常有用的。我们将介绍如何使用Transform2D或Transform来手动计算节点的变换。
变换之间的位置转换
在很多情况下,你会想把一个位置转换到一个变换中去。例如,如果你有一个相对于玩家的位置,并想找到世界(父相对)位置,或者如果你有一个世界位置,并想知道它相对于玩家的位置。
我们可以使用“ xform”方法找到相对于玩家的向量在世界空间中的定义:
# World space vector 100 units below the player.
print(transform.xform(Vector2(0, 100)))
我们可以使用 "xform_inv "方法来找到一个相对于玩家的世界空间位置:
# Where is (0, 100) relative to the player?
print(transform.xform_inv(Vector2(0, 100)))
如果您事先知道变换的位置在 (0, 0),您可以使用 “basis_xform” 或 “basis_xform_inv” 方法来代替,这些方法可以跳过处理转换。
相对于自身移动物体
一个常见的操作,特别是在3D游戏中,是相对于自身移动一个物体。例如,在第一人称射击游戏中,当你按下W时,你会希望角色向前移动(-Z轴)。
由于基本向量是相对于父对象的方向,而原点向量是相对于父对象的位置,因此我们可以简单地添加多个基本向量来相对于自身移动对象。
该代码将一个对象移动100个单位到自己的右边。
transform.origin += transform.x * 100
如果要在3D中移动,你需要将 "x "替换为 “basis.x”。
在实际项目中,你可以在3D中使用translate_object_local或者在2D中使用move_local_x和move_local_y来实现。
将变换应用到变换上
关于变换的最重要的事情之一是如何将几个变换一起使用。一个父节点的变换会影响它的所有子节点。让我们来剖析一个例子。
在这个图像中,子节点在组件名称后面有一个“2”,以区别于父节点。这么多的数字可能看起来有点让人不知所措,但请记住,每个数字都显示了两次(箭头旁边和矩阵中),而且几乎一半的数字是零。
这里进行的唯一转换是,父节点的缩放为(2,1),子节点的缩放为(0.5,0.5),并且两个节点都被给定了位置。
我们知道,单位矩阵(如下图),表示没有旋转和缩放。现在父节点,X(2,0),表面了x轴方向放大2倍,Y(0,1)在y轴没有缩放,保持不变,所以父节点看起来被向横拉伸,同理,子节点在x,y轴都被缩小一半。
而原点向量表示平移,初始时O(0,0)。父节点O(1,2),表示了相对于舞台平移到了(1,2)位置。