使用场景树 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().get_root() # Access via scene main loop.
get_node("/root") # Access via absolute path.
GetTree().GetRoot(); // Access via scene main loop.
GetNode("/root"); // Access via absolute path.

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

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

场景树

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

这意味着, 如之前教程中所述, 它将获得 _enter_tree()_ready() 回调函数(以及 _exit_tree()).

../../_images/activescene.png

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

树顺序

Godot中的大多数节点操作(例如绘制2D, 处理, 或获取通知)均以树顺序完成. 这意味着在树顺序中具有较低排名的父级和同胞将在当前节点之前得到通知.

../../_images/toptobottom.png

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

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

  2. 该场景的根节点(只有一个根, 还记得吗?)作为子级被添加至 "根" 视图(来自场景树), 或添加至其子孙节点.

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

  4. 当节点及其所有子节点都位于活动场景中时, 为方便起见, 会提供一个额外的通知 ready (GDScript中的 _ready() 回调函数).

  5. 删除场景(或场景的一部分)后, 它们将按照从下到上的顺序收到 退出场景(exit scene) 通知(GDScript中的 _exit_tree() 回调函数)

更改当前场景

加载场景后, 通常需要更改该场景为另一个场景. 简单的方法是使用 SceneTree.change_scene() 函数:

func _my_level_was_completed():
    get_tree().change_scene("res://levels/level2.tscn")
public void _MyLevelWasCompleted()
{
    GetTree().ChangeScene("res://levels/level2.tscn");
}

除了使用文件路径之外, 还可以使用等效的函数 SceneTree.change_scene_to(PackedScene scene) 来使用现成的 PackedScene 资源:

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

func _my_level_was_completed():
    get_tree().change_scene_to(next_scene)
public void _MyLevelWasCompleted()
{
    var nextScene = (PackedScene)ResourceLoader.Load("res://levels/level2.tscn");
    GetTree().ChangeSceneTo(nextScene);
}

这些是切换场景的快速而有用的方法, 但缺点是游戏将停止直到新场景被加载并运行. 在游戏开发的某个时刻, 最好使用进度条, 动画化的指示器或线程(背景)加载来创建适当的加载画面. 必须使用自动加载功能(请参阅下一章)和 后台加载 手动完成此操作.