警告:本篇含有大量不雅字眼。
以圖片的形式(?)
不喜者請自行斟酌喔!
愉快的隨手札記時間!
隨著某獨立遊戲專案的規模越來越大,做為主程式的我也來到需要設計「跨Scene儲存/保留/更新Player資料」功能的時候了。
配合胡亂說・隨便寫大大提供的無縫場景轉換機制教學文,現在我已經有一個簡單的GameSystemManager了。保持靜態的結構如下:
public class GameSystemManager : MonoBehaviour
{
public static GameSystemManager exist;
//this is used to check whether there's already one manager.
private void Awake()
{
//here, do the "only-one" process.
if(exist == null)
{
exist = this;
DontDestroyOnLoad(gameObject);
}
else if(exist != this)
{
//update Game and Scene Data
//.......
//update Game and Scene Data finished. Delete the later one (this)
Destroy(gameObject);
}
}
}
接下來,我要完成「把Player資料存取到GameSystemManager」的功能:
- 基本倉儲:當GSM裡面沒有Player資料時,存入這個Player資料。(用在第一個場景)
- 更新資訊:如果GSM裡面存在同一個Player的資料,更新它。
- 獨一性:類似前面的GSM--當更新完資訊之後,多餘的Player必須
爆炸銷毀。 - 可存取性:規範Manager族群(包含GSM本身)在載入Player資料之後才取用這份資料清單。
當然,這些都是建構在GSM必須預先載入完畢(更新 exist 內容)的前提下。
而精美的Unity官方手冊表示,做為工程師的你有一個現成的 Awake() -> OnEnable() -> Start() 函式順序可以使用。
Unity官方手冊函式執行流程圖。 建議點進網站裡看大圖。 |
於是我們心中就可以勾勒出一份美好的藍圖:
看起來很美好、簡單,對吧?
很可惜,事情總不如想像中那麼順利。
剛寫完之後立刻來跑跑看,卻發現Player仍然跳警告、表示找不到GSM。
然後測試之後發現、原來是調用順序出了問題啊。
每個訊息都是照字面上去呼叫的。 |
若 Scene 同時含有以下GameObject / 腳本:
- Player: 擁有OnEnable()與Start()
- GSM: 擁有Awake()
- LM: 擁有Awake()與Start()
則場景執行腳本的順序可能是:(注意,只是可能)
- Player(2) 的 OnEnable()
- LM、GSM 的 Awake()
- Player(1) 的 OnEnable()
- Player(2)、LM、Player(1)的Start()
所以實測發現:在 Unity 中, Awake()->OnEnable() 的順序並不總是正確。
更精確來說,在同一個Component中、Awake()確實早於OnEnable();然而在多個Component的執行過程中,同個Component的Awake()與OnEnable()是綁在一起執行的。
....真是莫名其妙,那這樣幹嘛分兩個部分?
「您的疑惑我聽見了~就讓 Unity 小精靈來回答您吧!」
->如果仔細讀取上面那份Unity手冊,就可以發現:
- Awake()會在Prefab生成(instantiated)的時候就開始執行。(不過呢,如果GameObject在一開始是Inactive狀態就不會執行,等到active後才會執行)
- OnEnable()是在GameObject啟動(enabled)的下一瞬間開始執行。(一樣要求active)
....乾,感覺起來還是差不多啊= ="
於是我又再稍微鑽研了一下:Awake會在GameObject打勾/啟用(become "active")的時候執行,而OnEnable則是在Component本身打勾/啟用(become "enabled")的時候執行。
簡而言之:如果你只啟動了GameObject卻沒有啟動Component,才有可能保證先跑Awake()再跑OnEnable().....因為系統也只會跑Awake()。
(參見這篇博客)
.....然後這個Component的OnEnable()就和Start()沒有差別了。
而且硬要用的話,我還得寫腳本去開啟Component,根本是多此一舉、浪費資源。
這設定根本是僅供Editor逐步測試程式碼用的吧......
================================
那麼,該怎麼樣才能完成一開始提到的儲存資料功能呢?
雖然就我所知,目前的Unity Editor(2017.2.1)調用函式順序仍然是「逐個Component的(Awake&OnEnable) -> 逐個Component的Start」,然而可以透過Editor的內建設定來調整Component的(AO套組)執行順序。
只要到 File -> ProjectSettings -> Script Execution Order、然後再放上特定的腳本並排序,他們的AO套組就會被強制排序執行。以前面的例子來說是這樣的畫面:
執行之後就會變得像是我們想要的了:
裁剪相片時邊界各種亂跳、結果就切邊了。偉哉Win 10。 |
若 Scene 套用前述SEO設定,並同時含有以下GameObject / 腳本:
- Player: 擁有OnEnable()與Start()
- GSM: 擁有Awake()
- LM: 擁有Awake()與Start()
則腳本執行順序大概會是這樣:
- GSM 的 Awake()
- Player(2)、Player(1)的 OnEnable()
- LM 的 Awake()
- Player(2)、Player(1)、LM的Start()
順帶一提,流程圖變成了這樣:
.....某種程度上來說也是達成了最初的執行順序需求啦。
可是瑞凡,我就是想要這個Awake()後面接那個OnEnable()
有辦法辦到嗎?有的!透過協程強制等待(Coroutine / yield return ... )、或者強行委派(Delegate / Invoke())就可以辦到囉。
協程(概念):我就等你跑完、我才開始跑。
委派(概念):我跑完之後,趕緊交棒給你。
....不過兩種寫法都太冗長、又太針對了,我目前還不打算使用就是。
有興趣的人可以自行參考這篇Unity問答,Bilelmnasser大大的講解與範例應該可以提供足夠的教學。
感謝講解,非常清楚~
回覆刪除