- 文档
- 教程
- 游戏开发
【免费下载链接】godot-docs
Godot Engine official documentation
CanvasLayer 是 Godot 引擎中用于 2D 场景独立渲染的核心节点:它通过一个数值化的 layer 索引把挂在其下的所有 CanvasItem(如 Node2D、Control)与默认游戏画面分离,从而让 HUD、视差背景、转场特效等元素不受摄像机与画布变换的影响,以固定屏幕位置绘制。读完本文,你将掌握 CanvasLayer 的全部属性、方法与信号,理解其在 Viewport 变换链中的底层原理,并能在实战中搭建多层 HUD、视差背景与分屏渲染场景。
一、CanvasLayer 是什么:一个独立于游戏画面的渲染层
CanvasLayer 是一个 2D 场景节点,用于"独立渲染"其下的对象。在 Godot 中,所有 2D 节点(Node2D、Control 等)的共同基类是 CanvasItem。CanvasItem 派生的节点,只要是 CanvasLayer 的直接或间接子节点,就会被绘制在该 CanvasLayer 所在的层上。
从继承关系上看,CanvasLayer 继承自 Node,而 ParallaxBackground 又继承自 CanvasLayer——这从源码级说明:ParallaxBackground 本质上就是一个用于视差滚动的 CanvasLayer(其默认 layer 被覆盖为-100)。
CanvasLayer 的核心思想是:把"绘制顺序"从场景树结构里抽离出来,变成一个数值化、跨场景树的全局层索引:
- 默认的 2D 场景以索引
0渲染; - layer 为
-1的 CanvasLayer 绘制在默认画面下方; - layer 为
1的 CanvasLayer 绘制在默认画面上方。
这种顺序关系不受各层内部节点 CanvasItem.z_index 的影响——同一层内部用 z_index 排序,层与层之间则完全由 layer 数值决定。
为什么需要独立渲染层?
在单个场景树中,我们通常希望部分内容不受摄像机(canvas transform)影响:
| 典型需求 | 说明 |
|---|---|
| 视差背景 | 背景移动速度比舞台其他部分慢,营造纵深错觉 |
| UI / HUD | 生命计数器、得分显示、暂停按钮等需要保持屏幕固定位置,即使游戏视角发生变化 |
| 转场特效 | 淡入淡出、混合等过渡效果需要固定在屏幕位置 |
如果直接移动根 CanvasItem 或整个场景来模拟滚动,所有内容都会一起动。而 CanvasLayer 拥有独立的 transform,不依赖其他层的变换,因此可以让 UI 固定在屏幕空间,同时游戏世界的视角正常变化。
二、属性详解:8 个关键配置项
CanvasLayer 的属性如下(括号内为默认值):
| 属性 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| custom_viewport | Node | — | 指定绘制该层的 Viewport,为 null 时使用默认视口 |
| follow_viewport_enabled | bool | false | 是否跟随视口在屏幕空间移动 |
| follow_viewport_scale | float | 1.0 | 跟随视口时的缩放系数 |
| layer | int | 1 | 层的索引,决定绘制顺序 |
| offset | Vector2 | Vector2(0, 0) | 层的基础偏移 |
| rotation | float | 0.0 | 层的旋转(弧度) |
| scale | Vector2 | Vector2(1, 1) | 层的缩放 |
| transform | Transform2D | Transform2D(1, 0, 0, 1, 0, 0) | 层的 2D 变换矩阵 |
| visible | bool | true | 层的可见性 |
1. layer:绘制顺序的数值索引
layer是 CanvasLayer 最核心的属性,默认值为1。数值越小绘制越靠后(越"底"),数值越大绘制越靠前(越"顶")。注意两点边界行为:
- 同层不保证顺序:如果有多个 CanvasLayer 使用相同的 layer 索引,它们的 CanvasItem 子节点之间的前后关系是非确定性的(non-deterministic),不要依赖同层之间的绘制顺序;
- 取值范围:layer 索引应在 RenderingServer.CANVAS_LAYER_MIN(
-2147483648)与 RenderingServer.CANVAS_LAYER_MAX(2147483647)之间(含端点),任何超出该范围的值都会环绕(wrap around),产生意料之外的绘制结果。
2. visible 与 visibility_changed 信号
当visible为false时,该 CanvasLayer 下的所有 CanvasItem 都会被隐藏。与CanvasItem.visible不同,CanvasLayer 的可见性不会传播到其下方的底层——隐藏某个 HUD 层只会影响该层本身的内容。切换 visible 时会发射 visibility_changed 信号,可用于监听 HUD 显隐并联动暂停、淡出等逻辑。
3. transform 家族:offset / rotation / scale / transform
这四个属性共同构成 CanvasLayer 的变换:
offset:层的基础位置偏移(Vector2);rotation:层在屏幕空间中的旋转,单位为弧度;scale:层的缩放(Vector2,可分别控制 X/Y 轴);transform:完整的 Transform2D 变换矩阵,底层直接对应 RenderingServer 中该 canvas 的变换。
由于这些变换施加在整层之上,因此旋转、缩放 HUD 或背景时,只需修改 CanvasLayer 的 transform,无需逐个遍历子节点。
4. follow_viewport_enabled / follow_viewport_scale:伪 3D 效果的关键
follow_viewport_enabled(默认false):开启后,CanvasLayer保持其在世界空间中的位置;关闭时,CanvasLayer 固定在屏幕上的某个位置(这正是 HUD 所需的行为)。follow_viewport_scale(默认1.0):配合跟随模式使用,对层进行缩放。移向"前景"的层应使用逐渐增大的 scale,移向"背景"的层应使用逐渐减小的 scale。
两者组合可以做出伪 3D 效果——例如摄像机拉近时,前景层放大得比背景层更快,从而模拟深度。
5. custom_viewport:一个层对应一个视口
每个 CanvasLayer 只能绘制在一个特定 Viewport上,无法在多个 Viewport 之间共享。custom_viewport指定该 CanvasLayer 使用的 Viewport 节点;为null时使用默认视口。在分屏游戏等使用多个 Viewport 的场景中,必须为每个 Viewport 分别创建一个独立的 CanvasLayer,才能让 HUD 正确显示在各自的屏幕上。
三、方法与信号速查
CanvasLayer 提供 4 个方法和 1 个信号:
| 成员 | 类型 | 说明 |
|---|---|---|
| get_canvas() | RID(const) | 返回该层使用的 canvas 的 RID(RenderingServer 资源标识符) |
| get_final_transform() | Transform2D(const) | 返回从 CanvasLayer 坐标系到 Viewport 坐标系的最终变换矩阵 |
| hide() | void | 隐藏该层下所有 CanvasItem,等价于visible = false |
| show() | void | 显示该层下所有 CanvasItem,等价于visible = true |
| visibility_changed | signal | 层的可见性改变时发射 |
例如在代码中控制 HUD 显隐:
# 显示 HUD 层(等价于 visible = true) $HUDLayer.show() # 隐藏 HUD 层(等价于 visible = false) $HUDLayer.hide() # 获取该层在 RenderingServer 中的 canvas RID,可用于自定义渲染操作 var canvas_rid: RID = $HUDLayer.get_canvas() # 获取层到视口的最终变换 var final_xform: Transform2D = $HUDLayer.get_final_transform()也可以直接操作属性并监听信号:
func _ready() -> void: $HUDLayer.visibility_changed.connect(_on_hud_visibility_changed) func _on_hud_visibility_changed() -> void: print("HUD layer visibility changed")四、实战应用:搭建三层 2D 场景
结合官方教程 Canvas layers,一个典型的横版游戏场景树如下:
Root (Node2D) ├── CanvasLayer (layer = -1) # 视差背景 │ └── ParallaxBackground │ └── ParallaxLayer │ └── Sprite2D (背景贴图) ├── Level (Node2D) # 游戏世界,默认层 0 │ ├── TileMap / CharacterBody2D / Camera2D ... └── CanvasLayer (layer = 1) # HUD └── Control ├── Label (得分、生命值) └── Button (暂停)- 背景层(-1):挂载 ParallaxBackground(本身继承 CanvasLayer,默认 layer 为
-100)或普通 Sprite2D,绘制在所有游戏内容之后; - 游戏世界(0):普通的 2D 内容,受 Camera2D / canvas_transform 控制;
- HUD 层(1 及更高):得分、生命值、暂停按钮等 Control 节点,保持固定屏幕位置。
CanvasLayer与场景树顺序无关,只取决于 layer 数值,因此可以按需动态实例化,无需刻意调整节点在 Scene 面板中的上下位置。
代码动态创建 CanvasLayer
CanvasLayer 可以在运行时动态添加:
func _create_hud_layer() -> void: var hud_layer := CanvasLayer.new() hud_layer.layer = 1 hud_layer.follow_viewport_enabled = false # 固定屏幕位置 add_child(hud_layer) var label := Label.new() label.text = "Score: 0" hud_layer.add_child(label)伪 3D 视差效果
使用跟随视口 + 缩放制造深度感:
var bg_layer := CanvasLayer.new() bg_layer.layer = -5 bg_layer.follow_viewport_enabled = true bg_layer.follow_viewport_scale = 0.5 # 背景缩小、移动更慢 add_child(bg_layer) var fg_layer := CanvasLayer.new() fg_layer.layer = 5 fg_layer.follow_viewport_enabled = true fg_layer.follow_viewport_scale = 1.5 # 前景放大、移动更快 add_child(fg_layer)前景层 scale 增大、背景层 scale 减小,视觉上即产生"前景快、背景慢"的纵深错觉。
五、与 z_index、场景树顺序的关系
初学者容易混淆三种控制绘制顺序的手段,它们的差异如下:
| 手段 | 作用范围 | 说明 |
|---|---|---|
| 场景树顺序 | 同一 CanvasItem 层级内 | 场景面板中最上方的节点反而绘制在最底层(与直觉相反) |
| CanvasItem.z_index | 同一 CanvasLayer / 同一 canvas 内 | 数值越大越靠前,且子节点会叠加父节点的 z_index |
| CanvasLayer.layer | 全局层 | 数值越大越靠前,优先于z_index,跨层不受 z_index 影响 |
从 CanvasItem 文档 可以确认:只有位于相同 z_index上的节点才按相对顺序排序,且子节点的最终 Z 值是其自身 z_index 与父节点 final Z index 之和。而 CanvasLayer 之间的绘制顺序完全由 layer 数值决定——即使某层内的节点 z_index 高达 999,也无法画到 layer 数值更高的层之上。因此:
- 大多数情况下,控制节点前后顺序首选调整场景树顺序或 z_index,不必动用 CanvasLayer;
- 当需要整体性的屏幕固定 UI、视差背景、转场遮罩时,才使用 CanvasLayer。
六、底层原理:CanvasLayer 与变换链
要理解 CanvasLayer 为何能"独立于屏幕",需要了解 Viewport 与画布变换 中的完整变换链。从 CanvasItem 局部坐标到最终屏幕坐标,依次经过:
- Canvas transform(层变换):每个 CanvasLayer 的变换(即 offset/rotation/scale/transform 属性),可通过
get_final_transform()获取; - Global canvas transform:Viewport 的全局画布变换,影响所有层的变换,主要用于 CanvasItem 编辑器;
- Stretch transform:窗口缩放/拉伸时的变换,输入事件会乘以该变换;
- Window transform:根视口即 Window,负责按固定宽高比显示内容(例如两侧黑边)。
get_final_transform()返回的正是从 CanvasLayer 坐标系到 Viewport 坐标系的变换。它背后的实现是:CanvasLayer 在创建/设置 layer 时向 RenderingServer 请求一个 canvas RID(对应get_canvas()),并设置该 canvas 的变换——这就是为什么"整层"可以共享一个 transform 而无需逐个遍历子节点,也是滚动时操作画布变换比移动整个场景更高效的原因。
在代码中手动完成坐标换算时,可参考 2D transforms 教程 给出的链式乘法:
# 将 CanvasItem 局部坐标转换为屏幕坐标 var screen_coord = get_viewport().get_screen_transform() * get_global_transform_with_canvas() * local_pos # 反向换算:屏幕坐标 → 局部坐标(用于处理输入事件) var local_pos = get_global_transform().affine_inverse() * canvas_pos七、注意事项与限制
- 嵌入窗口的层级:嵌入的 Window 被放置在层
1024上;1025及更高层的 CanvasItem 会绘制在嵌入窗口前面。若需要让内容覆盖嵌入窗口,必须使用大于 1024 的层号。 - 一视口一层:CanvasLayer 不能跨 Viewport 共享,分屏游戏要为每个 Viewport 单独创建 CanvasLayer(见
custom_viewport)。 - 层号越界会环绕:layer 必须在
CANVAS_LAYER_MIN(-2147483648)到CANVAS_LAYER_MAX(2147483647)之间,否则数值会回绕。 - 同层顺序不确定:多个 CanvasLayer 使用相同 layer 时,谁前谁后无保证,应避免依赖。
- 可见性不传播:隐藏一个 CanvasLayer 不会影响其他层(区别于 CanvasItem.visible 的层级传播语义)。
- 官方替代节点:做视差滚动时,新项目建议优先使用 Parallax2D,ParallaxBackground(继承自 CanvasLayer)已被标记为 Deprecated。
总结
CanvasLayer 是 Godot 2D 渲染分层的关键机制:它以数值化 layer 索引定义全局绘制顺序,以独立 transform 让 HUD、背景与转场特效脱离摄像机影响,并通过 follow_viewport 系列属性实现伪 3D 视差。理解其 8 个属性、4 个方法与变换链原理后,你就能在单场景树中轻松组织"背景—世界—UI"的三层结构,并为分屏、多视口项目设计正确的渲染分层方案。深入学习可继续阅读官方教程 Canvas layers 与 Viewport and canvas transforms。
- 文档
- 教程
- 游戏开发
【免费下载链接】godot-docs
Godot Engine official documentation
相关推荐
FlappySwift中的渲染顺序控制:zPosition与绘画顺序管理
FlappySwift中的渲染顺序控制:zPosition与绘画顺序管理 在2D游戏开发中,正确的视觉层级关系直接影响玩家体验。想象一下,如果管道出现在背景后面
游戏开发Godot CanvasTexture 完全指南:为 2D 渲染接入法线贴图、镜面高光与独立采样控制
Godot CanvasTexture 完全指南:为 2D 渲染接入法线贴图、镜面高光与独立采样控制 CanvasTexture 是 Godot 2D 渲染管线
文档教程游戏开发Revery图形绘制API详解:2D与3D渲染基础
Revery图形绘制API详解:2D与3D渲染基础 你还在为跨平台桌面应用的图形渲染性能发愁吗?想在保持开发效率的同时获得媲美原生C代码的渲染速度?Revery
桌面应用跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考