Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Using SceneTree

前言

到目前位置的教學都是圍繞著節點的概念延伸的。場景是節點的集合。而場景會在進入 場景樹 後變為有效場景 (Active Scene)。

MainLoop

Godot 內部以下列方式運作。有一個 OS 類別,是在開始執行的時候唯一的實體。之後便會加載所有的驅動程式、伺服器、腳本語言、場景系統…等。

當初始化完成後, OS 就必須要提供 MainLoop 來執行。到這裡為止,都是屬於內部運作(若你有興趣瞭解內部如何運作,可以閱讀原始碼的 。main/main.cpp 檔案)。

使用者程式,或稱為遊戲,會在 MainLoop 內開始執行。這個類別有幾個方法,可以用來初始化、閒置(影格同步回呼)、固定(物理同步回呼)、與輸入。同樣地,這些都屬於低階的功能,而在 Godot 內製作遊戲的時候撰寫自己的 MainLoop 沒什麼意義。

SceneTree

還有一種可以說明 Godot 運作方式的方法就是將 Godot 看作是有低級中間件 (Middleware) 的高級遊戲引擎。

場景系統是遊戲引擎,而 OS 與伺服器則為低階 API。

場景系統提供了自己的 MainLoop 給 OS,即為 SceneTree 。這個類別會在場景執行時自動實體化,所以不需要額外做什麼。

而重要的是要知道有這個類別存在,因為他有一些重要的用途:

  • 包含了根 Viewport ,也就是場景第一次打開並進入 場景樹 時會被加為子節點的地方(詳細見下方)。

  • 包含了有關群組的資訊,且能夠呼叫群組中的所有節點或取得群組中節點的列表。

  • 包含了一些全域狀態的功能,如設定暫停模式或結束處理程式。

當節點成為場景樹的一部分後,便可以通過呼叫 Node.get_tree() 來取得 SceneTree 單例 (Singleton)。

根檢視區

Viewport 永遠為場景的最上層。在一個節點內,根檢視區可以通過兩種不同方式取得:

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

這個節點包含了主檢視區。所有東西 Viewport 的子節點預設都是繪製在其內部的,因此所有節點的最上層都會是 Viewport 型別,否則就不會顯示任何東西。

雖然場景內也可以建立其他的 Viewport(用於畫面分割效果等),但根 Viewport 永遠都不會是由使用者建立的,而是在 SceneTree 內自動建立的。

場景樹

當節點直接或間接地連接上了根檢視區,就變成了 場景樹 的一部分。

這表示,如之前解釋過的,節點會取得 _enter_tree() 與 _ready() 回呼函式(另外還有 _exit_tree())。

../../_images/activescene.webp

當節點進入 場景樹 便成為了有效節點。這些節點將可以存取所有節點需要處理的資源,如取得輸入、顯示 2D 或 3D 畫面、接受與送出通知、播放聲音…等。當節點從 場景樹 中移除後便失去了這些能力。

樹順序

Most node operations in Godot, such as drawing 2D, processing, or getting notifications are done in tree order, or top to bottom as seen in the editor (also known as pre-order traversal):

../../_images/toptobottom.webp

For example, the top node in a scene has its _process() function called first, then the node below it has its _process() function called, then the node below that and so on.

An important exception is the _ready() function: each parent node has its _ready() function called only after all its child nodes have their _ready() functions called, so that the parent knows its children are completely ready to be accessed. This is also known as post-order traversal. In the above image, NameLabel would be notified first (but only after its children, if it had any!), followed by Name, etc., and Panel would be notified last.

The order of operations can also be overridden using the process_priority node property. Nodes with a lower number are called first. For example, nodes with the priorities "0, 1, 2, 3" would be called in that order from left to right.

藉由進入 場景樹 來「成為有效狀態」

  1. 某個場景由硬碟載入或由腳本建立。

  2. 該場景的根節點(只有一個根節點,還記得嗎?)不是被加到(SceneTree 內的)「root」檢視區作為子節點,就是被加到 root 檢視區的子節點或次級子節點當中。

  3. Every node of the newly added scene will receive the "enter_tree" notification ( _enter_tree() callback in GDScript) in top-to-bottom order (pre-order traversal).

  4. Every node will receive the "ready" notification ( _ready() callback in GDScript) for convenience, once all its children have received the "ready" notification (post-order traversal).

  5. When a scene (or part of it) is removed, they receive the "exit scene" notification ( _exit_tree() callback in GDScript) in bottom-to-top order (the exact reverse of top-to-bottom order).

修改目前場景

場景載入後,通常會有需要更改目前的場景成另一個場景。最簡單的方法是使用 SceneTree.change_scene() 函式:

func _my_level_was_completed():
    get_tree().change_scene_to_file("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_packed(next_scene)

這幾個切換場景的方法比較快速且實用,但缺點是遊戲在新場景載入並開始執行之前都會停止。遊戲開發到某個程度的時候最高做個合適的載入畫面,並配合使用進度條、動畫指示器、或執行緒(背景)載入。這些東西必須手動使用 Autoload(詳見下一章)或是 後臺載入