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().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 型別,否則就不會顯示任何東西。

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

場景樹

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

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

../../_images/activescene.png

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

樹順序

在 Godot 中大多數的節點操作,如繪製 2D、處理、或取得通知等都是依樹順序來完成的。這表示在樹狀順序中有較低等級的母節點與同級節點會比目前節點還要晚收到通知。

../../_images/toptobottom.png

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

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

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

  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);
}

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