第61節 - Unity: Life Cycle
這一節我會介紹Unity的生命週期(Life Cycle)。
主要會介紹以下幾種方法,如下圖:
1: Reset()
2: Awake() & Start()
3: FixedUpdate(), Update() & LateUpdate()
4: OnRenderObject()

- Reset()。
- Awake()。
- Start()。
- FixedUpdate()。
- Update()。
- LateUpdate()。
- OnRenderObject()。
1.0 Reset()
首先Reset()是Unity Life Cycle的第一個方法,當你在Unity Inspector內加入(Add Component) Reset() Script或點擊Reset按鈕,Reset()方法就會執行。Reset()方法僅會在Unity Inspector內執行。
Reset()方法主要在開始時用來重設默認值。

- 建立一個新Project。
- 在Hierarchy區域內建立一個ResetTest GameObject。
- 在Assets區域內建立一個MyReset C# Script。

- 在Reset()內加入Debug.Log();。
1.1 加入(Add Component) Reset() Script

- 把C# Script拖放到ResetTest內。
- 執行程式,Reset()方法就會被執行。
1.2 點擊Reset按鈕

- 點擊Reset按鈕。
- 執行程式,Reset()方法就會被執行。
2.0 Awake() & Start()
2.1 Script Execution Order

- 建立一個新Project。
- 建立三個GameObject。
- 建立三個C# Script,然後把C# Script拖放到對應的GameObject內。

- 在C# ScriptA內建立Awake()和Start()方法。
- 在C# ScriptB內建立Awake()和Start()方法。
- 在C# ScriptC內建立Awake()和Start()方法。

- 執行程式,三個Awake()方法會先被執行。
- 然後三個Start()方法。

- 選擇Edit>Project Settings>Script Execution Order。
- 選擇次序(ScriptA>ScriptB>ScriptC)。
- Awake()方法就會順序執行。
- Start()方法就會順序執行。
2.2 Create Object in Awake() or in Start()
我們在之前介紹過,所有Awake()方法會順序執行,然後到Start()方法,如下圖:

- 所有Awake()方法會順序執行,然後到Start()方法。
假設我們在C# ScriptA的Awake()方法內建立一個新物件A1,再在C# ScriptB的Awake()方法內建立一個新物件B1,最後,在C# ScriptC的Awake()方法內就可以調用物件A1和B1,如下圖:

- 在C# ScriptA的Awake()方法內建立一個新物件A1
- 再在C# ScriptB的Awake()方法內建立一個新物件B1。
- 最後,在C# ScriptC的Awake()方法內就可以調用物件A1和B1。
但是,假設我們在C# ScriptB的Start()方法內建立一個新物件B1,在C# ScriptC的Awake()方法內調用物件A1和B1,因為物件B1還沒有產生,程式就會出現錯誤,如下圖:

- 在C# ScriptB的Start()方法內建立一個新物件B1。
- 在C# ScriptC的Awake()方法內調用物件A1和B1,因為物件B1還沒有產生,程式就會出現錯誤。
2.3 Case Study No.1
在Hierarchy內建立一個GameObject GameOb jectA。

- 建立一個新Project。
- 建立一個新GameObject GameObjectA。
- 把C# ScriptA拖放到GameObjectA內。
在Hierarchy內建立一個GameObject GameObjectC。
注意: GameObject GameObjectB會在ScriptB內用程式產生。

- 建立一個新Project。
- 建立一個新GameObject GameObjectC。
- 把C# ScriptB和ScriptC拖放到GameObjectC內。

- 建立Awake()和Start()方法。
- 建立一個MethodA()方法。

- 建立一個GameObject Reference tempGameObjectB。
- 在Awake()方法內,
i) 建立tempGameObjectB的物件,GameObject tempGameObjectB會在ScriptB內用程式產生。。
ii) 加上tag = "B"作標籤。
iii) 再加上ScriptA script。
在ScriptA內建立一個MethodA()方法。

- 建立一個GameObject Reference TempGameObjectC。
- 建立TempGameObjectC的物件,這次指向TempGameObjectB物件。
- 再加上ScriptA script,再執行MethodA()方法。
在ScriptB內用程式建立一個。

- 選擇Edit>Project Settings>Script Execution Order,選擇次序(ScriptA>ScriptB>ScriptC)。

- 成功執行程式。
2.4 Case Study No.2

- 把原先在Awake()方法內的程式碼轉移到Start()方法內。

- "Object reference not set to an instance of"的錯誤信息,就會顯示出來,意思是有reference TempGameObjectC,但沒有指向物件。
2.5 Conclusion of Awake() and Start()
- 把所有初始化(Initialization)和產生物件(Create Object)放在Awake()方法內執行。就好像Java的Constructor一樣(e.g. Class A a = New A();)。
- 把其他程式部分放在放在Start()方法內執行。
3.0 FixedUpdate(), Update() & LaterUpdate()
3.1 Display Frame Rate - Time.deltaTime
以下會介紹FixedUpdate()、Update()和LaterUpdate()的用法:

