1. 概況
這個實現(xiàn)來自于我的個人開源項目 UnityGameWheels(以下簡稱 UGW),并在實際生產(chǎn)中已有一定的應(yīng)用。UGW 的代碼地址:
- Core: 純 C# 部分。其中資產(chǎn)管理和更新相關(guān)內(nèi)容位于 Asset。
- Unity: 和 Unity 結(jié)合的部分。其中資產(chǎn)管理和更新相關(guān)內(nèi)容位于 Asset,編輯器相關(guān)位于編輯器。
- Demo:一些示例代碼。
此間一些設(shè)計方式參考了我一位老友的 GameFramework。此外,玩具代碼頗多(比如有個玩具版 IoC 容器),請見諒并無視。
1.1. 企圖
- 希望為移動平臺(主要是 iOS 和 Android 系統(tǒng))實現(xiàn)具有一定通用性的資產(chǎn)管理與更新系統(tǒng)。
- 在使用時不必過多顧及資產(chǎn)包(Asset Bundle),而是關(guān)注單個資產(chǎn)(Asset)。
- 對更新的內(nèi)容,做出一定程度的分組,實現(xiàn)邊玩邊下。
1.2. 名詞
- 資產(chǎn)和資產(chǎn)包:即 Unity 中的 Asset 和 AssetBundle。
- 兩種模式:
- 編輯器模式:在編輯器下開發(fā)時,通過
UnityEditor.AssetDatabase中的方法直接訪問資產(chǎn)文件。 - 資產(chǎn)包模式:構(gòu)建資產(chǎn)包使用的模式。這種模式為后文的主要討論對象。
- 編輯器模式:在編輯器下開發(fā)時,通過
- 資源(Resource):在 Core 中指資產(chǎn)包。這用法也來自 GameFramework(但是我后悔這么命名了 :P)。
- 索引(Index)文件:專指收集、記錄資產(chǎn)和資產(chǎn)包基本信息(類似于 Unity 提供的資產(chǎn)包 manifest 文件的功能)的文件。
- CR:安裝包索引文件。
- RR:遠(yuǎn)端索引文件。
- PR:持久化索引文件。
- Manifest 文件:Unity 構(gòu)建資產(chǎn)包時生成的數(shù)據(jù)文件,包含資產(chǎn)包和資產(chǎn)的關(guān)系以及資產(chǎn)包間的依賴關(guān)系。
- 資產(chǎn)系統(tǒng):指本文所描述的資產(chǎn)管理和更新系統(tǒng)。
1.3. 主要組成部分
- Core(純 C#)部分
-
AssetService類(實現(xiàn)IAssetService接口)是資產(chǎn)包模式的主入口,提供資產(chǎn)管理與更新的入口。- 通過
Prepare方法來進(jìn)行資產(chǎn)系統(tǒng)的準(zhǔn)備工作。 - 通過
CheckUpdate方法來檢查是否需要進(jìn)行更新以及哪些內(nèi)容需要更新。 - 通過
IResourceUpdater接口(實現(xiàn)為AssetService.ResourceUpdater)來進(jìn)行資產(chǎn)包(資源)的更新。 - 通過
LoadAsset,LoadSceneAsset,UnloadAsset等方法來加載和卸載資源。
- 通過
-
- Unity 部分:
- Asset 文件夾提供依賴于 Unity 庫的實現(xiàn),和編輯器模式下使用的
IAssetService的實現(xiàn)。 - Editor/AssetBundle 文件夾提供構(gòu)建資產(chǎn)包相關(guān)的編輯器工具。
- Asset 文件夾提供依賴于 Unity 庫的實現(xiàn),和編輯器模式下使用的
2. 一些重要概念
2.1. 資產(chǎn)包的分組
在 Unity 中,每個資產(chǎn)文件至多顯式打入一個資產(chǎn)包,所以對資產(chǎn)包分組(Group),就相當(dāng)于對顯式打入資產(chǎn)包的每個資產(chǎn)都分組。為什么要分組呢?一方面,是為了按組為單位做資產(chǎn)包更新;另一方面,是控制依賴關(guān)系的復(fù)雜度。
使用非負(fù)整數(shù)來標(biāo)記每個資產(chǎn)包的組號。
- 0 代表公共組,可以被其他組依賴。
- 正整數(shù)代表其他組,不允許組間依賴,但是都可以依賴 0 組。
在分組更新的基礎(chǔ)上,這樣的限制帶來的好處是,不需要為了更新一個分組的內(nèi)容而大量更新其他分組的內(nèi)容。當(dāng)然,這種選擇同時也是一種局限。
在應(yīng)用啟動過程中,“正式”進(jìn)入游戲之前,應(yīng)將 0 組的內(nèi)容更新完畢。
2.2. 索引文件
Unity 自身在構(gòu)建資產(chǎn)包時,提供了 manifest 文件,用于指明每個資產(chǎn)包中包含哪些資產(chǎn)以及依賴于哪些其他資產(chǎn)包。索引文件,在此基礎(chǔ)上加入了包括資產(chǎn)之間的依賴關(guān)系、資產(chǎn)包分組(后文解釋)在內(nèi)的若干其他信息。編輯器工具構(gòu)建資產(chǎn)包時會生成三個文件夾:
- Client:用于放在 StreamingAssets 中、打入首包的資產(chǎn)包;
- ClientFull:用于放在 StreamingAssets 中的全量資產(chǎn)包,適合調(diào)試或者關(guān)閉更新功能的情形。
- Server:用于放在 CDN 上用于更新的全量資產(chǎn)包。
這三個文件夾中各自會有一個索引文件。前兩者自然格式一致,稱為安裝包索引文件(記為 CR,其中 C 代表 Client, R 代表 Resource),隨首包發(fā)布。Server 文件夾中的索引文件稱為遠(yuǎn)端索引文件(記為 RR,其中第一個 R 代表 Remote)。在資源更新和使用的過程中,本地持久化目錄中會存放一份索引文件,稱為持久化索引文件(記為 PR,其中 P 代表 Persistent),它記載的是本地保存的那些資產(chǎn)包的信息。
注意,在 Server 文件夾中的每個文件都會后綴它自身的 CRC-32 校驗和,用于下載之后的校驗。

2.3. 版本號
資產(chǎn)系統(tǒng)使用的資產(chǎn)包版本號包括兩部分,是由應(yīng)用程序版本號 VerApp (其實是 UnityEngine.Application.version 的值)和資源內(nèi)部版本號 VerRes 拼接而成,對于每一個 VerApp,在每個平臺上,打包的時候 VerRes 最好從 1 開始自增。如 VerApp 為 1.0.1,在這個應(yīng)用程序版本下,Android 平臺第 19 次資產(chǎn)包構(gòu)建,其版本號為 1.0.1.19。如果應(yīng)用程序版本升為 1.1.0,則再度打 Android 資源包的時候版本號就是 1.1.0.1。這也是后文講的資產(chǎn)包構(gòu)建器的默認(rèn)行為。
應(yīng)用程序運(yùn)行時,如果開啟了資源更新,則本系統(tǒng)只是根據(jù)輸入的信息來判定應(yīng)該下載哪個 RR,而不會去檢查版本的新舊。標(biāo)準(zhǔn)的做法,是應(yīng)用程序從某個服務(wù)器獲取當(dāng)前 VerApp 對應(yīng)的最新的 VerRes,以及相應(yīng)的文件尺寸、CRC-32 等信息,來判定是否需要下載這個版本的 RR。
3. 更新資產(chǎn)包
3.1. 初始化和準(zhǔn)備階段
構(gòu)造 AssetService 對象時需要傳入一些配置信息,包括但不限于 CDN 服務(wù)器的根目錄、同時進(jìn)行的資產(chǎn)加載任務(wù)數(shù)量限制、同時進(jìn)行的資產(chǎn)包加載任務(wù)數(shù)量限制等內(nèi)容。
系統(tǒng)初始化之后,通過 AssetService.Prepare 方法進(jìn)行的準(zhǔn)備工作,其實就是要把 CR 和 PR 從各自所在的文件系統(tǒng)中載入內(nèi)存。CR 是必須要存在的,而 PR 一開始的時候不存在,就認(rèn)為存在一個空的 PR。
3.2. 更新檢測階段
在準(zhǔn)備階段完成之后(這要是都沒成功就別玩兒了),就要通過 AssetService.CheckUpdate 來檢測是否有需要更新的內(nèi)容。這里需要傳入一個 AssetIndexRemoteFileInfo (索引文件信息)對象,是使用者從相關(guān)服務(wù)器獲取的關(guān)于 RR 的信息,其中包括如下一些字段:
<img src='remote_index_info.jpg' height=150/>
其中 InternalAssetVersion 就是前面所說的 VerRes,指這個 RR 對應(yīng)的資產(chǎn)包版本,Crc32 是該 RR 的 CRC-32 校驗和,FileSize 是該文件的大小(字節(jié))。后面這兩個字段都是為了下載之后的校驗。
更新檢測又有幾種情況。
- 如果關(guān)閉了更新,則直接使用 CR 作為 PR。此時,認(rèn)為安裝包中 StreamingAssets 目錄下的內(nèi)容是完整可用的(即從前述之 ClientFull 文件夾復(fù)制而來,如果之前下載了任何資源,我們都認(rèn)為是沒用的。
- 如果打開更新,且本地緩存的 RR 的
Crc32和FileSize均和AssetIndexRemoteFileInfo中提供的數(shù)據(jù)一致,說明不需要從服務(wù)器下載 RR,用本地緩存的即可。 - 其余情況,需要從遠(yuǎn)端下載 RR。UGW 中有支持文件下載系統(tǒng)的實現(xiàn),超出本文范疇,不贅述。
對于上述后兩種情形,系統(tǒng)會對 CR, RR, PR 做三方比較,來決定哪些資產(chǎn)包是需要下載的,哪些資產(chǎn)包是需要(從持久化目錄刪除的)。具體地:
- RR 中沒有的資產(chǎn)包(說明已經(jīng)沒用了),如果 PR 中有,則應(yīng)該從本地持久化目錄中刪除。
- RR 中有和 CR 中相同(通過比較 Unity 生成的 Hash 值和文件尺寸來決定)的資產(chǎn)包,則刪去 PR 中包含的那個版本(如果有的話)。
- 對 RR 中有,但是 CR 中缺少或內(nèi)容不同(通過比較 Unity 生成的 Hash 值和文件尺寸來決定)的資產(chǎn)包,需要更新。
在三方比較的同時,系統(tǒng)還會對每個資產(chǎn)包分組構(gòu)造資產(chǎn)包更新摘要信息。這摘要由 ResourceGroupUpdateSummary 類描述,包含其所指向的資產(chǎn)包分組中的資產(chǎn)包總量、剩余下載量等信息。這些摘要對象將用于后面的資產(chǎn)包更新。
3.3. 更新
前述準(zhǔn)備工作完成后,就可以使用 AssetService.ResourceUpdater 更新器對象進(jìn)行更新了,通過它(實現(xiàn) IResourceUpdater 接口)可以:
- 獲取可用的資產(chǎn)包分組都有哪些。
- 對給定的資產(chǎn)包分組,獲取其中資源狀態(tài)(需要更新、正在更新、已經(jīng)最新)。
- 對某一組的資產(chǎn)包開始、停止更新;
- 通過前述
ResourceGroupUpdateSummary類,獲取各組資產(chǎn)包更新進(jìn)度和狀態(tài)(是否在更新、是否已經(jīng)最新等)。
更新資產(chǎn)包的過程中,會更新 PR 中的內(nèi)容并在適當(dāng)?shù)臅r候保存到持久化目錄中。對于每個資產(chǎn)包分組,一定要全部更新完才可使用其中的內(nèi)容。
4. 使用資產(chǎn)
資產(chǎn)系統(tǒng)中提供了一些輔助方法,來判定資產(chǎn)是否已經(jīng)可以使用,也就是判斷資產(chǎn)的存在性、以及所屬的資產(chǎn)包分組是否已經(jīng)更新完畢。在此基礎(chǔ)上,使用者可以使用(邏輯層面的)加載、卸載接口來使用和釋放資產(chǎn)。
4.1. 加載接口與資產(chǎn)訪問器
AssetService 提供 LoadAsset 和 LoadSceneAsset 方法來加載一般資產(chǎn)和場景資產(chǎn)。鑒于后者沒有進(jìn)行仔細(xì)測試,此處暫時僅對前者做出說明。LoadAsset 的函數(shù)簽名為
IAssetAccessor LoadAsset(string assetPath, LoadAssetCallbackSet callbackSet, object context);
使用者將資產(chǎn)路徑(從 "Assets/" 開始)、回調(diào)函數(shù)和可選的自定義上下文對象傳入,即可同步地獲得一個 IAssetAccessor,即資產(chǎn)訪問器(簡稱 AA)。AA 的引入,是由于加載資產(chǎn)操作在概念上是異步的(盡管由于內(nèi)部緩存等原因可能實際上是同步完成的)。如果在加載未完成的情況下,使用者不想用這個資產(chǎn)了,通過這個訪問器可以卸載資產(chǎn)。通過 IAssetAccessor 接口,使用者可以獲取資產(chǎn)路徑、資產(chǎn)對象(如果已經(jīng)加載完成)以及其狀態(tài)。
一般情況下,任何使用某一資產(chǎn)的代碼,都應(yīng)通過 LoadAsset 獲得一個該資產(chǎn)的訪問器。資產(chǎn)和訪問器是一對多的關(guān)系。
4.2. 卸載接口
AssetService 提供 UnloadAsset 方法來(從邏輯上)卸載資產(chǎn)。
void UnloadAsset(IAssetAccessor assetAccessor);
卸載資產(chǎn)時,只需要傳入 AA 即可。要注意,一個資產(chǎn)訪問器只允許卸載一次。卸載之后,就不可再使用/引用這個 AA 對象,否則可能造成很難查找的 bug。
4.3. 內(nèi)部實現(xiàn)的基本數(shù)據(jù)模型
在 AssetService 內(nèi)部,用資產(chǎn)緩存(AssetService.Loader.AssetCache 內(nèi)部類,簡稱 ACache)來描述一個資產(chǎn),用資產(chǎn)包緩存(AssetService.Loader.ResourceCache 內(nèi)部類,簡稱 RCache)來描述一個資產(chǎn)包。這兩種緩存內(nèi)部都保存了自己代表的資產(chǎn)(包)的引用計數(shù)。
首先,一個 ACache 可對應(yīng)多個資產(chǎn)訪問器。每個 AA 都綁定一個 ACache,ACache 的狀態(tài)變化會反應(yīng)到訪問器中。

其次,ACache 內(nèi)部會記錄它所代表的資產(chǎn)依賴于哪些其他資產(chǎn)和資產(chǎn)包(從索引文件 PR 中獲得),這些信息用來維護(hù) ACache 和 RCache 的引用計數(shù),最終決定資產(chǎn)和資產(chǎn)包的何時釋放。這里要注意,單獨看 ACache 的時候,它們構(gòu)成有向無環(huán)圖(即不允許資產(chǎn)間的依賴構(gòu)成環(huán)路)。而即使有資產(chǎn)包分組間的依賴關(guān)系限制,和資產(chǎn)間不允許依賴成環(huán)路的限制,RCache 之間仍然可能構(gòu)成環(huán)路,如下圖所示(實現(xiàn)代表依賴關(guān)系,虛線代表資產(chǎn)和資產(chǎn)包的從屬關(guān)系)。

由于上圖中資產(chǎn) a 依賴于資產(chǎn) c,c 又依賴于依賴于資產(chǎn) b,而 a, b 屬于資產(chǎn)包 x,c 屬于資產(chǎn)包 y,因此 x 和 y 是相互依賴的。
注意:AA、ACache、RCache 實際上都有相應(yīng)的對象池來管理,以便減少運(yùn)行時的 GC Alloc。
4.4. 加載資產(chǎn)的過程
當(dāng)嘗試(通過文件路徑)加載一個資產(chǎn)的時候(即調(diào)用 AssetService.LoadAsset 方法時),如果沒有相應(yīng)的 ACache 對象,則從對象池獲取一個或創(chuàng)建一個;否則,這資產(chǎn)應(yīng)該已經(jīng)被要求加載過,直接使用已有的 ACache 對象即可。不論哪種情況,一個 AA 將和這個 ACache 綁定(并增加 ACache 的引用計數(shù)使之一定為正的)并同步返回。
ACache 初始化的時候,會做以下事情:
- 遞歸的初始化它依賴的資產(chǎn)的 ACache(如果需要的話),增加后者的引用計數(shù),并觀察后者的狀態(tài)變化。由于 ACache 構(gòu)成有向無環(huán)圖,所以簡單遞歸即可完成這步操作。
- 初始化自身指向的資產(chǎn)所在的資產(chǎn)包的 RCache 對象(如果需要的話),增加后者的引用計數(shù),并作為后者狀態(tài)變化的觀察者。
- 從自身所屬資產(chǎn)包的 RCache 對象出發(fā),在 RCache 構(gòu)成的圖結(jié)構(gòu)中做遍歷,增加過程中每個 RCache 的引用計數(shù)。
由于依賴關(guān)系相關(guān)問題都在 ACache 中處理,RCache 的業(yè)務(wù)相對簡單,只是負(fù)責(zé)自身指向的資產(chǎn)包的
加載和發(fā)送狀態(tài)變化的通知給觀察者。
ACache 會等待自己代表的資產(chǎn)所屬的資產(chǎn)包的 RCache 加載完成,以及自己依賴的其他 ACache 加載完成,之后再加載自身代表的資產(chǎn)。于是,只要一個 ACache 加載完成(其資產(chǎn)對象對所有綁定到自身的 AA 都已可用),它所依賴的(顯式打資產(chǎn)包的)資產(chǎn)都加載完成了,于是相關(guān)聯(lián)的資產(chǎn)包也是加載完成了的。
使用者需要注意:
- 本資產(chǎn)系統(tǒng)中,加載失敗即為錯誤情況,不可繼續(xù)使用。使用者在加載一個資產(chǎn)時,需要確定它是可用的,比如資產(chǎn)本身是否存在、所在資產(chǎn)包分組是否更新完畢等。
- 某些 Android 設(shè)備上,文件 IO 很容易出現(xiàn)問題,盡管 Unity 層的實現(xiàn)(
ResourceLoadingTaskImpl類)增加了重試機(jī)制,仍然可能在從文件創(chuàng)建資產(chǎn)包的時候失?。ㄟB續(xù)失敗多次)。目前只能降低同時加載的資產(chǎn)包的數(shù)量限制來減少出問題的概率。
4.5. 卸載資產(chǎn)的過程
卸載資產(chǎn)時(AssetService.UnloadAsset 方法),使用者進(jìn)行的操作實際上是歸還 AA 對象,歸還時不需要在意真實的資產(chǎn)是否仍處于正在被加載的狀態(tài)。資產(chǎn)系統(tǒng)會清理 AA 內(nèi)部保存的回調(diào)(通過 AssetService.LoadAsset 方法傳入),以防止在 AA 被完全清理之前恰好有回調(diào)發(fā)生。此時對于使用者,這個 AA 對象已經(jīng)失效,不應(yīng)在以任何方式引用或使用它。后面系統(tǒng)進(jìn)行輪詢的時候會回收或丟棄被卸載的 AA 對象。依前述 AA, ACache, RCache 之間的關(guān)系,相關(guān)的 ACache 和 RCache 的引用計數(shù)會減少。
如果一個 ACache 或 RCache 的引用計數(shù)減少到 0,它將進(jìn)入一個集合,以便進(jìn)行清理。真正清理將也在系統(tǒng)輪詢時進(jìn)行,主要步驟是:
- 清理被歸還的 AA 對象。
- 按資產(chǎn)間依賴關(guān)系,遞歸清理引用為 0 的 ACache。因為 Unity 實際上不允許取消加載資產(chǎn)的操作,所以如果 ACache 指向的資產(chǎn)正在被加載,就暫緩清理。注意,雖然清理了 ACache 對象,但不會真的卸載單個資產(chǎn),這算是一種實現(xiàn)選擇。
- 隔一段時間,或者使用者要求清理時,如果引用計數(shù)為 0 的這些 RCache 中,其指向的資產(chǎn)包均不處于加載狀態(tài),則將它們一同卸載。這時候 Unity 層的實現(xiàn)部分是會真實調(diào)用
AssetBundle.Unload(true)方法,將資產(chǎn)包真正卸載。
對引用計數(shù)為 0 的資產(chǎn)包的同時卸載規(guī)則,主要是為了保障,彼此存在依賴關(guān)系的資產(chǎn)包會被一起卸載掉,否則可能出現(xiàn)一些很難查明的資產(chǎn)丟失 bug。
4.6. 資產(chǎn)包的規(guī)劃
一個相對獨立的功能,從直覺上說,可以打成一個或多個放在一個分組中的資產(chǎn)包。實際操作中,在一個功能內(nèi)部,經(jīng)常是按文件夾來分割資產(chǎn)包的,而文件夾又經(jīng)常是按資產(chǎn)類型分的。
考慮一個問題:如果一個貼圖文件夾中有很多貼圖,在同一個功能的兩個不同界面 p, q 上使用,由于這個文件夾打在一個資產(chǎn)包中,它只會作為一個總體釋放。界面 p 可能是掛在游戲主界面上的,長期存在,只使用了少量貼圖;而界面 q 是這個功能的主界面,使用了大量貼圖。在運(yùn)行時,p 的生命期明顯比 q 長,一旦加載了 q 使用的貼圖資產(chǎn),只是關(guān)閉和銷毀 q,是釋放不掉 q 使用的這些貼圖的。直到 p 也被銷毀,這些貼圖才會一并被卸載。如果有很復(fù)雜的資產(chǎn)包間的依賴關(guān)系,這個釋放來得可能很晚。
可以通過按“生命期”劃分資產(chǎn)包(從文件夾層面就可以這樣做),以及簡化資產(chǎn)包之間的依賴關(guān)系來規(guī)避這樣的問題。
5. 編輯器
5.1. 資產(chǎn)包組織器
編輯器層面提供了一個資產(chǎn)包組織器類 AssetBundleOrganizer 來配置將哪些資產(chǎn)打入哪些資產(chǎn)包,并配有一個簡單的可視化工具(AssetBundleOrganizerEditorWindow)來進(jìn)行編輯。

