UI的版面配置暫時來說是足夠的,接下來要開始將實際的功能逐一加進來。而為了要達成這部份,還是要花些時間回到程式碼端建構類似於Manager概念的物件。而這部份就用Zenject當做基底。Zenject是一套相依性注入的簡易框架,利用它當做基底可以有效解決相依性拿取、引用的問題。
解決相依性的套件最大的幫助是解決濫用Singleton的問題,不過探討這部份不是此開發記錄的重點。總之使用了Zenject後,已經有好幾年沒有在Unity的Runtime中弄出任何Singleton了。有興趣的開發者可以自行研究,這套件的資訊在很多日文Unity開發網站上均有深入的使用內容可以參考,而英文網站則是比較簡易使用的資料,對於初學者有相當大的幫助。
有些可惜的是這套件目前處於消極維護的狀態,也是少數幾個一、二年沒有什麼重大更新(真的沒有什麼更新),但依然是在製作專案時少數幾個會率先加進來的外掛之一(另一個就是UniRx,後續會有機會介紹)。
而現在加入這些套件可以直接利用OpenUPM,在package.json裡直接放入下列這段,基於某些歷史原因,放在OpenUPM的名稱是Extenject而不是Zenject,雖然名稱有異但為同一套外掛
"scopedRegistries": [
{
"name": "package.openupm.com",
"url": "https://package.openupm.com",
"scopes": [
"com.svermeulen.extenject"
]
}
]
由於這是微框架性質的套件,每個人用法不同,就不再這裡贅述。該外掛裡的範例雖然以實際開發角度來看不太合理,但很適合拿來了解Zenject怎麼使用。
為了讓Code端有個比較穏定的架構,導入Zenject進行Manager的製作。由於是多場景的使用結構,故以SceneContext為主體,搭配ProjectContext進行全場景功能引用。
為了之後會用Addressable進行多數資料拿取的動作,參照了官方影片,進行了一些調整
從影片中3:04的截圖包含了整個架構的概觀
將主要載入資料的Scene劃分出來,也就是提供了一個Loader(Initialization)概念的Scene。而這個Loader並沒有做太多事,只是下載Addressable資料。而這個資料裡包含了主選單和實際遊玩場景。
而Loader會利用VS進行載入Addressable的動作,這裡利用VS最主要的考量是它的狀態機和彈性。多數時間利用VS所得到的彈性是可以勝過效能上的損失,更不用說利用視覺化的狀態機可以達到很不錯的分類、分批執行,但VS也是有令人覺得不方便的地方,像是namespace一改,引用的function就整壞掉。
這讓我想起很久以前的Unity版本,好像是4.x版,那時可以用C#的namespace了,但調整namespace的名稱也會讓元件跑掉是一樣的。
而另一點則是Regenerate Units會因為專案裡的外掛或是asmdef數量太多而導致時間拉長,真的很久。多數時間都會落在10分鐘左右。如果幾天才Regenerate一次,累積的時間也還好,看起來像是一杯咖啡的時間。但用VS的經驗,特別是前期開發時,不停的增加新的型別、型別函式,都需要進行Regenerate,VS端才會有相對應的改變。因此,一天會Regenerate很多次,倘若一天六到七次就一個小時沒了。
不過它特有的彈性也還是很適合不確定性高的開發,就算是有些不便利,也還是可以接受。
調整加入Zenject除了處理程式碼端的相依性,也連帶著將VS會需要用到的相依性一併處理。不過,處理VS端沒有辦法像程式碼端那縻方便。程式碼端原則上利用這樣的寫法即可
[Inject]
public void SomeFunc(
IRefType1 refType1,
IRefType2 refType2)
{
}
然而要放到VS裡,只能利用已拿取到的相依性型別,再自行注入到VS的Variables元件裡
[Inject]
public void SomeFunc(
IRefType1 refType1,
IRefType2 refType2)
{
var v = gameObject.GetComponent<Variables>();
v.declarations.Set("refType1", refType1);
v.declarations.Set("refType2", refType2);
}
雖然調整成利用Zenject很花時間,但為了之後全數都進入到Addressable中進行載入和引用,這樣調整是必需的。
使用Addressable有時會將Handler存下來以供後續移除使用,然而移除是以Async方式進行。若是移除的時間點是在Dispose裡,會因為Dispose本身並不是也不能以Async方式進行清除。只能依賴其它的機制進行。
這幾篇有看來相當不錯的解決方案
在沒有C# 8.0的規範下一樣可以自行處理。
不過,沒有試過,不清楚
或許比較好的方式是清除動作不要留到Dispose裡進行。