這一章來說說AssetBundles锭魔,介紹下它的基礎系統(tǒng)例证,還有一些和AssetBundles進行交互的核心API路呜。尤其是AssetBundles的加載迷捧、卸載,以及從AssetBundles中加載胀葱、卸載指定的Asset和Objects漠秋。
3.1. 概述
AssetBundles系統(tǒng)提供了一種Unity可以索引的打包格式。這是為了提供了一個可以和Unity序列化系統(tǒng)兼容的數(shù)據(jù)傳遞(交付)方法抵屿。AssetBundles是Unity用來在安裝后交付和更新非代碼內(nèi)容的主要工具庆锦。它使得開發(fā)者可以減少安裝包中的資源大小、減少運行時的內(nèi)存壓力轧葛,還可以為終端設備做優(yōu)化:選擇性的加載內(nèi)容搂抒。
了解AssetBundles的工作方式是構建成功Unity項目(移動設備上)的必要因素。
3.2. AssetBundle中都有些什么尿扯?
一個AssetBundle中包含兩個部分:文件頭和數(shù)據(jù)段求晶。
文件頭是在構建AssetBundle時由Unity生成的。它包含了AssetBundle的信息衷笋,比如:AssetBundle的標識芳杏,AssetBundle是否被壓縮,還有一個清單辟宗。
這個清單是一個查詢表爵赵,以對象的名字為鍵。每一項提供一個字節(jié)的索引來標識在AssetBundle的數(shù)據(jù)段中給定對象的位置泊脐。在大多數(shù)平臺,這個查詢表使用STL的std::multimap實現(xiàn)的容客。雖然具體算法依賴不同平臺STL實現(xiàn)方式氛悬,但大多數(shù)都是平衡樹的一種。Windows和OSX衍生系統(tǒng)(包括IOS)使用的是紅黑樹耘柱。所以如捅,構建清單的時間和AssetBundles中資源數(shù)量,不是線性關系那么樂觀调煎。
數(shù)據(jù)段包含本AssetBundle中序列化的Assets原始數(shù)據(jù)镜遣。如果數(shù)據(jù)段被壓縮,那么LZMA算法將會被應用到所有序列化的字節(jié),也就是先序列化所有的assets悲关,然后對整個字節(jié)數(shù)組進行壓縮谎僻。
Unity5.3之前,AssetBundle中的Objects不會被單獨壓縮寓辱。因此艘绍,5.3之前的Unity若是想從壓縮的AssetBundle中讀取一個或多個Objects就需要將整個AssetBundle解壓縮。一般來說秫筏,為了提高后續(xù)在同一個AssetBundle加載的性能诱鞠,Unity會緩存一個AssetBundle解壓后的副本。
Unity5.3添加了LZ4壓縮選項这敬。由LZ4壓縮選項構建的AssetBundles將會單獨壓縮其中的Objects航夺,來讓Unity在硬盤上存儲壓縮的AssetBundles。這也允許Unity單獨解壓Objects崔涂,而無須解壓整個AssetBundle阳掐。
3.3. AssetBundle管理器
Unity在Bitbucket上開發(fā)并維護了一個AssetBundle管理器的實現(xiàn)方式作為參考。這個管理器采用了許多這個章節(jié)中描述的概念和API冷蚂。為那些必須將AssetBundles整合到資源管理工作流程中的項目缭保,提供了一個非常有用的起點。
“模擬模式”就是其中一個引人注意的特性蝙茶。當在Unity編輯器中激活艺骂,這個模式將AssetBundle中Assets的加載請求重定位至工程文件夾/Assets/下,當然這一切對開發(fā)者來說都是透明的尸闸。這樣就不需要開發(fā)者在開發(fā)階段不停地重新構建AssetBundles了彻亲。
AssetBundle管理器是個開源項目,你可以在這找到吮廉。
3.4.加載AssetBundles
在Unity5中苞尝,AssetBundles可以通過四個不同的API加載。這四個API根據(jù)下面兩個條件情況將有不同的行為:
- AssetBundle是否被壓縮宦芦,是使用LZMA壓縮還是LZ4壓縮
- AssetBundle在什么平臺上加載
這四個API是:
- AssetBundle.LoadFromMemoryAsync
- AssetBundle.LoadFromFile
- WWW.LoadFromCacheOrDownload
- UnityWebRequest的DownloadHandlerAssetBundle(至少是Unity5.3)
3.4.1. AssetBundle.LoadFromMemoryAsync
Unity建議不要使用這個函數(shù)
Unity3.3.3更新:這個API在Unity5.3.3.中重命名了宙址。在Unity5.3.2(及之前版本),這個API是AssetBundle.CreateFromMemory调卑。功能沒有發(fā)生變化抡砂。
AssetBundle.LoadFromMemoryAsync從托管代碼的字節(jié)數(shù)組中加載AssetBundle(C#中的byte[]).它總是將源數(shù)據(jù)從托管代碼的字節(jié)數(shù)組中拷貝到一個新分配連續(xù)的本地內(nèi)存塊中.如果AssetBundle是LZMA壓縮格式,它將在拷貝時將AssetBundle解壓恬涧。未壓縮和LZ4壓縮的AssetBundles將逐字拷貝注益。
這個API所消耗的內(nèi)存峰值至少是AssetBundle大小的兩倍:一份是API所創(chuàng)建的本地內(nèi)存中的副本,一份是傳遞給API的托管字節(jié)數(shù)組溯捆。通過這個API從AssetBundle中加載的Assets丑搔,因此會在內(nèi)存中出現(xiàn)3次:一次是從托管代碼的字節(jié)數(shù)組,一次是AssetBundle的本地內(nèi)存副本,還有一次是GPU或系統(tǒng)內(nèi)存中啤月。
3.4.2. AssetBundle.LoadFromFile
Unity5.3更新:這個API在Unity5.3中重命名了煮仇。在Unity5.2及之前版本,這個API是AssetBundle.CreateFromFile谎仲。它的功能未變浙垫。
AssetBundle.LoadFromFile是一個用來從本地(一塊硬盤或一個SD卡)加載未壓縮AssetBundle的高效API。如果AssetBundles是未壓縮的或LZ4壓縮郑诺,這個API的行為如下:
移動設備:API只會加載AssetBundle的文件頭夹姥,不會從硬盤中加載剩余的數(shù)據(jù)。AssetBundle的Objects只有被加載方法調(diào)用(比如AssetBundle.Load)间景,或?qū)嵗齀D被間接引用時佃声,才會被加載艺智。在這個情境中沒有額外的內(nèi)存被消耗倘要。
Unity編輯器:這個API將會把整個AssetBundle加載到內(nèi)存中,就如AssetBundle.LoadFromMemoryAsync被調(diào)用一樣十拣,將所有字節(jié)從硬盤中讀出封拧。如果項目在Unity編輯器中進行評估,這個API在AssetBundle加載時夭问,將會引起內(nèi)存峰值泽西。不過這不會影響實機上的表現(xiàn),在實機上需要重新測試下缰趋,確定會遇到峰值問題再進行補救行為捧杉。
備注:對LZMA壓縮的AssetBundles調(diào)用AssetBundle.LoadFromFile將會失敗。
3.4.3. WWW.LoadFromCacheOrDownload
WWW.LoadFromCacheOrDownload是一個從遠端服務器或本地存儲加載Objects的API秘血。本地文件可以通過添加file://URL進行加載味抖。如果AssetBundle已經(jīng)在Unity緩存中了,這個API的處理方式和AssetBundle.LoadFromFile一模一樣灰粮。
如果AssetBundle還未被緩存仔涩,WWW.LoadFromCacheOrDownload將會從他的源地址讀取AssetBundle。如果AssetBundle被壓縮了粘舟,將會在工作線程中解壓并寫入緩存熔脂。否則,他將通過工作線程被直接寫入緩存柑肴。
一旦AssetBundle被緩存霞揉,WWW.LoadFromCacheOrDownload將從緩存中加載頭信息,解壓AssetBundle晰骑。接著API的表現(xiàn)與通過AssetBundle.LoadFromFile加載AssetBundle一致适秩。
備注:當數(shù)據(jù)被解壓并通過一個固定大小的緩沖區(qū)寫入緩存,WWW對象將在本地內(nèi)存中保持一份完整的AssetBundle字節(jié)副本。保留這個額外的AssetBundle副本是為了支持WWW.bytes這個屬性隶症。
由于在WWW對象中緩存一份AssetBundle內(nèi)容字節(jié)的內(nèi)存開銷政模,建議所有使用WWW.LoadFromCacheOrDownload的開發(fā)者確保他們的AssetBundles小一些:最多幾兆。接下來還有個建議蚂会,對那些對內(nèi)存有限制的平臺(比如移動設備)的開發(fā)者:為了防止內(nèi)存峰值淋样,確保同一時間代碼中只下載一個AssetBundle。對于AssetBundle大小的更多討論胁住,見[AssetBundle使用模式](AssetBundle usage patterns)章節(jié)中的Asset分配策略部分趁猴。
備注:每調(diào)用一次這個API將會創(chuàng)建一個新的工作線程。當多次調(diào)用這個API時彪见,注意此時創(chuàng)建了過多的線程儡司。如果超過5-10個AssetBundles需要下載,建議你的代碼寫成可以確保只有少量的AssetBundle在同時下載余指。
3.4.4. AssetBundleDownloadHandler
UnityWebRequest在Unity5.3中引入到移動平臺上捕犬,相比于WWW 它提供了另一種更靈活的選擇。UnityWebRequest允許開發(fā)者指定如何處理下載的數(shù)據(jù)酵镜,并且讓開發(fā)者避免不必要的內(nèi)存使用碉碉。通過UnityWebRequest下載一個AssetBundle最簡單的方法就是調(diào)用UnityWebRequest.GetAssetBundle。
根據(jù)這篇文章的主題淮韭,我們最關心的類就是DownloadHandlerAssetBundle垢粮。使用時,這個DownloadHandler表現(xiàn)的和WWW.LoadFromCacheOrDownload一樣靠粪。使用一個工作線程蜡吧,他將下載的數(shù)據(jù)放入一個固定大小的緩沖,然后根據(jù)DownloadHandler的配置占键,將緩沖數(shù)據(jù)移至一個臨時存儲或AssetBundle緩存昔善。LZMA壓縮的AssetBundles在下載時將會被解壓,然后緩存起來捞慌。
所有的這些操作都發(fā)生在本地代碼耀鸦,避免了擴大托管堆的風險。而且啸澡,這個DownloadHandler沒有在本地代碼中保存下載字節(jié)的副本袖订,這樣減少了下載AssetBundle帶來的內(nèi)存占用。
當下載結束了嗅虏,可以通過DownloadHandler的assetBundle屬性洛姑,訪問下載好的AssetBundle,就像對下載好的AssetBundle調(diào)用AssetBundle.LoadFromFile一樣皮服。
UnityWebRequest也支持像WWW.LoadFromCacheOrDownload一樣的緩存方式楞艾。如果提供給UnityWebRequest對象的緩存信息中参咙,正在請求的AssetBundle已經(jīng)在Unity緩存中了,那么AssetBundle將會立即可用硫眯,并且這個API將會進行和AssetBundle.LoadFromFile一樣的操作蕴侧。
備注:Unity的AssetBundle緩存在WWW.LoadFromCacheOrDownload和UnityWebRequest之間是共享的。在一個API中下載的任何AssetBundle在另一個API中也是有效的两入。
備注:不像WWW净宵,UnityWebRequest系統(tǒng)它內(nèi)部維護一個工作線程池,還有一個內(nèi)部任務系統(tǒng)來確保開發(fā)者不會同時啟動過量的下載裹纳。線程池的大小目前無法配置择葡。
3.4.5. 建議
一般來說,應該盡可能的采用AssetBundle.LoadFromFile剃氧。這個API在速度敏储,硬盤和運行內(nèi)存的使用上是效率最高的。
對于那些必須下載和對AssetBundles進行打補丁的項目來說朋鞍,強烈建議:在Unity5.3以及更新的版本上使用UnityWebRequest已添,而那些使用Unity5.2或更老的項目則使用WWW.LoadFromCacheOrDownload。在下一章的發(fā)布這節(jié)中我們將詳細描述番舆,如何將AssetBundle打包在在項目的安裝包中酝碳。
當使用WWW.LoadFromCacheOrDownload時矾踱,強烈建議你確保項目的AssetBundles小于項目最大內(nèi)存預算的2-3%恨狈,來防止應用由于內(nèi)存使用峰值被強行終止。對于大部分項目來說呛讲,AssetBundles的文件大小應該不超過5MB禾怠,并且同時下載的AssetBundles不超過1-2個。
不論是用WWW.LoadFromCacheOrDownload還是UnityWebRequest贝搁,確保下載部分的代碼在加載完AssetBundle后適時調(diào)用Dispose吗氏。還有一種選擇:C#的using聲明是最方便的方法來確保一個www或UnityWebRequest被安全Dispose掉。
對于那種大型團隊和特殊緩存及下載需求的項目來說雷逆,定制一個Downloader是有必要的弦讽。自己寫個Downloader是一項大工程,并且自己寫的這些downloader應該和AssetBundle.LoadFromFile兼容膀哲。詳情請見下篇的發(fā)布部分
3.5. 從AssetBundles中加載Assets
UnityEngine.Objects可以通過三個不同的API從AssetBundles中加載往产,這三個方法分別是AssetBundle對象的:LoadAsset,LoadAllAssets和LoadAssetWithSubAssets。這些API也有異步形式某宪,以-Async結尾:LoadAssetAsync, LoadAllAssetsAsync和LoadAssetWithSubAssetsAsync仿村。
在Unity5.1和更老的版本中,同步接口總會比異步接口快兴喂,最少快一幀蔼囊。因為在Unity5.2之前焚志,所有異步接口每幀最多加載一個對象。這意味著LoadAllAssetsAsync和LoadAssetWithSubAssetAsync都會明顯的慢于相應的同步接口畏鼓。在Unity5.2中這種行為被修正了酱酬。異步加載每幀將會加載多個Object,直到時間片的限制云矫。查看底層加載細節(jié)這部分岳悟,來了解造成這種行為的底層技術原因,和關于時間片的更多細節(jié)泼差。
當加載多個獨立的UnityEngine.Object時贵少,可以使用LoadAllAssets。這個接口應該只在同一個AssetBundle中多數(shù)或全部Objects需要被加載時再去調(diào)用堆缘。相比于其他兩個接口滔灶,LoadAllAssets比多次單獨調(diào)用LoadAssets要快一點點。因此吼肥,如果需要同時加載大量assets录平,但是這些assets又不超過AssetBundle內(nèi)容的三分之二,可以考慮將AssetBundle細分為多個小點的包缀皱,然后使用LoadAllAssets斗这。
當去加載一個復雜的Asset(包含許多內(nèi)嵌的Object,比如一個FBX模型內(nèi)嵌動畫啤斗,或一個圖集表箭,內(nèi)嵌多個精靈)時,可以使用LoadAssetWithSubAssets钮莲。如果這些需要加載的Objects都來自同一個Asset免钻,同時這個AssetBundle中還有很多其他不相關的Objects,那就用這個接口崔拥。
對于其他情況极舔,就用LoadAsset或LoadAssetAsync吧。
3.5.1. 底層加載細節(jié)
UnityEngine.Object的加載不是在主線程進行的:Object的數(shù)據(jù)是在工作線程從存儲中讀取出來的链瓦。Unity系統(tǒng)中任何線程不敏感的部分都會放在工作線程中拆魏。比如,從mesh中創(chuàng)建VBO慈俯,紋理解壓渤刃,等。
在Unity5.3之前的版本肥卡,Object是一個個加載的溪掀,并且Object的某些部分只能在主線程中加載。這稱為“整合”步鉴。當工作線程加載完Object數(shù)據(jù)揪胃,它將暫停璃哟,去把新加載好的Object整合到主線程中,在此期間工作線程一直保持著暫停狀態(tài)喊递,不會去加載下一個Object随闪,直到主線程整合結束。
從Unity5.3起骚勘,Object的加載是并行的铐伴。多個Objects可以在工作線程中反序列化,處理和整合俏讹。當一個Object完成加載当宴,它的Awake函數(shù)將被調(diào)用,Object在下一幀對于Unity系統(tǒng)來說就變成了有效對象泽疆。
同步方法AssetBundle.Load將暫停主線程户矢,直到Object加載結束。在5.3版本之前殉疼。異步函數(shù)AssetBundle.LoadAsync在將Objects整合到主線程時梯浪,才會暫停主線程。加載對象會有一個時間片瓢娜,來確保Object整合不會占用每幀幀太多的時間(毫秒級別)挂洛。具體這個時間通過Application.backgroundLoadingPriority屬性去設置。單位是毫秒眠砾。
- ThreadPriority.Hight: 最多50毫秒每幀
- ThreadPriority.Normal: 最多10毫秒每幀
- ThreadPriority.BelowNormal: 最多4毫秒每幀
- ThreadPriority.Low: 最多2毫秒每幀
在Unity5.1及更老的版本中虏劲,異步函數(shù)每幀只會整合一個Object。這被認為是一個bug荠藤,并且在Unity5.2的時候已經(jīng)修復了伙单。從Unity5.2起,多個Object可以被加載哈肖,除非對象加載時到了幀率的限制。AssetBundle.LoadAsync完成的總會比相應的同步API慢(假設其他因素一致)念秧,因為在LoadAsync調(diào)用后和object對引擎生效淤井,之間至少需要一幀。
實際測試下來看看其中的差距摊趾。在5.2之前币狠,在某個低端設備上加載大量紋理:同步API需要7毫秒,異步API需要70毫秒砾层。5.2之后漩绵,觀察到的差距近乎為零。
3.5.2. AssetBundle依賴
在Unity5的AssetBundle系統(tǒng)中肛炮,AssetBundles間的依賴止吐,根據(jù)不同的運行環(huán)境宝踪,可以通過兩個不同的API來跟蹤。在Unity編輯器中碍扔,AssetBundle依賴關系可以通過AssetDatabaseAPI去查詢瘩燥。AssetBundle的分配和依賴可以通過AssetImporterAPI訪問和改變。運行時不同,Unity提供一個 ScriptableObject-based AssetBundleManifest 去加載構建AssetBundle時生成的依賴信息厉膀。
當AssetBundle的父AssetBundle(直接引用)中Object有引用其他的AssetBundle中的Object,那么就存在了間接引用關系(這個關系并沒有記錄在清單中)[1]二拐。關于對象間引用的更多信息服鹅,請查看Assets,Objects and Serialization一文中的對象間的引用章節(jié)。
如Assets, Objects and Serialization一文里序列化和實例段落中描述的一樣百新,Object數(shù)據(jù)被糅合在AssetBundle中菱魔,以FileGUID和LocalID作為唯一標識。
Object在他的實例ID被第一次引用時被加載吟孙;Object在其AssetBundle被加載時澜倦,被賦予一個有效的實例ID。由此看來杰妓,加載一個Object之前應該先把其依賴項所在的AssetBundles全都加載了藻治。而并不是說擁有依賴關系的AssetBundle之間必須按順序加載。(譯者注:如果這里有迷惑巷挥,可以先向下看桩卵,例子中有再次提到這句話,結合例子就容易理解一些)Unity不會在一個父AssetBundle加載時倍宾,幫你把其子AssetBundles都自動加載的雏节。
示例:
假設材質(zhì)A引用紋理B。材質(zhì)A被打包至AssetBundle1高职,而紋理B被打包到AssetBundle2钩乍。
在此用例中,從AssetBundle1中加載材質(zhì)A之前怔锌,必須要先加載AssetBundle2寥粹。
這并不意味著AssetBundle2必須要先于AssetBundle1加載,或者紋理B必須顯式的從AssetBundle2中加載埃元。只有從AssetBundle1中加載材質(zhì)A時涝涤,AssetBundle2才需要先加載。
在AssetBundle1加載時岛杀,Unity不會自動加載AssetBundle2阔拳。這必須通過腳本手動完成。上面提到的AssetBundle的API都可以任意用于加載AssetBundles1和AssetBundles2上类嗤,隨意組合糊肠。通過WWW.LoadFromCacheOrDownload加載AssetBundles辨宠,可以任意和通過AssetBundle.LoadFromFile或AssetBundle.LoadFromMemoryAsync加載AssetBundles組合在一起。
3.5.3. AssetBundle清單(manifests)
當通過接口BuildPipeline.BuildAssetBundles執(zhí)行AssetBundle的構建流程時罪针,Unity會序列化一個對象彭羹,它包含每個AssetBundle的依賴信息。這個數(shù)據(jù)存儲在一個單獨的AssetBundle中泪酱,而這個AssetBundle只包含一個AssetBundleManifest類型的對象派殷。
這個Asset所在的AssetBundle和構建的AssetBundles在同一個目錄中,其(這個Asset所在的AssetBundle)名稱就是這個目錄名墓阀。如果項目將AssetBundles構建至(工程目錄)/build/Client毡惜,那么包含清單的AssetBundle將會保存為(工程目錄)/build/Client/Client.manifest。(譯者注:應該沒有manifest的后綴名吧斯撮?)
包含清單的這個AssetBundle可以被加載经伙,緩存,卸載勿锅。就像其他的AssetBundle一樣帕膜。
AssetBundleManifest對象本身提供GetAllAssetBundles接口,用來羅列所有和清單一起構建的AssetBundle溢十。還有兩個方法垮刹,用來查詢指定AssetBundle的依賴:
AssetBundleManifest.GetAllDependencies返回AssetBundle所有的依賴,包括迭代依賴张弛。
翻譯:莫銘
原文地址:AssetBundle fundamentals
AssetBundleManifest.GetDirectDependencies只返回AssetBundle直接依賴荒典。
注意這些API都會分配字符串數(shù)組。不要多用吞鸭,尤其是在性能緊張的時候寺董。
3.5.4. 建議
在用戶進入應用的性能嚴峻區(qū)(比如游戲主場景或世界)之前,盡可能多的加載所需的對象刻剥,這被認為是最佳實踐遮咖。這在移動平臺上尤為重要,因為訪問本地存儲很慢透敌,并且在play-time時加載和卸載對象引起的內(nèi)存變更盯滚,會引起垃圾回收機制。
對于那些必須在應用交互時加載和卸載對象的項目酗电,可以看看AssetBundle使用模式一文中的管理已加載的assets段落,來獲取更多關于卸載Objects和AssetBundles的信息内列。
-
這段看不懂撵术,于是按照個人理解寫下的,建議對照原文自行理解话瞧。 ?