2D 坐标系统与 2D 变换
2D 坐标系统与 2D 变换
在 2D 游戏开发中,"坐标"就像地图上的经纬度——它告诉引擎每个东西应该画在屏幕的哪个位置。但 Godot 里不止一种坐标系统,理解它们之间的关系,是解决"为什么我的角色不在预期的位置"这类问题的关键。
这一章,我们来看看 Godot 内置了哪些 2D 坐标系统,以及 Transform2D(2D 变换矩阵)是如何在它们之间转换坐标的。
前置知识
如果你还不了解视口(Viewport)和画布(Canvas)变换的基本概念,建议先阅读官方文档中的 Viewport and canvas transforms 章节。
Transform2D 是什么?
Transform2D 是一个 2D 变换矩阵,它可以把坐标从一个坐标系统转换到另一个坐标系统。
你可以把它想象成一个"翻译官"——当你说"我在 (100, 50) 这个位置"时,这个"位置"是相对于哪个坐标系统的?是相对于角色自身的?还是相对于整个游戏世界的?还是相对于屏幕的?Transform2D 就负责在这些不同的"语言"之间做翻译。
想要深入理解矩阵变换的数学原理,可以参考 Matrices and transforms 教程。
Godot 2D 坐标系统一览
Godot 内部使用了多种坐标系统,下面这张图展示了它们之间的关系:
上面的图基于以下节点树结构:Root Window(嵌入窗口) ⇒ Window(不嵌入窗口) ⇒ CanvasLayer ⇒ CanvasItem ⇒ CanvasItem ⇒ CanvasItem。实际中可以有更复杂的组合(比如多层嵌套的 Window 和 SubViewport),但这个例子足以说明整体方法论。
为了简洁,上图没有包含
SubViewport、SubViewportContainer、ParallaxLayer和ParallaxBackground——它们同样会影响变换。
下面从左到右(从外到内)逐一介绍每个坐标系统:
项目坐标(Item Coordinates)
这是 CanvasItem(画布项)自身的局部坐标系统。
简单来说,就是"以这个节点自己为原点"的坐标系。当你给一个 Sprite2D 设置 position = (100, 0) 时,这个 (100, 0) 就是它相对于父节点的位置——但 Sprite2D 内部的纹理坐标、碰撞形状等,都是用项目坐标来描述的。
父节点坐标(Parent Item Coordinates)
这是父级 CanvasItem 的局部坐标系统。
当你在场景中放置 CanvasItem 时,它们通常会继承父级 CanvasItem 的变换。也就是说,父节点移动了,子节点也会跟着移动。一个例外是设置了 top_level = true 的 CanvasItem,它会跳出父节点的变换链。
画布坐标(Canvas Coordinates)
正如在 Canvas layers 中提到的,有两种画布:Viewport 画布和 CanvasLayer 画布,它们各自拥有一个画布坐标系统。
画布坐标也常被称为世界坐标(World Coordinates)。一个 Viewport 可以包含多个具有不同坐标系统的画布。
视口坐标(Viewport Coordinates)
这是 Viewport 自身的坐标系统。
相机坐标(Camera Coordinates)
这个坐标系统仅在引擎内部使用,用于 3D 相机的射线投影等功能。作为游戏开发者,你一般不会直接操作它。
嵌入者坐标 / 屏幕坐标(Embedder Coordinates / Screen Coordinates)
场景树中的每个 Viewport(Window 或 SubViewport)要么嵌在另一个节点中,要么嵌在操作系统的窗口管理器中。这个坐标系统的原点是嵌入它的节点或操作系统窗口的左上角,缩放比例也与嵌入者或窗口管理器一致。
如果嵌入者就是操作系统的窗口管理器,那么它也叫屏幕坐标(Screen Coordinates)。
绝对嵌入者坐标 / 绝对屏幕坐标(Absolute Embedder / Screen Coordinates)
这个坐标系统的原点是嵌入节点或操作系统屏幕的左上角,缩放比例与嵌入者或窗口管理器一致。
如果嵌入者就是操作系统的窗口管理器,那么它也叫绝对屏幕坐标(Absolute Screen Coordinates)。
节点变换详解
上面提到的每个坐标系统之间,是通过各种节点上的变换(Transform)来连接的。除了少数例外,这些变换都是 Transform2D 类型。下面逐一介绍:
CanvasItem 变换
CanvasItem 包括两种节点:Control(UI 控件)和 Node2D(2D 节点)。
- Control 节点:变换由相对于父节点原点的位置、以及围绕锚点的缩放和旋转组成。
- Node2D 节点:变换由位置(
position)、旋转(rotation)、缩放(scale)和倾斜(skew)组成。
这个变换不仅影响节点自身,通常也会影响其子 CanvasItem。在 SubViewportContainer 的情况下,它还会影响所包含的 SubViewport。
CanvasLayer 变换
CanvasLayer 的变换会影响该层内的所有 CanvasItem,但不会影响同一 Viewport 中的其他 CanvasLayer 或 Window。
CanvasLayer 跟随视口变换
这是一个自动计算的变换,基于 Viewport 的画布变换和 CanvasLayer 的跟随视口缩放(follow_viewport_scale)。启用后,可以用来实现伪 3D 效果(比如远景移动得比近景慢)。它影响的子节点范围与 CanvasLayer 变换相同。
Viewport 画布变换
画布变换影响 Viewport 默认画布中的所有 CanvasItem,同时也会影响启用了跟随视口变换的 CanvasLayer。Viewport 中激活的 Camera2D 就是通过修改这个变换来工作的。它不会影响该 Viewport 中嵌入的 Window。
Viewport 全局画布变换
Viewport 还有一个全局画布变换,这是最高级别的变换,会影响所有 CanvasLayer 和嵌入的 Window 变换。它主要在 Godot 的 CanvasItem 编辑器中使用。
Viewport 拉伸变换
Viewport 还有一个拉伸变换,用于在调整视口大小或拉伸视口时使用。正如 Multiple resolutions 中描述的那样,它用于 Window;也可以通过 size 和 size_2d_override 属性在 SubViewport 上手动设置。它的平移、旋转和倾斜始终是默认值,只能有非默认的缩放。
Window 变换
每个 Window 都包含一个窗口变换,用于缩放和定位窗口内容(详见 Multiple resolutions)。比如,它负责在窗口两侧显示黑边,以保持 Viewport 以固定的宽高比显示。
Window 位置
每个 Window 还有一个 position 属性,描述它在嵌入者中的位置。嵌入者可以是另一个 Viewport 或操作系统窗口管理器。
SubViewportContainer 收缩变换
stretch 配合 stretch_shrink 属性决定了一个 SubViewportContainer 是否以及以什么整数倍率缩放其包含的 SubViewport(相对于容器的大小)。
各坐标系统之间的关系总结
下表总结了从"最内层"到"最外层"的坐标系统层次:
| 层级 | 坐标系统 | 对应的变换/属性 | 说明 |
|---|---|---|---|
| 1 | 项目坐标 | CanvasItem 自身 | 节点的本地坐标系 |
| 2 | 父节点坐标 | 父 CanvasItem 变换 | 继承父节点变换(除非 top_level) |
| 3 | 画布坐标 | CanvasLayer 变换 | 也称世界坐标 |
| 4 | 视口坐标 | Viewport 画布变换 | Camera2D 通过修改此变换工作 |
| 5 | 拉伸后坐标 | Viewport 拉伸变换 | 用于多分辨率适配 |
| 6 | 窗口坐标 | Window 变换 + 位置 | 窗口在嵌入者中的位置 |
| 7 | 屏幕坐标 | 嵌入者 / OS 窗口管理器 | 相对于屏幕左上角 |
| 8 | 绝对屏幕坐标 | OS 屏幕 | 屏幕的绝对像素位置 |
实用建议
在大多数 2D 游戏开发中,你只需要关心项目坐标和画布坐标(世界坐标)这两个层级。其他层级主要在处理多分辨率适配、UI 嵌入、子视口等高级场景时才会用到。
最后同步日期
本文档最后同步于 2026-04-15,基于 Godot 官方文档 (latest) 翻译整理。
