AssetBundle基本原理

AssetBundle基本原理

版本檢查:2017.3 -難度:高級

這是涵蓋Unity5資產,資源和資源管理系列文章的第四章。

本章討論AssetBundles。它介紹了構建AssetBundles的基本系統,以及用於與AssetBundles進行交互的核心API。具體來說,它討論了AssetBundles本身的加載和卸載以及AssetBundles中特定資產和對象的加載和卸載。

有關AssetBundles使用的更多模式和最佳實踐,請參閱本系列的下一章。

3.1.概觀

AssetBundle系統提供了一種方法,用於存儲Unity可以索引和序列化的檔案格式的一個或多個文件。AssetBundles是Unity的主要工具,用於在安裝後交付和更新非代碼內容。這允許開發人員提交更小的應用程序包,最大限度地減少運行時內存壓力,並有選擇地加載爲最終用戶設備優化的內容。

瞭解AssetBundles的工作方式對於爲移動設備構建成功的Unity項目至關重要。有關AssetBundle內容的總體說明,請查看AssetBundle文檔

3.2.AssetBundle設計

總而言之,一個AssetBundle由兩部分組成:頭部和數據段。

頭部包含有關AssetBundle的信息,例如標識符,壓縮類型和清單。清單是一個由對象名稱鍵入的查找表。每個條目都提供一個字節索引,用於指示在AssetBundle的數據段中可以找到給定對象的位置。在大多數平臺上,這個查找表被實現爲一個平衡搜索樹。具體來說,Windows和OSX派生的平臺(包括iOS)採用紅黑樹。因此,構建清單所需的時間將隨着AssetBundle內資產數量的增長而線性增加。

數據段包含通過序列化AssetBundle中的資產生成的原始數據。如果將LZMA指定爲壓縮方案,則將壓縮所有序列化資產的完整字節數組。如果指定LZ4,單獨資源的字節將被單獨壓縮。如果不使用壓縮,數據段將保持爲原始字節流。

在Unity 5.3之前,對象無法在AssetBundle中單獨壓縮。因此,如果指示5.3版本的Unity版本從壓縮的AssetBundle中讀取一個或多個對象,則Unity必須解壓縮整個AssetBundle。通常,Unity會緩存AssetBundle的解壓縮副本,以提高同一AssetBundle上後續加載請求的加載性能。

3.3.加載AssetBundles

AssetBundles可以通過五個不同的API加載。這五個API的行爲取決於兩個標準是不同的:

  1. AssetBundle是否是LZMA壓縮,LZ4壓縮或未壓縮

  2. 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從託管代碼字節數組在C#中的字節[])中加載一個AssetBundle它將始終將來自託管代碼字節數組的源數據複製到新分配的連續本機內存塊中。如果AssetBundle是LZMA壓縮的,它將在複製時解壓縮AssetBundle。未壓縮的和LZ4壓縮的AssetBundles將被逐字複製。

此API消耗的最大內存量至少爲AssetBundle的兩倍:由API創建的本地內存中的一個副本,以及傳遞給API的託管字節數組中的一個副本。因此,從通過此API創建的AssetBundle加載的資產將在內存中重複三次:一次位於託管代碼字節數組中,一次位於AssetBundle的本機內存副本中,第三次位於GPU或系統內存中用於資產本身。

在Unity 5.3.3之前,這個API被稱AssetBundle.CreateFromMemory。它的功能沒有改變。*

3.3.2.AssetBundle.LoadFromFile(異步)

AssetBundle.LoadFromFile是一個高效的API,用於從本地存儲器(如硬盤或SD卡)加載未壓縮或LZ4壓縮的AssetBundle。

在桌面獨立,控制檯和移動平臺上,API只會加載AssetBundle的標題,並將剩餘的數據保留在磁盤上。AssetBundle的對象將在加載方法(例如AssetBundle.Load)被調用或InstanceID被解除引用時按需加載在這種情況下不會消耗過量的內存。在Unity編輯器中,API會將整個AssetBundle加載到內存中,就好像從磁盤讀取字節並使用AssetBundle.LoadFromMemoryAsync一樣。如果在Unity編輯器中對項目進行概要分析,則此API可能導致在AssetBundle加載期間出現內存尖峯。這不應該影響設備性能,並且應該在採取補救措施之前在設備上重新測試這些尖峯。

注意:在Unity 5.3或更早版本的Android設備上,嘗試從Streaming Assets路徑加載AssetBundles時,此API將失敗。Unity 5.4中已解決該問題。有關更多詳細信息,請參閱AssetBundle使用模式>章節中的分發 - 隨項目一起提供的部分。

*在Unity 5.3之前,這個API被稱爲AssetBundle.CreateFromFile。其功能尚未更改。

3.3.3.AssetBundleDownloadHandler