組織器可視化工具的功能大致如下:
- 左數(shù)第一欄為資產(chǎn)根目錄(可以有多個),設(shè)置將哪些目錄視為根目錄并從中讀取資產(chǎn),以及讀取什么類型的資產(chǎn)。
- 左數(shù)第二欄為資產(chǎn)目錄,森林結(jié)構(gòu),每個資產(chǎn)根目錄下的資產(chǎn)在為一棵樹。
- 左數(shù)第三欄為資產(chǎn)包目錄結(jié)構(gòu),可在其中添加、刪除、編輯資產(chǎn)包,指定分組等。
- 左數(shù)第四欄展示在第三欄中選中的資產(chǎn)包內(nèi)的資產(chǎn)內(nèi)容。
結(jié)合右邊三欄,可以選中資產(chǎn)文件或目錄分配如資產(chǎn)包中,也可以從資產(chǎn)包中刪除內(nèi)容。
此外,組織器還支持一個忽略某些資產(chǎn)的標(biāo)簽(AssetBundleOrganizer.IgnoreAssetLabel 屬性),給資產(chǎn)文件加上指定的標(biāo)簽(Label),組織器將忽略這些資產(chǎn),從而不會顯示將它們打入資產(chǎn)包。
組織器會將信息存放在一個 xml 文件中,如上圖左下角的 Config path 所示。對于規(guī)模較小的項目,直接用這個可視化工具也許就夠了。但如果項目規(guī)模較大,則建議使用 AssetBundleOrganizer 提供的 API 來編寫“規(guī)則”代碼,來動態(tài)生成這些內(nèi)容。
5.2. 資產(chǎn)包信息提供器
資產(chǎn)包信息提供器由類 AssetBundleInfosProvider 實現(xiàn),用于將組織器中的數(shù)據(jù)轉(zhuǎn)換成構(gòu)建資產(chǎn)包可用的數(shù)據(jù)。譬如,資產(chǎn)包組織器中可以將某個目錄分配到某個資產(chǎn)包中,但是實際構(gòu)建資產(chǎn)包需要將目錄中的資產(chǎn)文件和資產(chǎn)包對應(yīng)起來。資產(chǎn)包信息提供器就能進(jìn)行此轉(zhuǎn)換。此外,還可以檢測(打包用的)資產(chǎn)間依賴關(guān)系、資產(chǎn)包間的依賴關(guān)系是否合法(比如前述資產(chǎn)包編輯器可視化工具中的 Check Dependency Legality 按鈕)等等。
5.3. 構(gòu)建
資產(chǎn)包構(gòu)建器(AssetBundleBuilder 類)封裝了構(gòu)建資產(chǎn)包的過程(方法 BuildPlatform)。主要步驟如下:
- 通過資產(chǎn)包組織器和信息提供器,得到資產(chǎn)和資產(chǎn)包的對應(yīng)關(guān)系,構(gòu)造 Unity 的
AssetBundleBuild列表。 - 調(diào)用 Unity 的方法,構(gòu)建資產(chǎn)包,獲得 manifest 文件。
- 利用 manifest 文件和其他數(shù)據(jù),生成在索引文件中需要的資產(chǎn)包信息,如分組、CRC-32 校驗和、Unity 生成的 Hash 值等。
- 生成 Client, ClientFull, Server 文件夾及相應(yīng)的索引文件。
使用者可以通過實現(xiàn) IAssetBundleBuilderHandler 接口來指定構(gòu)建各個階段的回調(diào)。例如:使用 Lua 腳本的項目可以在自己的 IAssetBundleBuilderHandler 實現(xiàn)中,用 OnPreBeforeBuild 回調(diào)來給 .lua 后綴的文件改名為 .txt 之類的后綴,以便能被 Unity 識別為文本資產(chǎn)(Text asset);同樣,在 OnBuildSuccess 和 OnBuildFailure 回調(diào)中將重命名的文件復(fù)原。
6. 局限性
- 目前對已經(jīng)發(fā)起的資產(chǎn)加載調(diào)用是沒有優(yōu)先級的,內(nèi)部又有一些 Hash 存儲,不能保證實際的加載順序和發(fā)起加載調(diào)用的順序一致。
- 內(nèi)存中同時有資產(chǎn)間的依賴關(guān)系和資產(chǎn)包間的依賴關(guān)系,不知道是否可以舍棄后者,還能保證邏輯正確,不出現(xiàn)資產(chǎn)丟失的問題。
- 加載資產(chǎn)名義上是異步,但實際上有可能是同步返回的。實際使用時,為了便利起見可以增加中間層。
- 目前采用“集總式”索引文件,可能一次解析的內(nèi)容較多,在游戲啟動階段造成一些卡頓現(xiàn)象。
- 未能支持子資產(chǎn)(Sub-asset)或泛型加載資產(chǎn)。例如:對圖集(如 Texture Packer 這類插件輸出的)這種類型的資產(chǎn),需要用一個
SerializableObject來存放其中精靈圖的引用。