- 文档
- 教程
- 游戏开发
【免费下载链接】godot-docs
Godot Engine official documentation
Transform2D是 Godot 引擎内置的 2×3 变换矩阵 Variant 类型,由x、y两个基向量轴与origin原点三个Vector2分量组成,可统一表达 2D 空间中的平移、旋转、缩放与剪切。本文以官方类参考文档 classes/class_transform2d.rst 为骨架,结合仓库内 矩阵与变换教程、视口与画布变换教程 与 C# 差异文档,逐项讲解其属性、常量、构造器、方法、运算符,并给出坐标转换、父子节点合成、面向目标与插值动画等可复制的实战代码。读完本文,你将能徒手构造、分解、复合与求逆任意 2D 变换,并理解CanvasItem节点变换在引擎中的底层运作方式。
一、Transform2D 是什么:一张 2×3 矩阵的解剖
Transform2D是 Godot 的内置Variant类型,本质是一张 2×3 矩阵,用于表示 2D 空间中的一次变换。它包含三个Vector2分量,恰好对应矩阵的三列:
| 分量 | 矩阵列 | 职责 |
|---|---|---|
x | 第 0 列 | 基向量的 X 轴,决定旋转、缩放、剪切 |
y | 第 1 列 | 基向量的 Y 轴,决定旋转、缩放、剪切 |
origin | 第 2 列 | 平移偏移,即 2D 空间中的位置 |
其中x与y两轴合起来构成变换的basis(基)。基向量的长度(即Vector2.length())影响变换的缩放倍率;基向量的方向决定旋转角度;两轴通常互相垂直,但当单独旋转某一轴时,变换便产生剪切(skew),将剪切变换应用到 2D 精灵上会使其呈现扭曲变形。
与Transform3D不同,Godot没有与Basis对应的 2D 独立类型,所有"basis"均指Transform2D的x、y两个分量。另外注意两个布尔语境行为:当Transform2D等于IDENTITY常量时,在布尔语境中求值为false,否则恒为true。
在 Godot 的 2D 坐标系中,X 轴指向右、Y 轴指向下,且旋转方向为顺时针。有趣的是,由于"Y 向下"与"顺时针旋转"恰好互相抵消,所有三角公式的行为与"Y 向上、逆时针"的常规数学系统完全一致——可以认为两种系统中的旋转都是"从 X 到 Y"。
二、三个核心属性与默认值
Transform2D的三个属性全部公开可读写:
origin=Vector2(0, 0):变换的平移偏移,即矩阵第 2 列。在 2D 空间中可直接视为位置。x=Vector2(1, 0):basis 的 X 轴,矩阵第 0 列。与y一起表示旋转、缩放与剪切。在 identity 变换上指向右(Vector2.RIGHT)。y=Vector2(0, 1):basis 的 Y 轴,矩阵第 1 列。与x一起表示旋转、缩放与剪切。在 identity 变换上指向下(Vector2.DOWN)。
从矩阵教程可以得知矩阵各元素的位置关系:t.x.x是左上角,t.x.y是左下角,t.y.x是右上角,t.y.y是右下角(其中t为Transform2D)。理解了这种列主序布局,就能明白为何缩放只需将x、y各自乘以缩放因子:
var t = Transform2D() # 缩放 t.x *= 2 t.y *= 2 transform = t # 将节点变换改为计算结果而在实际项目中,更推荐直接调用scaled()方法完成缩放(详见下文方法章节)。
三、常量:IDENTITY、FLIP_X、FLIP_Y
IDENTITY =Transform2D(1, 0, 0, 1, 0, 0)
identity(单位)变换:无平移、无旋转,缩放为Vector2.ONE。此时x指向右(Vector2.RIGHT),y指向下(Vector2.DOWN)。官方文档给出了验证其列布局的打印代码:
var transform = Transform2D.IDENTITY print("| X | Y | Origin") print("| %.f | %.f | %.f" % [transform.x.x, transform.y.x, transform.origin.x]) print("| %.f | %.f | %.f" % [transform.x.y, transform.y.y, transform.origin.y]) # 输出: # | X | Y | Origin # | 1 | 0 | 0 # | 0 | 1 | 0任何Vector2、Rect2、PackedVector2Array或另一个Transform2D乘以IDENTITY都不发生任何变换。在 GDScript 中,Transform2D.IDENTITY等价于无参构造的Transform2D(),使用常量能让代码意图更清晰,且与 C# 保持一致。
FLIP_X =Transform2D(-1, 0, 0, 1, 0, 0)
当任意变换左乘FLIP_X时,会取反x轴(X 列)的所有分量;当FLIP_X右乘任意变换时,会取反所有轴的x分量(X 行)。换言之,FLIP_X用于绕 Y 轴做镜像翻转。
FLIP_Y =Transform2D(1, 0, 0, -1, 0, 0)
当任意变换左乘FLIP_Y时,会取反y轴(Y 列)的所有分量;当FLIP_Y右乘任意变换时,会取反所有轴的y分量(Y 行)。即绕 X 轴做镜像翻转。
四、五种构造器
Transform2D提供 5 个重载构造器,覆盖从零搭建与从已有数据拷贝两种需求:
Transform2D():构造一个与IDENTITY完全相同的变换。注意:在 C# 中无参构造会把所有分量置为Vector2.ZERO,因此 C# 应使用Transform2D.Identity(详见后文 C# 差异章节)。Transform2D(from: Transform2D):拷贝构造,复制给定变换。Transform2D(rotation: float, position: Vector2):由角度(弧度)与位置构造。Transform2D(rotation: float, scale: Vector2, skew: float, position: Vector2):由角度、缩放、剪切(弧度)与位置构造,参数最全。Transform2D(x_axis: Vector2, y_axis: Vector2, origin: Vector2):直接以三个Vector2指定矩阵三列构造。
矩阵教程中的父子变换手算示例就用到了第 5 种构造器,将手算出的基向量与原点点拼回一个变换:
# 计算子节点的世界空间变换 # origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200) var origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin # basis_x = (2, 0) * 0.5 + (0, 1) * 0 var basis_x = parent.x * child.x.x + parent.y * child.x.y # basis_y = (2, 0) * 0 + (0, 1) * 0.5 var basis_y = parent.x * child.y.x + parent.y * child.y.y # 把节点变换改成计算结果 transform = Transform2D(basis_x, basis_y, origin)五、方法详解:分解、求逆、复合、插值与判定
Transform2D共提供 20 个方法(均为const无副作用),按用途分组如下。
5.1 求逆:inverse 与 affine_inverse
inverse() -> Transform2D:返回本变换的逆矩阵。前提是 basis 必须正交归一(orthonormal),即只能表示旋转,否则结果不正确;此时应改用affine_inverse()。affine_inverse() -> Transform2D:返回本变换的仿射逆。与inverse()不同,它对几乎任何 basis 都有效(包括非均匀缩放),但速度更慢。前提是 basis 的行列式不能恰好为0.0(见determinant())。
逆变换的典型用法是"撤销"变换。矩阵教程给出了验证代码:
var ti = transform.affine_inverse() var t = ti * transform # 结果为单位变换 var ti2 = transform.affine_inverse() position = transform * position position = ti2 * position # position 与变换前相同5.2 分解提取:get_origin / get_rotation / get_scale / get_skew / determinant
get_origin() -> Vector2:返回平移量,等价于origin属性。get_rotation() -> float:返回旋转角(弧度),等价于x轴的角度(即Vector2.angle())。矩阵教程指出,也可用atan2(t.x.y, t.x.x)手动提取旋转角。get_scale() -> Vector2:返回x、y两轴的长度。若 basis 未剪切,该值就是缩放因子,且不受旋转影响。官方示例验证旋转前后缩放保持不变:
var my_transform = Transform2D( Vector2(2, 0), Vector2(0, 4), Vector2(0, 0) ) # 无论如何旋转 Transform2D,其缩放保持不变 my_transform = my_transform.rotated(TAU / 2) print(my_transform.get_scale()) # 输出 (2.0, 4.0)注意:若determinant()返回负数,则缩放也为负。
get_skew() -> float:返回剪切角(弧度)。determinant() -> float:返回 basis 矩阵的行列式,可用于判断两个关键属性:- 行列式恰好为
0.0:basis 不可逆(不可使用inverse()); - 行列式为负数:basis 表示负缩放;
- 若各轴缩放相同,则行列式恒等于该缩放的 2 次幂。
- 行列式恰好为
5.3 复合变换:rotated / scaled / translated 与各自的 _local 变体
六个复合方法两两成对,均返回"新"变换(不修改原对象),是*运算符的优化版本:
| 方法 | 等价运算 | 参照系 |
|---|---|---|
rotated(angle) | R * X(左乘旋转矩阵) | 全局/父级坐标系 |
rotated_local(angle) | X * R(右乘旋转矩阵) | 本地坐标系 |
scaled(scale) | S * X(左乘缩放矩阵) | 全局/父级坐标系 |
scaled_local(scale) | X * S(右乘缩放矩阵) | 本地坐标系 |
translated(offset) | T * X(左乘平移矩阵) | 全局/父级坐标系 |
translated_local(offset) | X * T(右乘平移矩阵) | 本地坐标系 |
其中rotated()在angle为正时顺时针旋转。理解"左乘 = 全局/父级坐标系、右乘 = 本地坐标系"是正确使用这些方法的关键。translated_local()的典型行为:一个顺时针旋转 90 度的对象,调用translated_local(Vector2.UP)会向右移动——因为它沿自身旋转后的方向平移;而translated()则始终按父级/全局方向平移。
矩阵教程强调,translated_local()与直接修改origin是不同的操作:修改origin是在父级坐标系中整体搬动,而translated_local()是沿对象自身朝向移动。日常项目中,2D 节点还可直接使用move_local_x、move_local_y完成本地轴移动。
5.4 插值与面向:interpolate_with / looking_at
interpolate_with(xform: Transform2D, weight: float) -> Transform2D:返回本变换与xform按weight线性插值的结果。weight应位于0.0 ~ 1.0(含端点);超出该范围则执行外推(extrapolation)。插值教程中利用它在两把椅子之间平滑过渡角色的位姿:
$Monkey.transform = $Position1.transform.interpolate_with($Position2.transform, t)静态类型检查文档也把Transform2D.interpolate_with()与Transform3D同名列为一对推荐的类型安全插值手段。
looking_at(target: Vector2 = Vector2(0, 0)) -> Transform2D:返回旋转后的副本,使旋转后的 X 轴指向全局空间中的target位置。这是 2D 物体"面向目标"的便捷实现。
5.5 正交归一与判定:orthonormalized / is_conformal / is_equal_approx / is_finite
orthonormalized() -> Transform2D:返回 basis 被正交归一化的副本。正交归一要求两轴互相垂直(orthogonal)且长度均为1.0(normalized),因此这样的 basis 只能表示旋转。is_conformal() -> bool:若 basis 是共形的则返回true。共形 basis 同时满足正交(两轴垂直)与均匀(两轴等长)。在物理计算中尤其有用——例如判断刚体变换是否只包含旋转与均匀缩放。is_equal_approx(xform: Transform2D) -> bool:对每个分量运行@GlobalScope.is_equal_approx(),判断两个变换是否近似相等。is_finite() -> bool:对每个分量调用@GlobalScope.is_finite(),判断变换是否有限(无inf/nan)。
5.6 基向量变换:basis_xform / basis_xform_inv
basis_xform(v: Vector2) -> Vector2:返回v经 basis 矩阵变换后的副本。与*运算符不同,它忽略origin。当已知变换位于原点(0, 0)时,用它可以跳过平移运算、节省开销(矩阵教程中的明确建议)。basis_xform_inv(v: Vector2) -> Vector2:返回v经basis 的逆矩阵变换后的副本(参考inverse()),同样忽略origin。注意:该方法假设 basis 正交归一;若 basis 非正交归一,应改用transform.affine_inverse().basis_xform(vector)。
六、运算符重载
Transform2D重载了 12 组运算符,覆盖比较、批量变换与标量运算:
6.1 比较运算符
==(Transform2D):各分量精确相等时返回true。因浮点精度误差,官方建议改用更可靠的is_equal_approx()。!=(Transform2D):各分量不完全相等时返回true,同样建议使用is_equal_approx()。
6.2 乘法运算符(变换的核心)
* Vector2:将Vector2按变换矩阵变换。这是把点从变换局部坐标系映射到父级坐标系的常用手段,例如"玩家下方 100 单位的世界坐标":
# 玩家(局部)下方 100 单位的全局/父级空间位置 print(transform * Vector2(0, 100))* Rect2:将整个Rect2矩形按变换矩阵变换(可用于把包围盒带入父级空间)。* PackedVector2Array:批量变换数组中的每个Vector2。在大型数组上,这比逐元素单独变换快得多(引擎层向量化处理)。* Transform2D:矩阵乘法,即两个变换的复合。官方文档明确指出:这就是父节点与子节点CanvasItem之间执行的运算。例如求子节点的世界变换:
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200)) var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100)) # 节点变换改为子节点的世界变换 transform = parent * child注意:矩阵乘法顺序不可颠倒。如果只需要修改某一个属性,官方建议优先使用对应的translated()/rotated()/scaled()等优化方法,而非手动拼矩阵乘法。
* float/* int:将所有分量(包括origin)乘以标量,等价于对变换做均匀缩放。
6.3 除法运算符
/ float// int:将所有分量(包括origin)除以标量,等价于均匀缩小。
6.4 下标运算符
[](int):按索引访问各轴(列):索引0等价于x,索引1等价于y,索引2等价于origin。适合在循环中统一处理三列。
七、实战应用:从坐标转换到引擎内部
7.1 父子节点变换合成
CanvasItem(Node2D、Control的公共基类)节点树中,每个子节点都继承父节点的变换——父节点移动,子节点随之移动。这正是*(Transform2D 乘法)在引擎中的直接体现:子节点的世界变换 =parent_transform * child_transform。矩阵教程中的apply.png示例(父缩放(2, 1)、子缩放(0.5, 0.5))演示了即便子节点自身比例是 1:1,其世界空间 X 向量仍会因父级基向量而变为(1, 0)。
7.2 局部 ↔ 全局坐标互转
视口与画布变换教程 给出了在CanvasItem中转换坐标的标准写法,逆变换正是affine_inverse()的典型应用:
# 从 CanvasItem 内调用 canvas_pos = get_global_transform() * local_pos local_pos = get_global_transform().affine_inverse() * canvas_pos若要进一步换算到屏幕坐标,则要串联画布变换与屏幕变换:
var screen_coord = get_viewport().get_screen_transform() * get_global_transform_with_canvas() * local_pos教程同时提醒:日常开发应尽量在 Canvas 坐标系内工作(get_global_transform()),让引擎自动处理屏幕分辨率缩放,避免手算屏幕坐标。
7.3 视口、画布层与相机
同一教程揭示Transform2D在引擎渲染管线中的多重角色:每个CanvasLayer拥有自己的Transform2D画布变换;Viewport还有全局画布变换(主变换,影响所有画布层)与拉伸变换(用于屏幕缩放);根Window则持有窗口变换。Camera2D的工作原理就是修改Viewport.canvas_transform属性(见 canvas_layers 教程)。从局部坐标到屏幕坐标需依次应用:局部 → 画布层 → 全局画布 → 拉伸 → 窗口这一整条变换链。
7.4 面向目标与插值动画
looking_at()让 2D 对象瞬间"看向"目标点,适合炮塔、朝向光标等逻辑;interpolate_with()则用于在两个位姿之间平滑过渡,配合weight在0.0 ~ 1.0之间取值即可完成补间,超出范围则可实现外推。二者组合可以做出"平滑转向 + 位姿过渡"的完整 2D 动画方案。
7.5 布尔语境与单位元
在布尔语境中Transform2D.IDENTITY求值为false(其余恒为true),可用于快速判断"变换是否为单位变换"。同时,任何类型乘以IDENTITY均不发生变换,矩阵教程因此建议:若想验证自己的矩阵运算是否正确,可用"乘以单位矩阵结果不变"这一性质做自检。
八、C# 使用差异(务必留意)
官方 C# 差异文档 明确指出 C# 中Transform2D的注意事项:
- C# 结构体不允许无参构造器,因此
new Transform2D()会把所有原始成员初始化为默认值(Vector2.ZERO),等价于 GDScript/C++ 的Transform2D()(IDENTITY)请改用Transform2D.Identity。 - 以下方法在 C# 中被转换为同名属性:
| GDScript | C# |
|---|---|
get_rotation() | Rotation |
get_scale() | Scale |
get_skew() | Skew |
此外,C# 构造Transform2D时直接使用 6 个浮点数(如new Transform2D(2, 0, 0, 1, 100, 200)),这与 GDScript 中传入三个Vector2的写法不同,见矩阵教程中的对照示例。
九、小结
Transform2D用一张 2×3 矩阵统一承载了 2D 游戏开发中最常用的四类变换:平移(origin)、旋转与缩放(x、y基向量)、剪切(基向量不垂直)。掌握它的关键是记住三件事:
- 列主序:
x、y、origin分别对应矩阵第 0、1、2 列,t.x.y是左下角元素; - 乘法顺序:
parent * child表示子级变换叠加到父级,顺序不可颠倒;左乘作用于父级/全局坐标系,右乘作用于本地坐标系; - 求逆有讲究:
inverse()要求正交归一 basis,通用场景用affine_inverse(),且要求行列式非零。
在引擎内部,Transform2D贯穿了CanvasItem父子合成、视口画布变换、Camera2D跟拍、输入事件坐标换算等几乎全部 2D 渲染路径。建议继续阅读仓库内的 矩阵与变换教程、数学文档索引 与 视口与画布变换教程,并结合 插值教程 进一步掌握位姿过渡的实战技巧。
- 文档
- 教程
- 游戏开发
【免费下载链接】godot-docs
Godot Engine official documentation
相关推荐
three.js Matrix4 完全指南:4×4 变换矩阵的原理、API 与源码解析
three.js Matrix4 完全指南:4×4 变换矩阵的原理、API 与源码解析 本指南以 docs/pages/Matrix4.html.md http
前端3D渲染图形学3分钟看清电脑硬件状态:LibreHardwareMonitor上手指南
3分钟看清电脑硬件状态:LibreHardwareMonitor上手指南 LibreHardwareMonitor 是一款免费的硬件监控工具,运行后就能持续读出
指标监控原神抽卡数据分析终极指南:免费开源工具genshin-wish-export完整教程
原神抽卡数据分析终极指南:免费开源工具genshin wish export完整教程 还在为原神抽卡记录无法保存而烦恼吗?想知道自己的抽卡概率到底如何吗?gen
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考