Unity MonoBehaviour 事件函式的生命週期

-前言-

在 Unity 使用 Script 控制場經中物件時, 該 Script 類別需要繼承 MonoBehaviour 類別. MonoBehaviour 想必都不陌生, 它提供許多事件函式(event function), 並且遵循著 lifecycle 執行事件函式. 事件函式幫助開發者能更簡便/更直覺的監聽物件變化或做出對應處理, 儘管方便, 但若不能熟悉善用它, 反倒會被突然冒出來的 bug 嚇的措手不及.

-主旨-

介紹 lifecycle 中經常使用的事件函式, 可依行為用途分成:

  • 初始化: Awake, OnEnable, Start
  • 更新: FixedUpdate, Update, LateUpdate
  • 渲染: OnPreCull, OnBecameVisible/OnBecameInvisible, OnWillRenderObject, OnPreRender, OnRenderObject, OnPostRender, OnRenderImage, OnGUI
  • 協程: yield …
  • 暫停/摧毀: OnApplicationPause, OnApplicationQuit, OnDisable, OnDestroy

MonoBehaviour Script lifecycle

完整的 lifecycle 可至 Unity 官網 Order of Execution for Event Functions 詳閱.

初始化 (Awake, OnEnable, Start)

當物件一被加入場景時 (實體化 Prefab, 物件創建, 或剛載入場景物建) 就會進入初始化階段.

Awake:
這個函式是最先被呼叫的, 而且只會被呼叫一次. 但若物件一開始載入時未被啟用 (inactive), Awake 便不會被呼叫, 直到該物件啟用 (active) 才被呼叫. Awake被呼叫代表 Script 已經被加載好了, 通常用於初始化變數遊戲狀態.

OnEnable:
只有當物件是啟用 (active), Script 被激活 (active) 時才會呼叫. 代表著MonoBehaviour 已經實體化並附加至物件上, 正要運行. 因此, 如果物件或 Script被開開關關多次, OnEnable 也會被呼叫很多次.

Start:
當 Script 要運行首幀的邏輯/渲染之前會呼叫. 與 Awake 相似都只會被呼叫一次, 但差別是 Awake 只要物件被啟用 (active) , 而 Start 必須滿足物件 active 與 Script active, 其實也就是 OnEnable 之後.

更新 (FixedUpdate, Update, LateUpdate)

lifecycle 中有幾個事件函式能追蹤遊戲邏輯、玩家操作、動畫…等. 開發上, 很常見到在 Update 函式執行大多數的遊戲邏輯.

Update:
每幀畫面更新時都會被呼叫. 受到當前繪製畫面的影響, 繪製的時間不固定, 所以更新一次所需的時間也不盡相同.

FixedUpdate:
與 Update 的區別是, FixedUpdate 固定時間會被呼叫. 若幀頻 (每秒更新畫面的次數) 高的話, 固定時間內 Update 次數可能比 FixedUpdate 次數多; 反之, 幀頻低的情況, FixedUpdate 次數會多於 Update 次數. FixedUpdate 不被幀畫面更新的時間影響, 相較於 Update, 更適合用於處理物理計算的邏輯.

LateUpdate:
每幀畫面更新時, 在 Update 之後被呼叫, 屬於遊戲邏輯完成計算後的處理函式. 常見用法就是第三人稱跟隨相機, 在確保角色已經移動/旋轉計算完畢後, 調整相機的位置與方向.

: 物理運算相關的事件函式 (像是動畫、碰撞事件) 會早於遊戲邏輯的事件函式.

渲染 (OnPreCull, OnBecameVisible/OnBecameInvisible, OnWillRenderObject, OnPreRender, OnRenderObject, OnPostRender, OnRenderImage, OnGUI)

渲染的流程大致上為: 相機剔除 (Camera Culling)、繪製場景 (Rendering)、後製處理 (Post Processing) 以及 UI 繪製.

相機剔除 (Camera Culling)