UnityWebRequestAPI允許開發人員指定統一究竟應該如何處理下載的數據,並允許開發者以消除不必要的內存使用情況。使用UnityWebRequest下載AssetBundle的最簡單方法是調用。
就本指南而言,感興趣的類是DownloadHandlerAssetBundle使用工作線程,它會將下載的數據流式傳輸到固定大小的緩衝區,然後根據下載處理程序的配置方式將緩衝的數據緩衝到臨時存儲或AssetBundle緩存所有這些操作都以本機代碼形式進行,消除了擴展託管堆的風險。此外,該下載處理程序並沒有把所有下載的字節的本機代碼副本,進一步降低了下載的AssetBundle的內存開銷。

LZMA壓縮的AssetBundles將在下載過程中進行解壓縮並使用LZ4壓縮進行緩存。通過設置Caching.CompressionEnabled可以更改此行爲。
當下載完成後,assetBundle下載處理程序的屬性提供下載的AssetBundle,彷彿已經呼籲下載AssetBundle。

如果將緩存信息提供給UnityWebRequest對象,並且所請求的AssetBundle已經存在於Unity的緩存中,則AssetBundle將立即變爲可用,並且此API將以與AssetBundle.LoadFromFile相同的方式運行。
在Unity 5.6之前,UnityWebRequest系統使用固定的工作線程池和內部作業系統來防止過度的併發下載。線程池的大小不可配置。在Unity 5.6中,這些保護措施已被刪除,以適應更多現代硬件,並允許更快地訪問HTTP響應代碼和標頭。

3.3.4.WWW.LoadFromCacheOrDownload

注意: 從Unity 2017.1開始,WWW.LoadFromCacheOrDownload *只是包裝UnityWebRequest,因此,使用Unity 2017.1或更高版本的開發人員應遷移到UnityWebRequest。
WWW.LoadFromCacheOrDownload將在未來版本中棄用。*
以下信息適用於Unity 5.6或更早版本。

WWW.LoadFromCacheOrDownload是一個API,允許從遠程服務器和本地存儲裝載對象。文件可以通過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提供。

雖然數據將通過固定大小的緩衝區解壓縮並寫入緩存,但WWW對象將在本機內存中保留AssetBundle字節的完整副本。AssetBundle的額外副本保留爲支持WWW.bytes屬性。

由於在WWW對象中緩存AssetBundle字節的內存開銷,AssetBundles應該保持很小 - 最多幾兆字節。有關AssetBundle大小的更多討論,請參閱AssetBundle使用模式章節中的資產分配策略部分。

與UnityWebRequest不同,每次調用此API都會產生一個新的工作線程。因此,在移動設備等內存有限的平臺上,每次只能使用此API下載一個AssetBundle,以避免內存高峯。多次調用此API時,請小心創建過多的線程。如果需要下載超過5個AssetBundle,請在腳本代碼中創建並管理下載隊列,以確保只有少量AssetBundle下載正在同時運行。

3.3.5.建議

一般情況下應儘可能使用AssetBundle.LoadFromFile。就速度,磁盤使用情況和運行時內存使用情況而言,此API是最高效的。
對於必須下載或修補AssetBundles的項目,強烈建議對於使用Unity 5.3或更新版本UnityWebRequest,對於使用Unity 5.2或更早,則強烈建議使用WWW.LoadFromCacheOrDownload。正如下一章分發詳細描述的那樣,可以使用包含在項目安裝程序中的Bundle來安裝AssetBundle緩存。

使用UnityWebRequest WWW.LoadFromCacheOrDownload時,請確保下載器代碼在加載AssetBundle後正確調用Dispose。或者,C#的使用語句是確保WWW或UnityWebRequest安全處置的最便捷方式。

對於需要獨特的特定緩存或下載要求的大量工程團隊的項目,可以考慮使用自定義下載器。編寫自定義下載程序是一項不重要的工程任務,任何自定義下載程序都應與AssetBundle.LoadFromFile兼容 。有關更多詳細信息,請參閱下一章分發部分。

3.4.加載資產

UnityEngine.Objects可以使用三個不同的API從AssetBundles加載,這些API都附加到AssetBundle對象,它們同時具有同步和異步變體:
* LoadAsset(LoadAssetAsync)

異步加載將每幀加載多個對象,直到它們的時間片限制。有關此行爲的基本技術原因,請參閱低級加載詳細信息部分。

LoadAllAssets應該加載多個獨立UnityEngine.Objects時使用。只有在需要加載AssetBundle中的大部分或全部對象時才能使用它。與其他兩個API相比,LoadAllAssets比多個單獨調用LoadAssets稍快。因此,如果要加載的資產數量很大,但需要一次加載不到66%的AssetBundle,請考慮將AssetBundle拆分爲多個較小的捆綁包並使用LoadAllAssets。

加載包含多個嵌入對象的複合資產時,應使用LoadAssetWithSubAssets,例如嵌入動畫的FBX模型或嵌入多個精靈的精靈圖集。如果需要加載的對象全部來自同一個資產,但與許多其他不相關的對象一起存儲在AssetBundle中,則使用此API。

