翻譯自官方文檔: https://unity3d.com/cn/learn/tutorials/topics/best-practices/assetbundle-fundamentals
本章討論AssetBundles。它介紹了構(gòu)建AssetBundles的基本系統(tǒng),以及用于與AssetBundles進行交互的核心API。具體來說,它討論了AssetBundles本身的加載和卸載以及AssetBundles中特定資產(chǎn)和對象的加載和卸載。
有關(guān)AssetBundles使用的更多模式和最佳實踐,請參閱本系列的下一章。
3.1。概觀
AssetBundle系統(tǒng)提供了一種方法,用于存儲Unity可以索引和序列化的檔案格式的一個或多個文件。AssetBundles是Unity的主要工具,用于在安裝后交付和更新非代碼內(nèi)容。這允許開發(fā)人員提交更小的應(yīng)用程序包,最大程度地減少運行時內(nèi)存壓力,并有選擇地加載針對最終用戶設(shè)備優(yōu)化的內(nèi)容。
了解AssetBundles的工作方式對于為移動設(shè)備構(gòu)建成功的Unity項目至關(guān)重要。有關(guān)AssetBundle內(nèi)容的總體說明,請查看AssetBundle文檔。
3.2。AssetBundle布局
總而言之,一個AssetBundle由兩部分組成:頭部和數(shù)據(jù)段。
標題包含有關(guān)AssetBundle的信息,例如標識符,壓縮類型和清單。清單是一個由對象名稱鍵入的查找表。每個條目都提供一個字節(jié)索引,用于指示在AssetBundle的數(shù)據(jù)段中可以找到給定對象的位置。在大多數(shù)平臺上,這個查找表被實現(xiàn)為一個平衡搜索樹。具體來說,Windows和OSX派生的平臺(包括iOS)采用紅黑樹。因此,構(gòu)建清單所需的時間將隨著AssetBundle內(nèi)資產(chǎn)數(shù)量的增長而線性增加。
數(shù)據(jù)段包含通過序列化AssetBundle中的資產(chǎn)生成的原始數(shù)據(jù)。如果將LZMA指定為壓縮方案,則將壓縮所有序列化資產(chǎn)的完整字節(jié)數(shù)組。如果指定LZ4,單獨資產(chǎn)的字節(jié)將被單獨壓縮。如果不使用壓縮,數(shù)據(jù)段將保持為原始字節(jié)流。
在Unity 5.3之前,對象無法在AssetBundle中單獨壓縮。因此,如果指示5.3版本的Unity版本從壓縮的AssetBundle中讀取一個或多個對象,則Unity必須解壓縮整個AssetBundle。通常,Unity會緩存AssetBundle的解壓縮副本,以提高同一AssetBundle上后續(xù)加載請求的加載性能。
3.3。加載AssetBundles
AssetBundles可以通過五個不同的API加載。這五個API的行為取決于兩個標準是不同的:
AssetBundle是否是LZMA壓縮,LZ4壓縮或未壓縮
AssetBundle正在加載的平臺
這些API是:
AssetBundle.LoadFromMemory(異步可選)
AssetBundle.LoadFromFile(異步可選)
AssetBundle.LoadFromStream(異步可選)
UnityWebRequest的DownloadHandlerAssetBundle
WWW.LoadFromCacheOrDownload(在Unity 5.6或更早版本上)
這些API中的AssetBundle引用可以自由混合。也就是說,使用UnityWebRequest加載的AssetBundles與通過AssetBundle.LoadFromFile或AssetBundle.LoadFromMemoryAsync加載的*AssetBundles *兼容。
3.3.1 AssetBundle.LoadFromMemory(異步)
Unity的建議是不要使用這個API。
AssetBundle.LoadFromMemoryAsync從托管代碼字節(jié)數(shù)組(在C#中的字節(jié)[])中加載一個AssetBundle 。它將始終將來自托管代碼字節(jié)數(shù)組的源數(shù)據(jù)復(fù)制到新分配的連續(xù)本機內(nèi)存塊中。如果AssetBundle是LZMA壓縮的,它將在復(fù)制時解壓縮AssetBundle。未壓縮的和LZ4壓縮的AssetBundles將被逐字復(fù)制。
此API消耗的最大內(nèi)存量至少為AssetBundle的兩倍:由API創(chuàng)建的本地內(nèi)存中的一個副本,以及傳遞給API的托管字節(jié)數(shù)組中的一個副本。因此,從通過此API創(chuàng)建的AssetBundle加載的資產(chǎn)將在內(nèi)存中重復(fù)三次:一次位于托管代碼字節(jié)數(shù)組中,一次位于AssetBundle的本機內(nèi)存副本中,第三次位于GPU或系統(tǒng)內(nèi)存中用于資產(chǎn)本身。
在Unity 5.3.3之前,這個API被稱為****AssetBundle.CreateFromMemory****。它的功能沒有改變。
3.3.2。AssetBundle.LoadFromFile(異步)
AssetBundle.LoadFromFile是一個高效的API,用于從本地存儲器(如硬盤或SD卡)加載未壓縮或LZ4壓縮的AssetBundle。
在桌面獨立,控制臺和移動平臺上,API將僅加載AssetBundle的標題,并將剩余的數(shù)據(jù)保留在磁盤上。AssetBundle的對象將在加載方法(例如AssetBundle.Load)被調(diào)用或InstanceID被解除引用時按需加載。在這種情況下不會消耗過量的內(nèi)存。在Unity編輯器中,API會將整個AssetBundle加載到內(nèi)存中,就好像從磁盤讀取字節(jié)并使用AssetBundle.LoadFromMemoryAsync一樣。如果在Unity編輯器中對項目進行概要分析,則此API可能導(dǎo)致在AssetBundle加載期間出現(xiàn)內(nèi)存尖峰。這不應(yīng)該影響設(shè)備性能,并且應(yīng)該在采取補救措施之前在設(shè)備上重新測試這些尖峰。
注意:在Unity 5.3或更早版本的Android設(shè)備上,嘗試從Streaming Assets路徑加載AssetBundles時,此API將失敗。Unity 5.4中已解決該問題。有關(guān)更多詳細信息,請參閱AssetBundle使用模式章節(jié)中的分發(fā) - 隨項目一起提供的部分。
在Unity 5.3之前,這個API被稱為****AssetBundle.CreateFromFile****。其功能尚未更改。
3.3.3。AssetBundleDownloadHandler
該UnityWebRequest API允許開發(fā)人員指定統(tǒng)一究竟應(yīng)該如何處理下載的數(shù)據(jù),并允許開發(fā)者以消除不必要的內(nèi)存使用情況。使用UnityWebRequest下載AssetBundle的最簡單方法是調(diào)用UnityWebRequest.GetAssetBundle。
就本指南而言,感興趣的類是DownloadHandlerAssetBundle。使用工作線程,它會將下載的數(shù)據(jù)流式傳輸?shù)焦潭ù笮〉木彌_區(qū),然后根據(jù)下載處理程序的配置方式將緩沖的數(shù)據(jù)緩沖到臨時存儲或AssetBundle緩存。所有這些操作都以本機代碼形式進行,消除了擴展托管堆的風險。此外,該下載處理程序并沒有把所有下載的字節(jié)的本機代碼副本,進一步降低了下載的AssetBundle的內(nèi)存開銷。
LZMA壓縮的AssetBundles將在下載過程中進行解壓縮并使用LZ4壓縮進行緩存。通過設(shè)置Caching.CompressionEnabled可以更改此行為。
當下載完成后,assetBundle下載處理程序的屬性提供下載的AssetBundle,仿佛AssetBundle.LoadFromFile已經(jīng)呼吁下載AssetBundle。
如果將緩存信息提供給UnityWebRequest對象,并且所請求的AssetBundle已經(jīng)存在于Unity的緩存中,則AssetBundle將立即變?yōu)榭捎茫⑶掖薃PI將以與AssetBundle.LoadFromFile相同的方式運行。
在Unity 5.6之前,UnityWebRequest系統(tǒng)使用固定的工作線程池和內(nèi)部作業(yè)系統(tǒng)來防止過度的并發(fā)下載。線程池的大小不可配置。在Unity 5.6中,這些保護措施已被刪除,以適應(yīng)更多現(xiàn)代硬件,并允許更快地訪問HTTP響應(yīng)代碼和標頭。
3.3.4。WWW.LoadFromCacheOrDownload
注意:從Unity 2017.1開始,WWW.LoadFromCacheOrDownload只是包裝UnityWebRequest。因此,使用Unity 2017.1或更高版本的開發(fā)人員應(yīng)遷移到UnityWebRequest。WWW.LoadFromCacheOrDownload將在未來版本中棄用。
以下信息適用于Unity 5.6或更早版本。
WWW.LoadFromCacheOrDownload是一個API,允許從遠程服務(wù)器和本地存儲裝載對象。文件可以通過file:// URL從本地存儲中加載。如果AssetBundle存在于Unity緩存中,則此API的行為與AssetBundle.LoadFromFile完全相同。
如果AssetBundle尚未緩存,然后WWW.LoadFromCacheOrDownload將讀取其源AssetBundle。如果AssetBundle被壓縮,它將使用工作者線程解壓縮并寫入緩存。否則,它將通過工作線程直接寫入緩存。一旦AssetBundle被緩存,WWW.LoadFromCacheOrDownload將從緩存的解壓縮的AssetBundle中加載標題信息。然后,API將與使用AssetBundle.LoadFromFile加載的AssetBundle的行為相同。該緩存在WWW.LoadFromCacheOrDownload和UnityWebRequest之間共享。任何通過一個API下載的AssetBundle也將通過其他API提供。
雖然數(shù)據(jù)將通過固定大小的緩沖區(qū)解壓縮并寫入緩存,但WWW對象將在本機內(nèi)存中保留AssetBundle字節(jié)的完整副本。AssetBundle的額外副本保留為支持WWW.bytes屬性。
由于在WWW對象中緩存AssetBundle字節(jié)的內(nèi)存開銷,AssetBundles應(yīng)該保持很小 - 最多幾兆字節(jié)。有關(guān)AssetBundle大小的更多討論,請參閱AssetBundle使用模式一章中的資產(chǎn)分配策略部分。
與UnityWebRequest不同,每次調(diào)用此API都會產(chǎn)生一個新的工作線程。因此,在移動設(shè)備等內(nèi)存有限的平臺上,每次只能使用此API下載一個AssetBundle,以避免內(nèi)存高峰。多次調(diào)用此API時,請小心創(chuàng)建過多的線程。如果需要下載超過5個AssetBundle,請在腳本代碼中創(chuàng)建并管理下載隊列,以確保只有少量AssetBundle下載正在同時運行。
3.3.5。建議
一般情況下,應(yīng)盡可能使用AssetBundle.LoadFromFile。就速度,磁盤使用情況和運行時內(nèi)存使用情況而言,此API是最高效的。
對于必須下載或修補AssetBundles的項目,強烈建議對于使用Unity 5.3或更新版本的項目使用UnityWebRequest,對于使用Unity 5.2或更早版本的項目,則強烈建議使用WWW.LoadFromCacheOrDownload。正如下一章的分發(fā)部分詳細描述的那樣,可以使用包含在項目安裝程序中的Bundle來安裝AssetBundle緩存。
使用UnityWebRequest 或 WWW.LoadFromCacheOrDownload時,請確保下載器代碼在加載AssetBundle后正確調(diào)用Dispose。或者,C#的使用語句是確保WWW或UnityWebRequest安全處置的最便捷方式。
對于需要獨特的特定緩存或下載要求的大量工程團隊的項目,可以考慮自定義下載程序。編寫自定義下載程序是一項不重要的工程任務(wù),任何自定義下載程序都應(yīng)與AssetBundle.LoadFromFile兼容。有關(guān)更多詳細信息,請參閱下一章的分發(fā)部分。
3.4。從AssetBundles加載資產(chǎn)
UnityEngine.Objects可以使用三個不同的API從AssetBundles加載,這些API都附加到AssetBundle對象,它們同時具有同步和異步變體:
這些API的同步版本總是比其異步版本快至少一幀。
異步加載將每幀加載多個對象,直到它們的時間片限制。有關(guān)此行為的基本技術(shù)原因,請參閱低級加載詳細信息部分。
LoadAllAssets應(yīng)該加載多個獨立UnityEngine.Objects時使用。只有在需要加載AssetBundle中的大部分或全部對象時才能使用它。與其他兩個API相比,LoadAllAssets比多個單獨調(diào)用LoadAssets稍快。因此,如果要加載的資產(chǎn)數(shù)量很大,但需要一次加載不到66%的AssetBundle,請考慮將AssetBundle拆分為多個較小的捆綁包并使用LoadAllAssets。
加載包含多個嵌入對象的復(fù)合資產(chǎn)時,應(yīng)使用LoadAssetWithSubAssets,例如嵌入動畫的FBX模型或嵌入多個精靈的精靈圖集。如果需要加載的對象全部來自同一個資產(chǎn),但與許多其他不相關(guān)的對象一起存儲在AssetBundle中,則使用此API。
對于任何其他情況,請使用LoadAsset或LoadAssetAsync。
3.4.1。低級加載細節(jié)
UnityEngine.Object加載是在主線程之外執(zhí)行的:從工作線程的存儲中讀取對象的數(shù)據(jù)。任何不接觸Unity系統(tǒng)的線程敏感部分(腳本,圖形)的內(nèi)容都將在工作線程上進行轉(zhuǎn)換。例如,VBOs將從網(wǎng)格創(chuàng)建,紋理將被解壓縮等。
從Unity 5.3開始,對象加載已經(jīng)并行化。多個對象在工作線程上被反序列化,處理和集成。當一個對象完成加載時,它的Awake回調(diào)將被調(diào)用,并且該對象將在下一幀期間可用于Unity Engine的其余部分。
同步的AssetBundle.Load方法將暫停主線程,直到對象加載完成。他們還會對對象加載進行時間片分割,以便對象集成不會占用超過一定數(shù)量的毫秒幀時間。毫秒數(shù)由屬性Application.backgroundLoadingPriority設(shè)置:
ThreadPriority.High:每幀最多50毫秒
ThreadPriority.Normal:每幀最多10毫秒
ThreadPriority.BelowNormal:每幀最多4毫秒
ThreadPriority.Low:每幀最多2毫秒。
從Unity 5.2開始,多個對象被加載,直到達到對象加載的幀時間限制。假設(shè)所有其他因素相同,由于發(fā)出異步調(diào)用和引擎可用對象之間的最小一幀延遲,資產(chǎn)加載API的異步變體的完成時間總是比可比較的同步版本花費更長的時間。
3.4.2。AssetBundle依賴關(guān)系
AssetBundles之間的依賴關(guān)系使用兩個不同的API自動跟蹤,具體取決于運行時環(huán)境。在Unity編輯器中,可以通過AssetDatabase API 查詢AssetBundle依賴關(guān)系。可以通過AssetImporter API 訪問和更改AssetBundle分配和依賴關(guān)系。在運行時,Unity提供了一個可選的API來加載AssetBundle構(gòu)建期間通過基于腳本對象的AssetBundleManifest API 生成的依賴信息。
當一個或多個父AssetBundle的UnityEngine.Objects引用一個或多個其他AssetBundle的UnityEngine.Objects時,AssetBundle依賴于另一個AssetBundle。有關(guān)對象間引用的更多信息,請參閱資產(chǎn),對象和序列化文章的對象間引用部分。
如該文章的序列化和實例部分所述,AssetBundles充當由AssetBundle中包含的每個對象的FileGUID&LocalID標識的源數(shù)據(jù)的源。
因為一個對象在其實例ID被首先取消引用時被加載,并且因為一個對象在加載其AssetBundle時被分配了有效的實例ID,所以AssetBundles加載的順序并不重要。相反,加載對象本身之前加載包含Object的依賴關(guān)系的所有AssetBundles是非常重要的。裝載父級AssetBundle時,Unity不會嘗試自動加載任何子AssetBundles。
例:
假設(shè)材料A是指組織B。物料A打包到AssetBundle 1中,而質(zhì)地B打包到AssetBundle 2中。
在這種使用情況下,AssetBundle 2必須在從AssetBundle 1中加載材料A 之前加載。
這并不意味著必須在AssetBundle 1之前加載AssetBundle 2,或者必須從AssetBundle 2明確加載Texture B。在從MaterialBundle 1中加載材料A之前加載AssetBundle 2就足夠了。
但是,在加載AssetBundle 1時,Unity 不會自動加載AssetBundle 2。這必須在腳本代碼中手動完成。
有關(guān)AssetBundle依賴關(guān)系的更多信息,請參閱手冊頁。
3.4.3。AssetBundle清單
當使用BuildPipeline.BuildAssetBundles API 執(zhí)行AssetBundle構(gòu)建管道時,Unity會序列化包含每個AssetBundle的依賴性信息的Object。此數(shù)據(jù)存儲在單獨的AssetBundle中,其中包含AssetBundleManifest類型的單個對象。
此資產(chǎn)將存儲在AssetBundle中,其名稱與構(gòu)建AssetBundles的父目錄相同。如果項目將其AssetBundles構(gòu)建到(projectroot)/ build / Client /文件夾,則包含該清單的AssetBundle將被保存為(projectroot)/build/Client/Client.manifest。
包含清單的AssetBundle可以像其他任何AssetBundle一樣加載,緩存和卸載。
AssetBundleManifest對象本身提供GetAllAssetBundles API來列出與清單并發(fā)構(gòu)建的所有AssetBundles,以及兩種方法來查詢特定AssetBundle的依賴關(guān)系:
AssetBundleManifest.GetAllDependencies返回AssetBundle的所有分層依賴項,其中包括AssetBundle的直接子項,子項的子項等的依賴項。
AssetBundleManifest.GetDirectDependencies只返回一個AssetBundle的直接子項
請注意,這兩個API都分配字符串數(shù)組。相應(yīng)地,它們只應(yīng)該謹慎使用,而不應(yīng)該在應(yīng)用程序生命周期的性能敏感部分中使用。
3.4.4。建議
在許多情況下,在玩家進入應(yīng)用程序的性能關(guān)鍵區(qū)域(例如主游戲級別或世界)之前,最好加載盡可能多的所需對象。這在移動平臺上尤為重要,因為訪問本地存儲的速度較慢,而且在播放時加載和卸載對象的內(nèi)存流失可觸發(fā)垃圾收集器。
對于必須在應(yīng)用程序交互時加載和卸載對象的項目,請參閱AssetBundle使用模式文章的管理加載資產(chǎn)部分以獲取有關(guān)卸載Objects和AssetBundles的更多信息。