Autoload v.s. 一般節點

Godot 提供了一個能自動在專案根節點上載入的功能。這些自動載入的節點能夠在所有地方存取,並且能充當單例: 單例(Autoload) 的角色。這些自動在如的節點不會在從程式碼中用 SceneTree.change_scene 時被釋放。

在本篇指南中,我們將學習如何使用 Autoload 功能,以及可以避免使用 Autoload 的一些技巧。

音訊截斷問題

其他的引擎鼓勵使用 Manager 類別,即一種用來管理許多全域存取物件功能的單例。託節點樹與訊號的福,Godot 中提供了許多方法來避免全域狀態。

舉例來說,假設我們正在做平台遊戲,並且想在蒐集到金幣時播放音效。有可以播放音效的節點: AudioStreamPlayer 。但如果我們在 AudioStreamPlayer 正在播放聲音的時候呼叫,新的音效就會中斷之前播放的音效。

有一種解決方法是撰寫一個全域且自動載入的音訊管理員類別。這個類別要產生一個 AudioStreamPlayer 節點集區,然後在遇到音效播放要求的時候照順序讓每一個 AudioStreamPlayer 來播放。假設我們管這個類別叫 Sound ,則我們在專案任何一個地方都可以通過呼叫 Sound.play("coin_pickup.ogg") 來使用。這樣一來便在短期內解決了問題,但接著會引發更多的問題:

  1. 全域狀態 :現在,有一個物件必須要負責所有物件的資料。若 Sound 類別發生錯誤,或是沒有可用的 AudioStreamPlayer,則所有呼叫 Sound 的節點都會壞掉。

  2. 全域存取 :現在所有物件都能從任何地方呼叫 Sound.play(sound_path) ,所以要找到 Bug 的原因變得更加困難了。

  3. 全域資源分配 :現在從一開始就有一個 AudioStreamPlayer 節點集區,如果數量太少的話便會遇到 Bug,而數量太多的話則會佔用過多的記憶體。

備註

有關全域存取,問題在於,在本例中,任何地方的「所有」程式碼都有可能將錯誤的資料傳給 Sound Autoload。因此,發生問題時要尋找的範圍就變成了整個專案。

如果將程式碼保留在場景中,則只有一兩個程式碼可能會與音訊有關。

相較之下,如果每個場景都在自己內部保佑必要數量的 AudioStreamPlayer 的話,則這些所有的問題便解決了:

  1. 每個場景都管理自己的狀態資訊,所以當資料由問題的時候,就只會影響到單一場景。

  2. 每個場景都只存取自己的節點。所以當現在出現 Bug 時,就很容易找出是哪個節點的錯。

  3. 每個場景都只分配到所需數量的資源。

管理共用功能或資料

另一個使用 Autoload 的理由是這樣就可以在不同的場景間重複使用相同的方法或資料。

如果用函式的話,可以建立一個新型的 Node ,然後通過 GDScript 中的 class_name 關鍵字來為個別場景提供功能。

但遇到要處理資料的時候,就必須選擇:

  1. 建立一個新型的 Resource 來共享資料。

  2. 將資料儲存在每個節點都能存取的一個物件當中,比如說使用 owner 屬性來存取場景的根節點。

什麼時候該用 Autoload

自動載入的節點在某些情況下可以簡化程式碼:

  • 靜態資料 :若需要的資料只有一個類別會用到,如資料庫,則用 Autoload 是個不錯的選擇。目前在 Godot 中沒有腳本 API 可以建立與管理靜態資料。

  • 靜態函式 :建立一個函式庫,只回傳數值。

  • 作用域更廣的系統 :若單例管理自己的資訊,且不會存取到其他物件的資料,則建立一個處理廣域資料的系統也不錯。例如,任務或對話系統。

直到 Godot 3.1 版以前,另一個方便的做法是:每個 Autoload 都有一個由 GDScript 以其名稱產生的全域變數,這個全域變數可以用來在專案中的任何腳本呼叫 Autoload。但現在,必須使用 class_name 關鍵字來代替,才能在整個專案中享有型別的自動補全。

備註

Autoload 並不完全同等與單例 (Singleton)。在 Autoload 中沒有方法可以防止再次實體化一個 Autoload 的節點。只能算是一種能不考慮遊戲節點架構以及目前執行的節點是什麼,就讓節點作為場景樹子節點自動載入的方法,例如,按 F6 鍵。

然後,就能取得自動載入的節點,例如有一個叫 Sound 的 Autoload,則可以呼叫 get_node("/root/Sound")