對於任何其他情況,請使用LoadAsset或LoadAssetAsync。

3.4.1.低級加載細節

UnityEngine.Object加載是在主線程之外執行的:從工作線程的存儲中讀取對象的數據。任何不接觸Unity系統的線程敏感部分(腳本,圖形)的內容都將在工作線程上進行轉換。例如,VBOs將從網格創建,紋理將被解壓縮等。

從Unity 5.3開始,對象加載已經並行化。多個對象在工作線程上被反序列化,處理和集成。當一個對象完成加載時,它的Awake回調將被調用,並且該對象將在下一幀期間可用於Unity Engine的其餘部分。

同步的方法將暫停主線程,直到對象加載完成。他們還會對對象加載進行時間片分割,以便對象集成不會佔用超過特定數量的毫秒幀時間。毫秒數由屬性Application.backgroundLoadingPriority設置:

  • ThreadPriority.High:每幀最多50毫秒
  • ThreadPriority.Normal:每幀最多10毫秒
  • ThreadPriority.BelowNormal:每幀最多4毫秒
  • ThreadPriority.Low:每幀最多2毫秒。

從Unity 5.2開始,多個對象被加載,直到達到對象加載的幀時間限制。假設所有其他因素相同,由於發出異步調用和引擎可用對象之間的最小一幀延遲,資產加載API的異步變體的完成時間總是比可比較的同步版本更長。

3.4.2AssetBundles依賴關係

AssetBundles之間的依賴關係使用兩個不同的API自動跟蹤,具體取決於運行時環境。在Unity編輯器中,可以通過AssetDatabaseAPI查詢AssetBundle依賴關係可以通過AssetImporterAPI訪問和更改AssetBundle分配和依賴關係。行時,Unity提供了一個可選的API來加載AssetBundle構建期間通過基於腳本對象的setBundleManifestAPI生成的依賴信息。

當一個或多個父AssetBundle的UnityEngine.Objects引用一個或多個其他AssetBundle的UnityEngine.Objects時,AssetBundle依賴於另一個AssetBundle。有關對象間引用的更多信息,請參閱資產,對象和序列化文章對象間引用部分。

如該文章的列化和實例部分所述,AssetBundles充當由AssetBundle中包含的每個對象的FileGUID&LocalID標識的源數據的源。

因爲一個對象在其實例ID被首先取消引用時被加載,並且因爲一個對象在加載其AssetBundle時被分配了有效的實例ID,所以AssetBundles加載的順序並不重要。相反,加載對象本身之前加載包含Object的依賴關係的所有AssetBundles是非常重要的。裝載父級AssetBundle時,Unity不會嘗試自動加載任何子AssetBundles。

例:

假設材料A是指組織B。物料A打包到AssetBundle 1中,而質地B打包到AssetBundle 2中。
描述

在這種使用情況下,AssetBundle 2必須在從AssetBundle 1中加載材料A之前加載。

這並不意味着必須在AssetBundle 1之前加載AssetBundle 2,或者必須從AssetBundle 2明確加載Texture B。在將Material A從MaterialBundle 1中加載之前加載AssetBundle 2就足夠了。

但是,在加載AssetBundle 1時,Unity不會自動加載AssetBundle 2。這必須在腳本代碼中手動完成。

有關AssetBundle依賴關係的更多信息,請參閱手冊頁

3.4.3.AssetBundle清單

當使用BuildPipeline.BuildAssetBundles執行AssetBundle構建管道時Unity會序列化包含每個AssetBundle的依賴性信息的Object。此數據存儲在單獨的AssetBundle中,其中包含AssetBundleManifest類型的單個對象。

此資產將存儲在AssetBundle中,其名稱與構建AssetBundles的父目錄相同。如果項目將其AssetBundles構建到(projectroot)/ build/Client/文件夾,則包含該清單的AssetBundle將被保存爲(projectroot)/build/Client/Client.manifest。

包含清單的AssetBundle可以像其他任何AssetBundle一樣加載,緩存和卸載。

AssetBundleManifest對象本身提供GetAllAssetBundlesAPI來列出與清單併發構建的所有AssetBundles,以及兩種方法來查詢特定AssetBundle的依賴關係:

請注意,這兩個API都分配字符串數組。相應地,它們只應該謹慎使用,而不應該在應用程序生命週期的性能敏感部分中使用。

3.4.4.建議

在許多情況下,在玩家進入應用程序的性能關鍵區域(例如主遊戲級別或世界)之前,最好加載儘可能多的所需對象。這在移動平臺上尤其重要,因爲訪問本地存儲的速度很慢,而且在播放時加載和卸載對象的內存流失可能會觸發垃圾收集器。

對於必須在應用程序交互時加載和卸載對象的項目,請參閱AssetBundle使用模式管理加載資產部分以獲取有關卸載對象和AssetBundles的更多信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章