Godot Engine Walk-through | Scene Tree與物件生命週期

 前情提要

畢業工作與國軍Online

因為人生的許多理由,諸如學分、金錢、正職工作、抑或是懶散,使我這陣子無法繼續進行遊戲開發的副業活動,距離上次發文也有了兩年之久,不過在經過一段工作時間的沉澱與磨練後,也讓我有能力去閱讀與理解更多東西,像是程式整體架構問題,以及一個團隊如何去分工開發一個系統下的子系統。

另一個想法是,在工作一段時間後,認為用高階語言/別人做好的東西做事很碼農的感覺,後來想想其實遊戲引擎本身也是這樣的一個東西,額葉情不自禁的開始為自己辯護: 人的運算能力跟記憶體就這麼多,負責底層的就去做底層的事情、負責面向商業應用的就去做高階的事情,好像也無所謂碼不碼農,只是按個人觀感經驗來說,高階的事情好像真的門檻也較低,畢竟高階語言在光譜上就比較接近英文了,英文可是人在說的,越低階越像機器,能看得懂機器說的話估計也不是什麼容易社會化的動物是個人才。




另外很不幸的,四個月義務役徵集令在二月底的時候無情的射向我的膝蓋,滿腔熱血的大鳥應聲倒地,從此小舟逝,江海寄餘生。。。
其實也沒有,我在高雄二階段的日子開始以程式公差的名義坐進了辦公室,用著不能上網的電腦打著ES6以前的javascript,慢慢刻出一個像是以拉的線段編輯器,打著打著好像也把我的心境打回從前的自己,那個天真的我自己,反正在這把錢燒進無趣又漫長的時光裡,不如就來繼續研讀godot engine吧。

注意: 本篇心得文使用Godot 3.2+



Walk-through Godot: Scene Tree與物件生命週期

Scene Tree是引擎核心

遊戲執行期間, 我們將從主場景作為遊戲的進入點,中途間可以再經由程式控制來切換成其他場景,這個主場景可以由Project Settings中的general頁籤-->application-->run-->main scene來指定,一個遊戲必須要有這個主場景才能執行,就像是一支C++程式也要有一個main function來作為entry point,那是一切的起點。

不管是不是主場景,或是其他的場景,這些場景都必須在引擎內部內被正確載入,以及釋放。場景在抽象上是由很多物件所構成的樹狀資料結構,這些物件在Godot裡面我們稱之為Node,我們可以在scene panel上看到一層層像是資料夾結構的Nodes,整個場景則稱之為Scene Tree。

而場景載入與釋放的動作也是依循某一個特定順序來遍歷這些Node,要了解遊戲是如何在Godot運作,我們必須仔細檢視這一個美妙的過程。Scene Tree是引擎的核心。


場景系統的角色拆分

為了正確解釋這個過程,首先我們說明一些重要角色,你可以在這裡看到類似的解釋。
  • MainLoop
    在Godot的source code裡,各個平台上都會有一個作業系統的類別(OS),作為一個使整支程式運作的代理者,這個類別必需要有一個遊戲迴圈的實體(MainLoop),來提供當遊戲在運行時,他該怎麼跑、怎麼處理輸入資訊、怎麼執行遊戲設計師所提供的邏輯...等等的實作。
  • SceneTree
    Godot裡面預設提供的一種MainLoop實作,所以引擎使用者不必再規劃自己的迴圈邏輯。
    除了在MainLoop講到的功能,他可以控管整個場景的運作(變換場景、刪除、群組控管),並且擁有整棵場景樹的root:viewport,這表示可以由他來存取到場景中任何一個Node。

    注意:這跟上一小節提到的"Scene Tree"有本質上的差異,這裡"SceneTree"指的是引擎裡面的一個class,是具體的主迴圈實作,上面的Scene Tree則是代表整個場景樹,從root(root viewport)到場景nodes到末端node,在抽象意義上就是場景的意思。
  • Root Viewport
    目前而言,遊戲幾乎都是以一個2D平面圖像作為最終呈現,當引擎計算眾多邏輯後,他將把結果渲染至一個最後的視圖。這個root viewport則是當作"其他會渲染出東西之節點"的載體,所有人都在他底下,集合起來可以產生這一個最終視圖。在scene panel中是看不到這個root viewport的存在的,但他會是所有場景的root
  • Scene Tree
    如前兩點的小比較所述,它代表的是整個場景,當node進入場景時,我們就說node接上(enter)了這個scene tree,他將擁有引擎設計者所規範的生命週期,因為main loop將會遞迴的去處理這些node的事件呼叫(callbacks)和信號(notification/signal),直到他被刪除釋放,遊戲狀態和邏輯也都在這個流程做處理。


場景運作流程及物件生命週期

延續上面小節的最後一點,究竟這些node是如何在scene tree中被處理的呢? 

場景及物件都是資料,所以他們都會經歷初始化、被加入scene tree、刪除及離開scene tree,由於他們都繼承Node及Object,因此在特定事件發生時,會接收到內建的通知(notification)、以及依序呼叫在script中實作的callback function,通常notification較少用,多數時候只要寫callback function即可達到大部分需求。

在GDScript中,常用的callback function有
  •  _init()
  • _enter_tree()
  • _ready()
  • _process(delta)
  • _physics_process()
  • _input(event)
  • _unhandled_input(event)
  • _exit_tree()

當物件初始化完成會呼叫_init(),接著會依深度優先的順序來進入scene tree,一旦進入scene tree,_enter_tree()會被呼叫。

當一個node自己以及所有在他之下的節點(整個subtree)都進入了scene tree,那個node的_ready()會被呼叫,表示這個節點已經完全進入場景的控制,可以由該node存取到任何sub node。

_process(delta)會在main loop的process階段不斷地被呼叫,以一個能多快就多快的速度,delta即代表距離上次呼叫process經過了多少秒,可用來調整與速度有關的參數。而_physics_process()與_process()類似,只是速度為恆定值(預設每秒60次),與物理有關的操作通常寫在這裡。_input(event)和_unhandled_input(event)則是負責處理使用者輸入,事件由子節點向上傳遞,先經過_input(event)處理,最後沒有被處理到的輸入事件則會交由_unhandled_input處理,關於input的細節請看這裡

當node被刪除,所有在他底下的sub nodes也會被刪除,並以深度優先的反順序刪除,也就是末端node先被刪除完後,才會刪除其parent,刪除之後將離開scene tree,其_exit_tree會被呼叫。


示範專案

我們接著來研究一個示範虛擬場景。
可以從這裡下載場景專案
https://github.com/cccccroge/nodes_callback_order


場景包含一組互動UI: OptionButton、RichTextLabel,以及示範的scene: Scene,其中包含如2D editor所示的節點順序,場景有A tree和 B tree,A node底下依序有C、D,B node有一個子節點E node,E node底下依序有F、G、H。

運行遊戲後,可以點選下拉選單來選取欲觀察的callback呼叫順序:
  1. _enter_tree
    以深度優先順序,因此呼叫順序為A->C->D->B->E->F->G->H
  2. _ready
    只要該節點之sub tree都進入場景就會呼叫,因此呼叫順序為C->D->A->F->G->H->E->B
  3. _exit_tree
    與_enter_tree相反,呼叫順序為H->G->F->E->B->D->C->A
結果與上一小節提到的node生命週期相符!


示範專案裡的script

如果有興趣這個小專案是如何用GDScript實作的,可以自己開啟script editor查看,小弟有機會再補上說明。

留言

張貼留言

熱門文章