OnWillRenderObject:
相機剔除的一開始, 每個相機都會對場景中所有已啟用 (active) 的物件呼叫此函數. 物件未被啟用或是 Script 未被激活 (active) 的情況下, OnWillRenderObject不會被調用.

OnPreCull:
當相機開始進行剔除視角外物件前, 由相機呼叫 OnPreCull.

OnBecameVisible:
當某物件對任何相機轉變為可見時被呼叫.

OnBecameInvisible:
當某物件對任何相機轉變為不可見時被呼叫.

繪製場景 (Rendering)

OnPreRender:
相機開始渲染場景之前會呼叫OnPreRender.

OnRenderObject:
當所有的常規場景渲染完成後, 每個包含 Script 的物件 (已啟用) 都會呼叫OnRenderObject, 不論是否被相機剔除. 能夠在這個函式中客製化幾何體, 像是能使用 Graphics.DrawMeshNow 根據不同相機更換渲染的 Mesh.

後製處理 (Post Processing)

OnPostRender:
當相機渲染所有物體完成後, 被相機呼叫. 如果想在相機渲染後且 UI 渲染前做些事情的話, 適合寫在 OnPostRender 函式.

OnRenderImage:
當場景渲染完成後被呼叫. 能在這個函式內進行後製來修改最終的圖像, 更多請參考Post-processing Effects.

UI 繪製

OnGUI:
每幀畫面更新時會被呼叫很多次, 以回應GUI事件. 先處理佈局 (Layout) 和重繪(Repaint) 事件, 接下來是鼠標/鍵盤的輸入事件.

協程函式: yield …

呼叫協程函式會暫停當下的主線程而開啟另一個協程, 協程中直到 yield return 才會返回主線程. 協程函式為的是做出非同步的效果, 看起來很像有多個工作在執行, 但其實只是將工作快速切換而以. 一般的協程函式在 Update 函式完成後執行, 但也有在 FixedUpdate 之後的協程函式, 以及幀畫面渲染後的協程函式.

Update 後

yield:
在次幀所有的Update 函式執行完成後繼續協程內容.

yield WaitForSeconds:
在指定的延遲時間之後, 並在當前幀的所有 Update 函式執行完成後繼續協程內容.

yield WWW:
在 WWW 下載完成後繼續協程內容. 可以用來下載遠端內容, 像 asset bundle.

yield StartCoroutine:
再開啟一個協程, 並等待新的協成完成後繼續原協程內容.

FixedUpdate後

yield WaitForFixedUpdate:
在所有 FixedUpdate 函式都被執行完成後繼續協程內容.

幀畫面渲染後

yield WaitForEndOfFrame:
在所有相機與 GUI 完成渲染後 (即顯示於螢幕之前) 繼續協程內容. 可以在WaitForEndOfFrame 階段讀取到螢幕 Texture, 並製作成影像截圖發送/儲存在其他地方.

暫停/摧毀 (OnApplicationPause, OnApplicationQuit, OnDisable, OnDestroy)

OnApplicationPause:
當系統偵測到暫停事件 (如: 手機切換app), 並等到最後一幀被繪製完成後會呼叫, 同時再插入/渲染一幀. 通常用於顯示暫停畫面.

OnApplicationQuit:
退出遊戲應用程式之前 (物件尚未被摧毀), 所有遊戲物件 (game object) 都會呼叫此函數.

OnDisable:
當物件變成未啟用或是 Script 變成非激活狀態 (inactive) 會被呼叫.

OnDestroy:
在物件存在的最後一幀且完成所有更新操作後執行此函式. 物件的摧毀可能來自於場景的銷毀會或者由其他地方呼叫了 Object.Destroy.

: 物件被摧毀前, 會先呼叫 OnDisable 再呼叫 OnDestroy.

-總結-

對 MonoBehaviour 物件有基本認識後, 後續開發功能會更有概念. 目前只討論單一個 MonoBehaviour 物件的 lifecycle, 那如果有多個物件呢? 這些物件又是依照哪些原則決定 Update 的順序呢? 有機會的話, 後續文章會再做討論.

謝謝觀看:D

-參考-

發表留言