Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

使用 SceneTree

介绍

在之前的教程中,所有内容都是围绕节点这一概念展开的。场景是节点的合集,节点一进入 场景树 就会被激活。

MainLoop

Godot 内部的工作方式是这样的:开始的时候只会有一个 OS 类的实例在运行,然后才会把驱动程序、服务器、脚本语言、场景系统等等这些加载进来。

初始化完成后,就会为 OS 提供一个 MainLoop 来运行。目前为止的所有内容都是内部工作(如果你有兴趣查看内部如何工作,可以在源代码中查看 main/main.cpp 文件)。

用户程序或游戏会在 MainLoop 中启动,这个类包含初始化、空闲(帧同步回调)、固定(物理同步回调)、输入等方法。当然这也属于底层,用 Godot 制作游戏时几乎不会遇到需要自行编写 MainLoop 的情况。

SceneTree

解释 Godot 运作原理的一种方式是,它是一个构建在底层中间件之上的高级游戏引擎。

场景系统是游戏引擎,而 OS 和服务器是底层 API。

场景系统为 OS 提供了自己的主循环,即 SceneTree。运行场景时会自动实例化并设置该场景,无需执行任何其他工作。

重要的是要知道此类的存在, 因为它有一些重要的用途:

  • 它包含根 Viewport, 当场景第一次打开成为 Scene Tree 的一部分时, 会将场景作为子级添加到其中(接下来会有更多).

  • 它包含有关编组的信息, 并具有调用编组中所有节点或获取它们的列表的方法.

  • 它包含一些全局状态功能, 例如设置暂停模式或退出进程.

当节点是场景树的一部分时, 可以通过调用 Node.get_tree() 获得 SceneTree 单例.

根视图

Viewport 始终位于场景的顶部。从一个节点来看,可以通过两种不同的方式获得它:

get_tree().root # Access via scene main loop.
get_node("/root") # Access via absolute path.

此节点包含主视口, 默认情况下, Viewport 的任何子节点都将绘制在其中, 因此以Viewport作为根节点是有意义的, 否则将看不到任何内容.

尽管可以在场景中创建其他视图(用于分屏效果等),但该视图是唯一不由用户创建的视图。它是在 SceneTree 内部自动创建的。

场景树

当节点直接或间接连接到根视图时,它就成为了场景树的一部分。

因此,正如在之前的教程中所解释的,它将获得 _enter_tree()_ready() 回调(以及 _exit_tree() )。

../../_images/activescene.webp

当节点进入场景树时,它们将变为活动状态。它们可以访问需要处理的所有内容、获取输入、显示 2D 和 3D 视觉效果、收发通知、播放声音等。当从场景树中删除时,它们将失去这些能力。

树顺序

Godot中的大多数节点操作,如绘制 2D、处理或获取notifications(通知),都是以 树型顺序 或编辑器中看到的自上而下的方式完成的(也称为前序遍历):

../../_images/toptobottom.webp

例如,场景中的顶部节点首先调用其 _process() 函数,然后它下面的节点调用其 _process() 函数,然后是它下面的节点,依此类推。

不过有一个重要的例外,那就是 _ready() 函数:每个父节点的 _ready() 函数,只有在它所有的子节点都调用完各自的 _ready() 函数之后,才会被调用。这样一来,父节点就能确信它的子节点已经完全准备好,可以被正常访问了。这种顺序在计算机科学里也被称为“后序遍历”。在上面的示意图中, NameLabel 会最先收到通知(当然,前提是它自己没有子节点,如果有的话,得等它的子节点先准备好!),紧接着是 Name ,以此类推,而 Panel 则会最后一个收到通知。

也可以使用 process_priority 节点属性覆盖操作顺序。首先调用编号较小的节点。例如,优先级为 “0、1、2、3” 的节点将按从左到右的顺序被调用。

通过进入场景树“变为活动状态”

  1. 从磁盘加载场景或通过脚本创建场景。

  2. 该场景的根节点(记住,一个场景只能有一个根节点哦!)会被添加为 SceneTree 中 "root" 视口的子节点,或者被添加为 "root" 视口任意某个后代节点的子节点。

  3. 新添加的场景的每个节点都将按照从上到下的顺序接收“enter_tree”通知(GDScript 中的 _enter_tree() 回调函数)。

  4. 为了方便开发,每个节点都会在它所有的子节点都收到 "ready" 通知之后,才会收到自己的 "ready" 通知(也就是 GDScript 中的 _ready() 回调函数)。这种顺序在计算机科学里也被称为“后序遍历”。

  5. 删除场景(或场景的一部分)后,它们将按照自底向上的顺序(自顶向下的逆序)收到“exit scene”通知(GDScript 中的 _exit_tree() 回调函数)。

更改当前场景

场景加载完成之后,你可能想要把它切换成另一个场景。实现这一点的方法之一,就是使用 SceneTree.change_scene_to_file() 函数:

func _my_level_was_completed():
    get_tree().change_scene_to_file("res://levels/level2.tscn")

除了使用文件路径,我们也可以使用现成的 PackedScene 资源,并通过调用等效的函数 SceneTree.change_scene_to_packed(PackedScene scene) 来实现:

var next_scene = preload("res://levels/level2.tscn")

func _my_level_was_completed():
    get_tree().change_scene_to_packed(next_scene)

这些都是切换场景的快捷且实用的方法,但缺点也很明显:游戏会一直卡顿(暂停响应),直到新场景完全加载并运行起来。在游戏开发的某个阶段,你可能更希望制作出带有进度条、动态加载动画或者支持线程(后台)加载的‘正经’加载界面。要实现这些功能,就需要通过 单例(自动加载)后台加载 来手动实现了。