Unity 資產(chǎn)管理與更新系統(tǒng)的一種實(shí)現(xiàn)方式

1. 概況

這個(gè)實(shí)現(xiàn)來(lái)自于我的個(gè)人開(kāi)源項(xiàng)目 UnityGameWheels(以下簡(jiǎn)稱 UGW)组力,并在實(shí)際生產(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è)計(jì)方式參考了我一位老友的 GameFramework宾巍。此外,玩具代碼頗多(比如有個(gè)玩具版 IoC 容器)渔伯,請(qǐng)見(jiàn)諒并無(wú)視顶霞。

1.1. 企圖

  • 希望為移動(dòng)平臺(tái)(主要是 iOS 和 Android 系統(tǒng))實(shí)現(xiàn)具有一定通用性的資產(chǎn)管理與更新系統(tǒng)。
  • 在使用時(shí)不必過(guò)多顧及資產(chǎn)包(Asset Bundle)锣吼,而是關(guān)注單個(gè)資產(chǎn)(Asset)选浑。
  • 對(duì)更新的內(nèi)容,做出一定程度的分組玄叠,實(shí)現(xiàn)邊玩邊下古徒。

1.2. 名詞

  • 資產(chǎn)和資產(chǎn)包:即 Unity 中的 Asset 和 AssetBundle。
  • 兩種模式:
    • 編輯器模式:在編輯器下開(kāi)發(fā)時(shí)读恃,通過(guò) UnityEditor.AssetDatabase 中的方法直接訪問(wèn)資產(chǎn)文件描函。
    • 資產(chǎn)包模式:構(gòu)建資產(chǎn)包使用的模式。這種模式為后文的主要討論對(duì)象狐粱。
  • 資源(Resource):在 Core 中指資產(chǎn)包舀寓。這用法也來(lái)自 GameFramework(但是我后悔這么命名了 :P)。
  • 索引(Index)文件:專指收集肌蜻、記錄資產(chǎn)和資產(chǎn)包基本信息(類似于 Unity 提供的資產(chǎn)包 manifest 文件的功能)的文件互墓。
    • CR:安裝包索引文件。
    • RR:遠(yuǎn)端索引文件蒋搜。
    • PR:持久化索引文件篡撵。
  • Manifest 文件:Unity 構(gòu)建資產(chǎn)包時(shí)生成的數(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 類(實(shí)現(xiàn) IAssetService 接口)是資產(chǎn)包模式的主入口券盅,提供資產(chǎn)管理與更新的入口。
      • 通過(guò) Prepare 方法來(lái)進(jìn)行資產(chǎn)系統(tǒng)的準(zhǔn)備工作膛檀。
      • 通過(guò) CheckUpdate 方法來(lái)檢查是否需要進(jìn)行更新以及哪些內(nèi)容需要更新锰镀。
      • 通過(guò) IResourceUpdater 接口(實(shí)現(xiàn)為 AssetService.ResourceUpdater)來(lái)進(jìn)行資產(chǎn)包(資源)的更新。
      • 通過(guò) LoadAsset, LoadSceneAsset, UnloadAsset 等方法來(lái)加載和卸載資源咖刃。
  • Unity 部分:
    • Asset 文件夾提供依賴于 Unity 庫(kù)的實(shí)現(xiàn)泳炉,和編輯器模式下使用的 IAssetService 的實(shí)現(xiàn)。
    • Editor/AssetBundle 文件夾提供構(gòu)建資產(chǎn)包相關(guān)的編輯器工具嚎杨。

2. 一些重要概念

2.1. 資產(chǎn)包的分組

在 Unity 中花鹅,每個(gè)資產(chǎn)文件至多顯式打入一個(gè)資產(chǎn)包,所以對(duì)資產(chǎn)包分組(Group)枫浙,就相當(dāng)于對(duì)顯式打入資產(chǎn)包的每個(gè)資產(chǎn)都分組刨肃。為什么要分組呢?一方面箩帚,是為了按組為單位做資產(chǎn)包更新之景;另一方面,是控制依賴關(guān)系的復(fù)雜度膏潮。

使用非負(fù)整數(shù)來(lái)標(biāo)記每個(gè)資產(chǎn)包的組號(hào)锻狗。

  • 0 代表公共組,可以被其他組依賴焕参。
  • 正整數(shù)代表其他組轻纪,不允許組間依賴,但是都可以依賴 0 組叠纷。

在分組更新的基礎(chǔ)上刻帚,這樣的限制帶來(lái)的好處是,不需要為了更新一個(gè)分組的內(nèi)容而大量更新其他分組的內(nèi)容涩嚣。當(dāng)然崇众,這種選擇同時(shí)也是一種局限。

在應(yīng)用啟動(dòng)過(guò)程中航厚,“正式”進(jìn)入游戲之前顷歌,應(yīng)將 0 組的內(nèi)容更新完畢。

2.2. 索引文件

Unity 自身在構(gòu)建資產(chǎn)包時(shí)幔睬,提供了 manifest 文件眯漩,用于指明每個(gè)資產(chǎn)包中包含哪些資產(chǎn)以及依賴于哪些其他資產(chǎn)包。索引文件,在此基礎(chǔ)上加入了包括資產(chǎn)之間的依賴關(guān)系赦抖、資產(chǎn)包分組(后文解釋)在內(nèi)的若干其他信息舱卡。編輯器工具構(gòu)建資產(chǎn)包時(shí)會(huì)生成三個(gè)文件夾:

  • Client:用于放在 StreamingAssets 中、打入首包的資產(chǎn)包队萤;
  • ClientFull:用于放在 StreamingAssets 中的全量資產(chǎn)包轮锥,適合調(diào)試或者關(guān)閉更新功能的情形。
  • Server:用于放在 CDN 上用于更新的全量資產(chǎn)包要尔。

這三個(gè)文件夾中各自會(huì)有一個(gè)索引文件舍杜。前兩者自然格式一致,稱為安裝包索引文件(記為 CR盈电,其中 C 代表 Client, R 代表 Resource)蝴簇,隨首包發(fā)布杯活。Server 文件夾中的索引文件稱為遠(yuǎn)端索引文件(記為 RR匆帚,其中第一個(gè) R 代表 Remote)。在資源更新和使用的過(guò)程中旁钧,本地持久化目錄中會(huì)存放一份索引文件吸重,稱為持久化索引文件(記為 PR,其中 P 代表 Persistent)歪今,它記載的是本地保存的那些資產(chǎn)包的信息嚎幸。

注意,在 Server 文件夾中的每個(gè)文件都會(huì)后綴它自身的 CRC-32 校驗(yàn)和寄猩,用于下載之后的校驗(yàn)嫉晶。

2.3. 版本號(hào)

資產(chǎn)系統(tǒng)使用的資產(chǎn)包版本號(hào)包括兩部分,是由應(yīng)用程序版本號(hào) VerApp (其實(shí)是 UnityEngine.Application.version 的值)和資源內(nèi)部版本號(hào) VerRes 拼接而成田篇,對(duì)于每一個(gè) VerApp替废,在每個(gè)平臺(tái)上,打包的時(shí)候 VerRes 最好從 1 開(kāi)始自增泊柬。如 VerApp 為 1.0.1椎镣,在這個(gè)應(yīng)用程序版本下,Android 平臺(tái)第 19 次資產(chǎn)包構(gòu)建兽赁,其版本號(hào)為 1.0.1.19状答。如果應(yīng)用程序版本升為 1.1.0,則再度打 Android 資源包的時(shí)候版本號(hào)就是 1.1.0.1刀崖。這也是后文講的資產(chǎn)包構(gòu)建器的默認(rèn)行為惊科。

應(yīng)用程序運(yùn)行時(shí),如果開(kāi)啟了資源更新亮钦,則本系統(tǒng)只是根據(jù)輸入的信息來(lái)判定應(yīng)該下載哪個(gè) RR译断,而不會(huì)去檢查版本的新舊。標(biāo)準(zhǔn)的做法或悲,是應(yīng)用程序從某個(gè)服務(wù)器獲取當(dāng)前 VerApp 對(duì)應(yīng)的最新的 VerRes孙咪,以及相應(yīng)的文件尺寸堪唐、CRC-32 等信息,來(lái)判定是否需要下載這個(gè)版本的 RR翎蹈。

3. 更新資產(chǎn)包

3.1. 初始化和準(zhǔn)備階段

構(gòu)造 AssetService 對(duì)象時(shí)需要傳入一些配置信息淮菠,包括但不限于 CDN 服務(wù)器的根目錄、同時(shí)進(jìn)行的資產(chǎn)加載任務(wù)數(shù)量限制荤堪、同時(shí)進(jìn)行的資產(chǎn)包加載任務(wù)數(shù)量限制等內(nèi)容合陵。

系統(tǒng)初始化之后,通過(guò) AssetService.Prepare 方法進(jìn)行的準(zhǔn)備工作澄阳,其實(shí)就是要把 CR 和 PR 從各自所在的文件系統(tǒng)中載入內(nèi)存拥知。CR 是必須要存在的,而 PR 一開(kāi)始的時(shí)候不存在碎赢,就認(rèn)為存在一個(gè)空的 PR低剔。

3.2. 更新檢測(cè)階段

在準(zhǔn)備階段完成之后(這要是都沒(méi)成功就別玩兒了),就要通過(guò) AssetService.CheckUpdate 來(lái)檢測(cè)是否有需要更新的內(nèi)容肮塞。這里需要傳入一個(gè) AssetIndexRemoteFileInfo (索引文件信息)對(duì)象襟齿,是使用者從相關(guān)服務(wù)器獲取的關(guān)于 RR 的信息,其中包括如下一些字段:

<img src='remote_index_info.jpg' height=150/>

其中 InternalAssetVersion 就是前面所說(shuō)的 VerRes枕赵,指這個(gè) RR 對(duì)應(yīng)的資產(chǎn)包版本猜欺,Crc32 是該 RR 的 CRC-32 校驗(yàn)和,FileSize 是該文件的大锌酱堋(字節(jié))开皿。后面這兩個(gè)字段都是為了下載之后的校驗(yàn)。

更新檢測(cè)又有幾種情況篮昧。

  • 如果關(guān)閉了更新赋荆,則直接使用 CR 作為 PR。此時(shí)恋谭,認(rèn)為安裝包中 StreamingAssets 目錄下的內(nèi)容是完整可用的(即從前述之 ClientFull 文件夾復(fù)制而來(lái)糠睡,如果之前下載了任何資源,我們都認(rèn)為是沒(méi)用的疚颊。
  • 如果打開(kāi)更新狈孔,且本地緩存的 RR 的 Crc32FileSize 均和 AssetIndexRemoteFileInfo 中提供的數(shù)據(jù)一致,說(shuō)明不需要從服務(wù)器下載 RR材义,用本地緩存的即可均抽。
  • 其余情況,需要從遠(yuǎn)端下載 RR其掂。UGW 中有支持文件下載系統(tǒng)的實(shí)現(xiàn)油挥,超出本文范疇,不贅述。

對(duì)于上述后兩種情形深寥,系統(tǒng)會(huì)對(duì) CR, RR, PR 做三方比較攘乒,來(lái)決定哪些資產(chǎn)包是需要下載的,哪些資產(chǎn)包是需要(從持久化目錄刪除的)惋鹅。具體地:

  • RR 中沒(méi)有的資產(chǎn)包(說(shuō)明已經(jīng)沒(méi)用了)则酝,如果 PR 中有,則應(yīng)該從本地持久化目錄中刪除闰集。
  • RR 中有和 CR 中相同(通過(guò)比較 Unity 生成的 Hash 值和文件尺寸來(lái)決定)的資產(chǎn)包沽讹,則刪去 PR 中包含的那個(gè)版本(如果有的話)。
  • 對(duì) RR 中有武鲁,但是 CR 中缺少或內(nèi)容不同(通過(guò)比較 Unity 生成的 Hash 值和文件尺寸來(lái)決定)的資產(chǎn)包爽雄,需要更新。

在三方比較的同時(shí)沐鼠,系統(tǒng)還會(huì)對(duì)每個(gè)資產(chǎn)包分組構(gòu)造資產(chǎn)包更新摘要信息挚瘟。這摘要由 ResourceGroupUpdateSummary 類描述,包含其所指向的資產(chǎn)包分組中的資產(chǎn)包總量迟杂、剩余下載量等信息刽沾。這些摘要對(duì)象將用于后面的資產(chǎn)包更新本慕。

3.3. 更新

前述準(zhǔn)備工作完成后排拷,就可以使用 AssetService.ResourceUpdater 更新器對(duì)象進(jìn)行更新了,通過(guò)它(實(shí)現(xiàn) IResourceUpdater 接口)可以:

  • 獲取可用的資產(chǎn)包分組都有哪些锅尘。
  • 對(duì)給定的資產(chǎn)包分組监氢,獲取其中資源狀態(tài)(需要更新、正在更新藤违、已經(jīng)最新)浪腐。
  • 對(duì)某一組的資產(chǎn)包開(kāi)始、停止更新顿乒;
  • 通過(guò)前述 ResourceGroupUpdateSummary 類议街,獲取各組資產(chǎn)包更新進(jìn)度和狀態(tài)(是否在更新、是否已經(jīng)最新等)璧榄。

更新資產(chǎn)包的過(guò)程中特漩,會(huì)更新 PR 中的內(nèi)容并在適當(dāng)?shù)臅r(shí)候保存到持久化目錄中。對(duì)于每個(gè)資產(chǎn)包分組骨杂,一定要全部更新完才可使用其中的內(nèi)容涂身。

4. 使用資產(chǎn)

資產(chǎn)系統(tǒng)中提供了一些輔助方法,來(lái)判定資產(chǎn)是否已經(jīng)可以使用搓蚪,也就是判斷資產(chǎn)的存在性蛤售、以及所屬的資產(chǎn)包分組是否已經(jīng)更新完畢。在此基礎(chǔ)上,使用者可以使用(邏輯層面的)加載悴能、卸載接口來(lái)使用和釋放資產(chǎn)揣钦。

4.1. 加載接口與資產(chǎn)訪問(wèn)器

AssetService 提供 LoadAssetLoadSceneAsset 方法來(lái)加載一般資產(chǎn)和場(chǎng)景資產(chǎn)。鑒于后者沒(méi)有進(jìn)行仔細(xì)測(cè)試漠酿,此處暫時(shí)僅對(duì)前者做出說(shuō)明拂盯。LoadAsset 的函數(shù)簽名為

IAssetAccessor LoadAsset(string assetPath, LoadAssetCallbackSet callbackSet, object context);

使用者將資產(chǎn)路徑(從 "Assets/" 開(kāi)始)、回調(diào)函數(shù)和可選的自定義上下文對(duì)象傳入记靡,即可同步地獲得一個(gè) IAssetAccessor谈竿,即資產(chǎn)訪問(wèn)器(簡(jiǎn)稱 AA)。AA 的引入摸吠,是由于加載資產(chǎn)操作在概念上是異步的(盡管由于內(nèi)部緩存等原因可能實(shí)際上是同步完成的)空凸。如果在加載未完成的情況下,使用者不想用這個(gè)資產(chǎn)了寸痢,通過(guò)這個(gè)訪問(wèn)器可以卸載資產(chǎn)呀洲。通過(guò) IAssetAccessor 接口,使用者可以獲取資產(chǎn)路徑啼止、資產(chǎn)對(duì)象(如果已經(jīng)加載完成)以及其狀態(tài)道逗。

一般情況下,任何使用某一資產(chǎn)的代碼献烦,都應(yīng)通過(guò) LoadAsset 獲得一個(gè)該資產(chǎn)的訪問(wèn)器滓窍。資產(chǎn)和訪問(wèn)器是一對(duì)多的關(guān)系。

4.2. 卸載接口

AssetService 提供 UnloadAsset 方法來(lái)(從邏輯上)卸載資產(chǎn)巩那。

void UnloadAsset(IAssetAccessor assetAccessor);

卸載資產(chǎn)時(shí)吏夯,只需要傳入 AA 即可。要注意即横,一個(gè)資產(chǎn)訪問(wèn)器只允許卸載一次噪生。卸載之后,就不可再使用/引用這個(gè) AA 對(duì)象东囚,否則可能造成很難查找的 bug跺嗽。

4.3. 內(nèi)部實(shí)現(xiàn)的基本數(shù)據(jù)模型

AssetService 內(nèi)部,用資產(chǎn)緩存(AssetService.Loader.AssetCache 內(nèi)部類页藻,簡(jiǎn)稱 ACache)來(lái)描述一個(gè)資產(chǎn)桨嫁,用資產(chǎn)包緩存(AssetService.Loader.ResourceCache 內(nèi)部類,簡(jiǎn)稱 RCache)來(lái)描述一個(gè)資產(chǎn)包惕橙。這兩種緩存內(nèi)部都保存了自己代表的資產(chǎn)(包)的引用計(jì)數(shù)瞧甩。

首先,一個(gè) ACache 可對(duì)應(yīng)多個(gè)資產(chǎn)訪問(wèn)器弥鹦。每個(gè) AA 都綁定一個(gè) ACache肚逸,ACache 的狀態(tài)變化會(huì)反應(yīng)到訪問(wèn)器中爷辙。

其次,ACache 內(nèi)部會(huì)記錄它所代表的資產(chǎn)依賴于哪些其他資產(chǎn)和資產(chǎn)包(從索引文件 PR 中獲得)朦促,這些信息用來(lái)維護(hù) ACache 和 RCache 的引用計(jì)數(shù)膝晾,最終決定資產(chǎn)和資產(chǎn)包的何時(shí)釋放。這里要注意务冕,單獨(dú)看 ACache 的時(shí)候血当,它們構(gòu)成有向無(wú)環(huán)圖(即不允許資產(chǎn)間的依賴構(gòu)成環(huán)路)。而即使有資產(chǎn)包分組間的依賴關(guān)系限制禀忆,和資產(chǎn)間不允許依賴成環(huán)路的限制臊旭,RCache 之間仍然可能構(gòu)成環(huán)路,如下圖所示(實(shí)現(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 實(shí)際上都有相應(yīng)的對(duì)象池來(lái)管理可帽,以便減少運(yùn)行時(shí)的 GC Alloc娄涩。

4.4. 加載資產(chǎn)的過(guò)程

當(dāng)嘗試(通過(guò)文件路徑)加載一個(gè)資產(chǎn)的時(shí)候(即調(diào)用 AssetService.LoadAsset 方法時(shí)),如果沒(méi)有相應(yīng)的 ACache 對(duì)象蘑拯,則從對(duì)象池獲取一個(gè)或創(chuàng)建一個(gè)钝满;否則兜粘,這資產(chǎn)應(yīng)該已經(jīng)被要求加載過(guò)申窘,直接使用已有的 ACache 對(duì)象即可。不論哪種情況孔轴,一個(gè) AA 將和這個(gè) ACache 綁定(并增加 ACache 的引用計(jì)數(shù)使之一定為正的)并同步返回剃法。

ACache 初始化的時(shí)候,會(huì)做以下事情:

  • 遞歸的初始化它依賴的資產(chǎn)的 ACache(如果需要的話)路鹰,增加后者的引用計(jì)數(shù)贷洲,并觀察后者的狀態(tài)變化。由于 ACache 構(gòu)成有向無(wú)環(huán)圖晋柱,所以簡(jiǎn)單遞歸即可完成這步操作优构。
  • 初始化自身指向的資產(chǎn)所在的資產(chǎn)包的 RCache 對(duì)象(如果需要的話),增加后者的引用計(jì)數(shù)雁竞,并作為后者狀態(tài)變化的觀察者钦椭。
  • 從自身所屬資產(chǎn)包的 RCache 對(duì)象出發(fā)拧额,在 RCache 構(gòu)成的圖結(jié)構(gòu)中做遍歷,增加過(guò)程中每個(gè) RCache 的引用計(jì)數(shù)彪腔。

由于依賴關(guān)系相關(guān)問(wèn)題都在 ACache 中處理侥锦,RCache 的業(yè)務(wù)相對(duì)簡(jiǎn)單,只是負(fù)責(zé)自身指向的資產(chǎn)包的
加載和發(fā)送狀態(tài)變化的通知給觀察者德挣。

ACache 會(huì)等待自己代表的資產(chǎn)所屬的資產(chǎn)包的 RCache 加載完成恭垦,以及自己依賴的其他 ACache 加載完成,之后再加載自身代表的資產(chǎn)。于是窟赏,只要一個(gè) ACache 加載完成(其資產(chǎn)對(duì)象對(duì)所有綁定到自身的 AA 都已可用)囱晴,它所依賴的(顯式打資產(chǎn)包的)資產(chǎn)都加載完成了,于是相關(guān)聯(lián)的資產(chǎn)包也是加載完成了的建芙。

使用者需要注意:

  • 本資產(chǎn)系統(tǒng)中,加載失敗即為錯(cuò)誤情況懂扼,不可繼續(xù)使用禁荸。使用者在加載一個(gè)資產(chǎn)時(shí),需要確定它是可用的阀湿,比如資產(chǎn)本身是否存在赶熟、所在資產(chǎn)包分組是否更新完畢等。
  • 某些 Android 設(shè)備上陷嘴,文件 IO 很容易出現(xiàn)問(wèn)題映砖,盡管 Unity 層的實(shí)現(xiàn)(ResourceLoadingTaskImpl 類)增加了重試機(jī)制,仍然可能在從文件創(chuàng)建資產(chǎn)包的時(shí)候失斣职ぁ(連續(xù)失敗多次)邑退。目前只能降低同時(shí)加載的資產(chǎn)包的數(shù)量限制來(lái)減少出問(wèn)題的概率。

4.5. 卸載資產(chǎn)的過(guò)程

卸載資產(chǎn)時(shí)(AssetService.UnloadAsset 方法)劳澄,使用者進(jìn)行的操作實(shí)際上是歸還 AA 對(duì)象地技,歸還時(shí)不需要在意真實(shí)的資產(chǎn)是否仍處于正在被加載的狀態(tài)。資產(chǎn)系統(tǒng)會(huì)清理 AA 內(nèi)部保存的回調(diào)(通過(guò) AssetService.LoadAsset 方法傳入)秒拔,以防止在 AA 被完全清理之前恰好有回調(diào)發(fā)生莫矗。此時(shí)對(duì)于使用者,這個(gè) AA 對(duì)象已經(jīng)失效砂缩,不應(yīng)在以任何方式引用或使用它作谚。后面系統(tǒng)進(jìn)行輪詢的時(shí)候會(huì)回收或丟棄被卸載的 AA 對(duì)象。依前述 AA, ACache, RCache 之間的關(guān)系庵芭,相關(guān)的 ACache 和 RCache 的引用計(jì)數(shù)會(huì)減少妹懒。

如果一個(gè) ACache 或 RCache 的引用計(jì)數(shù)減少到 0,它將進(jìn)入一個(gè)集合双吆,以便進(jìn)行清理眨唬。真正清理將也在系統(tǒng)輪詢時(shí)進(jìn)行滔悉,主要步驟是:

  • 清理被歸還的 AA 對(duì)象。
  • 按資產(chǎn)間依賴關(guān)系单绑,遞歸清理引用為 0 的 ACache回官。因?yàn)?Unity 實(shí)際上不允許取消加載資產(chǎn)的操作,所以如果 ACache 指向的資產(chǎn)正在被加載搂橙,就暫緩清理歉提。注意,雖然清理了 ACache 對(duì)象区转,但不會(huì)真的卸載單個(gè)資產(chǎn)苔巨,這算是一種實(shí)現(xiàn)選擇。
  • 隔一段時(shí)間废离,或者使用者要求清理時(shí)侄泽,如果引用計(jì)數(shù)為 0 的這些 RCache 中,其指向的資產(chǎn)包均不處于加載狀態(tài)蜻韭,則將它們一同卸載悼尾。這時(shí)候 Unity 層的實(shí)現(xiàn)部分是會(huì)真實(shí)調(diào)用AssetBundle.Unload(true) 方法,將資產(chǎn)包真正卸載肖方。

對(duì)引用計(jì)數(shù)為 0 的資產(chǎn)包的同時(shí)卸載規(guī)則闺魏,主要是為了保障,彼此存在依賴關(guān)系的資產(chǎn)包會(huì)被一起卸載掉俯画,否則可能出現(xiàn)一些很難查明的資產(chǎn)丟失 bug析桥。

4.6. 資產(chǎn)包的規(guī)劃

一個(gè)相對(duì)獨(dú)立的功能,從直覺(jué)上說(shuō)艰垂,可以打成一個(gè)或多個(gè)放在一個(gè)分組中的資產(chǎn)包泡仗。實(shí)際操作中,在一個(gè)功能內(nèi)部猜憎,經(jīng)常是按文件夾來(lái)分割資產(chǎn)包的娩怎,而文件夾又經(jīng)常是按資產(chǎn)類型分的。

考慮一個(gè)問(wèn)題:如果一個(gè)貼圖文件夾中有很多貼圖拉宗,在同一個(gè)功能的兩個(gè)不同界面 p, q 上使用峦树,由于這個(gè)文件夾打在一個(gè)資產(chǎn)包中,它只會(huì)作為一個(gè)總體釋放旦事。界面 p 可能是掛在游戲主界面上的,長(zhǎng)期存在急灭,只使用了少量貼圖姐浮;而界面 q 是這個(gè)功能的主界面,使用了大量貼圖葬馋。在運(yùn)行時(shí)卖鲤,p 的生命期明顯比 q 長(zhǎng)肾扰,一旦加載了 q 使用的貼圖資產(chǎn),只是關(guān)閉和銷毀 q蛋逾,是釋放不掉 q 使用的這些貼圖的集晚。直到 p 也被銷毀,這些貼圖才會(huì)一并被卸載区匣。如果有很復(fù)雜的資產(chǎn)包間的依賴關(guān)系偷拔,這個(gè)釋放來(lái)得可能很晚。

可以通過(guò)按“生命期”劃分資產(chǎn)包(從文件夾層面就可以這樣做)亏钩,以及簡(jiǎn)化資產(chǎn)包之間的依賴關(guān)系來(lái)規(guī)避這樣的問(wèn)題莲绰。

5. 編輯器

5.1. 資產(chǎn)包組織器

編輯器層面提供了一個(gè)資產(chǎn)包組織器類 AssetBundleOrganizer 來(lái)配置將哪些資產(chǎn)打入哪些資產(chǎn)包,并配有一個(gè)簡(jiǎn)單的可視化工具(AssetBundleOrganizerEditorWindow)來(lái)進(jìn)行編輯姑丑。

組織器可視化工具的功能大致如下:

  • 左數(shù)第一欄為資產(chǎn)根目錄(可以有多個(gè))蛤签,設(shè)置將哪些目錄視為根目錄并從中讀取資產(chǎn),以及讀取什么類型的資產(chǎn)栅哀。
  • 左數(shù)第二欄為資產(chǎn)目錄震肮,森林結(jié)構(gòu),每個(gè)資產(chǎn)根目錄下的資產(chǎn)在為一棵樹(shù)留拾。
  • 左數(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)容屹篓。

此外疙渣,組織器還支持一個(gè)忽略某些資產(chǎn)的標(biāo)簽(AssetBundleOrganizer.IgnoreAssetLabel 屬性),給資產(chǎn)文件加上指定的標(biāo)簽(Label)堆巧,組織器將忽略這些資產(chǎn)妄荔,從而不會(huì)顯示將它們打入資產(chǎn)包。

組織器會(huì)將信息存放在一個(gè) xml 文件中谍肤,如上圖左下角的 Config path 所示啦租。對(duì)于規(guī)模較小的項(xiàng)目,直接用這個(gè)可視化工具也許就夠了荒揣。但如果項(xiàng)目規(guī)模較大篷角,則建議使用 AssetBundleOrganizer 提供的 API 來(lái)編寫“規(guī)則”代碼,來(lái)動(dòng)態(tài)生成這些內(nèi)容系任。

5.2. 資產(chǎn)包信息提供器

資產(chǎn)包信息提供器由類 AssetBundleInfosProvider 實(shí)現(xiàn)恳蹲,用于將組織器中的數(shù)據(jù)轉(zhuǎn)換成構(gòu)建資產(chǎn)包可用的數(shù)據(jù)虐块。譬如,資產(chǎn)包組織器中可以將某個(gè)目錄分配到某個(gè)資產(chǎn)包中嘉蕾,但是實(shí)際構(gòu)建資產(chǎn)包需要將目錄中的資產(chǎn)文件和資產(chǎn)包對(duì)應(yīng)起來(lái)贺奠。資產(chǎn)包信息提供器就能進(jìn)行此轉(zhuǎn)換。此外错忱,還可以檢測(cè)(打包用的)資產(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)包的過(guò)程(方法 BuildPlatform)航背。主要步驟如下:

  • 通過(guò)資產(chǎn)包組織器和信息提供器喉悴,得到資產(chǎn)和資產(chǎn)包的對(duì)應(yīng)關(guān)系,構(gòu)造 Unity 的 AssetBundleBuild 列表玖媚。
  • 調(diào)用 Unity 的方法箕肃,構(gòu)建資產(chǎn)包,獲得 manifest 文件今魔。
  • 利用 manifest 文件和其他數(shù)據(jù)勺像,生成在索引文件中需要的資產(chǎn)包信息,如分組错森、CRC-32 校驗(yàn)和吟宦、Unity 生成的 Hash 值等。
  • 生成 Client, ClientFull, Server 文件夾及相應(yīng)的索引文件涩维。

使用者可以通過(guò)實(shí)現(xiàn) IAssetBundleBuilderHandler 接口來(lái)指定構(gòu)建各個(gè)階段的回調(diào)殃姓。例如:使用 Lua 腳本的項(xiàng)目可以在自己的 IAssetBundleBuilderHandler 實(shí)現(xiàn)中,用 OnPreBeforeBuild 回調(diào)來(lái)給 .lua 后綴的文件改名為 .txt 之類的后綴瓦阐,以便能被 Unity 識(shí)別為文本資產(chǎn)(Text asset)蜗侈;同樣,在 OnBuildSuccessOnBuildFailure 回調(diào)中將重命名的文件復(fù)原睡蟋。

6. 局限性

  • 目前對(duì)已經(jīng)發(fā)起的資產(chǎn)加載調(diào)用是沒(méi)有優(yōu)先級(jí)的踏幻,內(nèi)部又有一些 Hash 存儲(chǔ),不能保證實(shí)際的加載順序和發(fā)起加載調(diào)用的順序一致戳杀。
  • 內(nèi)存中同時(shí)有資產(chǎn)間的依賴關(guān)系和資產(chǎn)包間的依賴關(guān)系该面,不知道是否可以舍棄后者,還能保證邏輯正確信卡,不出現(xiàn)資產(chǎn)丟失的問(wèn)題隔缀。
  • 加載資產(chǎn)名義上是異步,但實(shí)際上有可能是同步返回的坐求。實(shí)際使用時(shí)蚕泽,為了便利起見(jiàn)可以增加中間層。
  • 目前采用“集總式”索引文件桥嗤,可能一次解析的內(nèi)容較多须妻,在游戲啟動(dòng)階段造成一些卡頓現(xiàn)象。
  • 未能支持子資產(chǎn)(Sub-asset)或泛型加載資產(chǎn)泛领。例如:對(duì)圖集(如 Texture Packer 這類插件輸出的)這種類型的資產(chǎn)荒吏,需要用一個(gè) SerializableObject 來(lái)存放其中精靈圖的引用。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末渊鞋,一起剝皮案震驚了整個(gè)濱河市绰更,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锡宋,老刑警劉巖儡湾,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異执俩,居然都是意外死亡徐钠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門役首,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尝丐,“玉大人,你說(shuō)我怎么就攤上這事衡奥〉” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵矮固,是天一觀的道長(zhǎng)失息。 經(jīng)常有香客問(wèn)我,道長(zhǎng)档址,這世上最難降的妖魔是什么盹兢? 我笑而不...
    開(kāi)封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮辰晕,結(jié)果婚禮上蛤迎,老公的妹妹穿的比我還像新娘。我一直安慰自己含友,他們只是感情好替裆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著窘问,像睡著了一般辆童。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惠赫,一...
    開(kāi)封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天把鉴,我揣著相機(jī)與錄音,去河邊找鬼。 笑死庭砍,一個(gè)胖子當(dāng)著我的面吹牛场晶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怠缸,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼诗轻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了揭北?” 一聲冷哼從身側(cè)響起扳炬,我...
    開(kāi)封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搔体,沒(méi)想到半個(gè)月后恨樟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疚俱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年劝术,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片计螺。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夯尽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出登馒,到底是詐尸還是另有隱情匙握,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布陈轿,位于F島的核電站圈纺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏麦射。R本人自食惡果不足惜蛾娶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望潜秋。 院中可真熱鬧蛔琅,春花似錦、人聲如沸峻呛。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钩述。三九已至寨躁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牙勘,已是汗流浹背职恳。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人放钦。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓色徘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親最筒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贺氓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容