[Unity] 從零開始的學習心得 #2 – Unity 的基本組成

unity-learning-totally-review-2

此篇文章是《從零開始的 Unity 學習心得》系列的第二篇文章,這個系列是記錄我對 Unity 的開發及學習心得,包含我用何種方式去理解 Unity 的運作,並且在我所知的範圍內盡可能說明相關原理與開發要點。完整系列的目錄可以參考:

這次主要重點放在 場景、物件、組件 的關係,以及撰寫 MonoBehaiour 腳本時可能會用上的一些經驗談。

Unity 的跨平台特性

Unity 作為商業引擎的一個特色,就是它並不止針對一個系統平台去設計,一個 Unity 專案可以在極小的變動下,就分別發布到電腦 (Windows、OSX)、行動裝置 (Android、iOS),甚至掌上遊樂機 (PSV)、家用主機 (XBox) 都在 Unity 的守備範圍內。雖然做不到不改動就可以無痛轉換,但跨平台算是 Unity 的一大特色。

簡單解釋這個跨平台特性的原理,可以把 Unity 輸出的遊戲可分成兩個部分:Unity Player 以及 Unity Assets。

就像播放器之於影片一般,在不同的平台上用上的不同的播放器,可以播放同一段影片,呈現出基本相同的內容;Unity 在輸出專案時,會針對平台提供一個 Unity Player 作為啟動器,並將專案內容打包成一份或數份 Unity 專屬的 Assets 檔案,作為播放的內容。

所以將 Unity,或者說所有的遊戲當成可以互動的影片,是很直觀的想像。

Unity 的基本組成 - 場景、物件、組件

初接觸 Unity 開發,有幾個項目是會早期會先認識到的:

  • 場景(Scene)
  • 物件(GameObject)
  • 組件(Component)

場景(Scene)

在 Unity 遊戲執行時,都會有個運作中的場景。在編輯器之中,可以看到遊戲的3D模型、攝影機與燈光等物件會被佈置在場景中,場景(Scene) 就像是影像拍攝的片場一般,是作為 Unity 運作的根基。

場景可以被儲存為一個檔案,使遊戲進行能在數個場景間切換,也因此在遊戲打包輸出時也需要指定一個初始場景。而 Unity 遊戲被啟動時,會先載入並啟動初始場景的運作,遊戲迴圈(Game Loop) 也會在場景完成載入後開始生效,如果沒有場景的存在,接下來提到的 物件(GameObject) 及 組件(Component) 便沒辦法加入遊戲迴圈中發生作用。

物件(GameObject)

物件是在編輯器中,可以被看見與操作的主要實體。在場景中佈置物件,就像是在佈置舞台劇的道具一般,不同的物件會在場景中發揮不同的作用:可以打光的 Light物件、形成實體的 Mesh物件、將場景拍攝為遊戲畫面的 Camera物件。

各式各樣的的功能物件,我們都可以通一稱為 GameObject,雖然很難表達這些 GameObject 在軟體原理上的地位,但可以把它想成"事物的載體",用來表達一個遊戲中的"存在"之標的。一個最單純的 GameObject,就只是一個遊戲中的"存在",它會在場景中有個位置,成為 Unity Game Loop 的一部分,但不代表實際的作用,實際作用的部分要由 組件(Component) 來說明。

組件(Component)

我們在編輯器選取了任何一個 GameObject,就會在 Inspector 視窗 中看到一連串的欄位跟資訊,每個 GameObject 擁有的欄位跟資訊有些相同,又有些不同。

這些是稱為 組件(Component) 的重要元素,用哲學一點的話來說,是它們為 GameObject 這"存在"賦予了"功能與意義"。有些組件是每個物件都有的,如 Transform;有些組件是賦予物件特殊地位的,如 Camera。我們可以從選單創建各式各樣的物件,而這些物件之所以不同,就是因為它們一開始所擁有的組件不同。

用比較虛幻飄渺的用詞來比喻,場景代表了虛擬的"絕對空間",物件代表了空間中的"存在",組件則賦予了各自不同的"作用與身份"。

組件腳本的基本類別 - MonoBehaviour

雖然 Unity 已經內建了超多 Component 可以使用,但要靠這些 Component 做出所有期望的遊戲規則與設計還是有點強人所難,所以一般開發 Unity 遊戲都會需要自行撰寫程式碼腳本,設計自己所需要的 Component。

自己設計 Component 最主要的目的是為了自訂遊戲的邏輯,屬於 Game Loop 中 Update 的一環,也是不同遊戲之間差別最巨大的部分;另外也有可能是為了為了將 Unity 提供的 Input Trigger (玩家輸入的觸發器) 銜接到自訂的 Update 邏輯,或者針對 Unity 的 Rendering 階段進行調整或補充,呈現特別的特效等。

在上一篇文章說明 Game Loop 時有提到,Unity 就像在引擎的運作流程中留下許多空格,交由開發只去填入希望的邏輯,或者連接對應的玩皆輸入,變成期望的遊戲成品。為了能適當且合理的使用 Unity MonoBehaviour 提供的空格欄位 - Unity Event Function,閱讀文件並了解相關的運作順序有著相當大的意義。

說明 Update 流程的官方文件:https://docs.unity3d.com/Manual/ExecutionOrder.html

可以稱為百大錯誤之首的 Unity 執行意外:Null Reference Exception,除了場景設置不完善,最普遍的發生原因便是因為忽略了 MonoBehaviour 中各個事件 (Event) 的執行時機,在 runtime 時調用了還未準備完成的對象或資源。

以最常使用的 Awake、Start、Update 來說明:

  • 對單一組件來說 Awake 必定先於 Start,然後是 Update。
  • 場景中的預先擺設物件之間, Awake 必定會在任何 Start 之前完成;也就是 Awake 會是整個場景執行完畢,然後才輪到任何 Start。
  • 用腳本 Instantiate Prefab 物件不會依照上一點邏輯。

以這兩點來說,便可以知道 Awake 要避免去呼叫不同的物件資源,避開物件之間的互動;而 Start 可以進行物件之間的互動,但互動雙方要將事前準備工作在 Awake 全部完成。

當使用到的 Unity Event Function 越多,這些內建於 Unity 的細節便有可能使你開發的遊戲產生問題。所以我會告訴一些初心者:

Unity 沒有讀心晶片,只有你先去理解它的細節,並適當地傳達你的邏輯(寫好腳本,寫對程式),Unity 才會回應你的期待。