- 建立一個新Project。
- 建立一個新C# Script MyUpdate。
- 建立FixedUpdate(),Update()和LateUpdate()三個方法。
- 選擇Main Camera。
- 把C# Script MyUpdate拖放到Main Camera內。

- 首先會執行FixedUpdate()方法,Frame Rate是固定的frame Rate = 0.02s。
- 然後執行Update()方法,Frame Rate不是固定的。
- 最後執行LateUpdate()方法,Frame Rate不是固定的。
3.2 FixerdUpdate() & Update()
在正常情況下,FixedUpdate()方法的Frame Rate是固定的,而Update()方法的Frame Rate不是固定的。
注意: Unity預設Frame Rate = 50 FPS = 1/50 = 0.02s,以下例子假設Frame Rate = 10 FPS作介紹,
如下圖:

- Unity預設Frame Rate = 50 FPS = 1/50 = 0.02s,以下例子假設Frame Rate = 10 FPS作介紹。
當你的智能電話因為某些問題,而遇到低效能時,Update()方法的Frame Rate除了不是固定的外,還會減少, 如下圖:

- 以上例子,FixedUpdate()方法就會執行兩次,而Update()方法才會執行大約一次。
以下用"撞球"作解釋 - FixedUpdate():

- 如果Sliding Block沒有觸碰到球,球就會直去。
- 如果Sliding Block向上移動而在Frame = 6觸碰到球,球就會反彈,改變方向。
以下用"撞球"作解釋 - Update():

- 當你的智能電話因為些問題,而遇到低效能時,Update()方法的Frame Rate除了不是固定的外,還會減少,就算Sliding Block向上移動,也有可能沒有觸碰到球,引致執行結果錯誤。
3.3 Change of FixedUpdate() Farme Rate
最後如果大家有須要更改FixedUpdate()的Frame Rate預設值,可以根據以不方法:

- 選擇Edit>Project Settings>Time。
- 更改Fixed Timestep的值。
3.4 Latepdate()
LateUpdate()主要有以下兩個特性:
1) 與Update()方法的Frame Rate一樣,都是不固定的。
2) 執行完Update()方法才會執行LateUpdate()方法。
LateUpdate()的好處:
1) 這可用於確定Script是順序執行。例如: 當"Player"物件在Update里移動時,跟隨"Player"物件的相機物件可以在LateUpdate內執行。這樣就不會出現相機行得快過"Player"物件的情況。
3.5 Script at the End of Update() VS Script in LateUpdate()
大家可能會問把Camera物件放在Update()方法的尾部與放在LateUpdate()內有甚麼分別?
- 通常分別不大,但是要視乎程式編寫的結構或內容,如果Camera物件沒有影響其他程式部分,Camera物件也可以放在Update()方法的尾部。
- 不過一隻遊戲程式通常會由很多人負責,如果程式人員已把遊戲程式完成,再把它交給其他人加入Camera物件,為了確保程式執行無誤,把Camera物件放在LateUpdate()方法內,就可以很定Camera物件會在最後才執行。
3.6 Conclusion of FixedUpdated(), Updated() and LateUpdate
- FixedUpdate()方法與Physics Engine一樣都會用上固定Frame Rate,所以,所有Physics(2D/3D)的Rigidbody都須要放在這裡。
- 其他非物理(Non-Physics(2D/3D))的程式,例如: Sprite Animation和Particle System(如果沒有Physics部分)就可以放在Update()方法內。
注意: 你也可以把Non-Physics的程式放在FixedUpdate()方法內,不過因為FixedUpdate()方法執行的次數會多過Update()方法,所以有可能導致裝置效能下降。 - LateUpdate()方法會在Update()方法執行後才執行,所以我們可以把跟隨"Player"物件的相機(Camera)物件放在這裡。
4.0 OnRenderObject()
以下會介紹OnRenderObject()的用法,OnRenderObject()方法會在所有FixedUpdate()、Update()和LateUpdate()方法完成後,Camera物件把場景渲染(Render)之後執行:

- 建立一個新Project。
- 建立一個新GameObject GameObjectA。
- 建立一個新C# Script MyScript。
- 建立OnRenderObject()方法。
- 選擇Main Camera,把C# Script MyScript拖放到Main Camera內。

- Awake()方法只執行一次。
- Start()方法只執行一次。
- 之前提過,FixedUpdate()方法可能執行幾次,之後到Update()和LateUpdate()方法。
- 最後執行OnRenderObject()方法一次。
4.1 Conclusion of OnRenderObject
- OnRenderObject()方法會在所有FixedUpdate()、Update()和LateUpdate()方法完成後,Camera物件把場景渲染(Render)之後執行。
- 則當FixedUpdate()、Update()和LateUpdate()方法完成後,每一格動畫不是在Update()方法內渲染,而是在OnRenderObject()方法內渲染,再把每一格動畫顯示在屏幕上。
- OnRenderObject()方法只是Unity Render的其中一個方法,其他包括OnWillRenderObject()、OnPreCull和OnRenderImage()方法等等,這裡不作介紹。