Assets, Resources and AssetBundles(四):AssetBundle fundamentals

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

本章討論AssetBundles。它介紹了構建AssetBundles的基本系統,以及用於與AssetBundles交互的核心API。特別是,它既討論了AssetBundles本身的加載和卸載,也討論了從AssetBundles加載和卸載特定資產和對象的問題。

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

3.1. Overview

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

瞭解AssetBundles的工作方式對於建立一個成功的移動設備Unity項目至關重要。有關AssetBundle內容的總體說明,請參閱AssetBundle documentation.

3.2. AssetBundle layout

總之,AssetBundle由兩個部分組成:標題和數據段。

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

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

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

3.3. Loading AssetBundles

AssetBundles可以通過四個不同的API加載。根據兩個標準,這四個API的行爲是不同的:

  • Assetbundle是否爲LZMA壓縮,LZ4壓縮或未壓縮
  • 裝載Assetbundle的平臺

These APIs are:

  1. AssetBundle.LoadFromMemory(Async optional)

  2. AssetBundle.LoadFromFile(Async optional)

  3. UnityWebRequest's DownloadHandlerAssetBundle

  4. WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)

3.3.1 AssetBundle.LoadFromMemory(Async)

建議不要使用這個API。

AssetBundle.LoadFromMemoryAsync從託管代碼字節數組(C#中的字節[])加載AssetBundle。它將始終將源數據從託管代碼字節數組複製到新分配的、連續的本機內存塊中。如果AssetBundle被LZMA壓縮,它將在複製時解壓AssetBundle。未壓縮和LZ4壓縮AssetBundle將逐字複製。

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

在Unity5.3.3之前,這個API被稱爲AssetBundle.CreateFromMemory。其功能沒有改變。

3.3.2. AssetBundle.LoadFromFile(Async)

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

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

注意:在具有Unity5.3或更舊版本的Android設備上,當試圖從Streaming Assets path加載AssetBundles時,此API將失敗。這個問題已在團結5.4中得到解決。有關詳細信息,請參閱AssetBundle使用模式一章的項目部分附帶的分發部分。

在Unity5.3之前,這個API被稱爲AssetBundle.CreateFromFile。其功能沒有改變。

3.3.3. AssetBundleDownloadHandler

UnityWebRequest API 允許開發人員準確地指定Unity應如何處理下載的數據,並允許開發人員消除不必要的內存使用。使用UnityWebRequest下載AssetBundle的最簡單方法是調用UnityWebRequest.GetAssetBundle.

就本指南而言,感興趣的類是DownloadHandlerAssetBundle。使用工作線程,它將下載的數據流到一個固定大小的緩衝區中,然後根據下載處理程序的配置方式將緩衝數據放到臨時存儲或AssetBundle緩存中。所有這些操作都發生在本機代碼中,消除了擴展託管堆的風險。此外,此下載處理程序不保留所有下載字節的本機代碼副本,從而進一步減少了下載AssetBundle的內存開銷。

LZMA-壓縮AssetBundles將在下載和緩存期間使用LZ4壓縮。可以通過設置Caching.CompressionEnable來更改此行爲。

下載完成後,下載處理程序的assetbundle屬性提供對下載的assetbundle的訪問,就像AssetBundle.LoadFromFile已在下載的AssetBundle上調用一樣。

如果將緩存信息提供給UnityWebRequest對象,並且所請求的AssetBundle已經存在於Unity的緩存中,那麼AssetBundle將立即可用,並且此API將與AssetBundle.LoadFromFile相同操作。

在Unity5.6之前,UnityWebRequest系統使用了一個固定的工作線程池和一個內部作業系統來防止過多的併發下載。線程池的大小是不可配置的。在Unity5.6中,這些安全措施已經被刪除,以適應更現代的硬件,並允許更快地訪問HTTP響應代碼和報頭。

3.3.4. WWW.LoadFromCacheOrDownload

注:從Unity2017.1開始,WWW.LoadFromCacheOrDownload簡單地包裝了UnityWebRequest。因此,使用Unity2017.1或更高版本的開發人員應該遷移到UnityWebRequest。在未來的版本中,WWW.LoadFromCacheOrDownload將棄用。

以下信息適用於Unity5.6或更舊版本。

WWW.LoadFromCacheOrDownload是一個API,允許從遠程服務器和本地存儲加載對象。可以通過文件/URL從本地存儲加載文件。如果AssetBundle存在於Unitycache中,則此API的行爲將與AssetBundle.LoadFromFile完全相同。

如果AssetBundle尚未緩存,則WWW.LoadFromCacheOrDownload將從其源讀取AssetBundle。如果AssetBundle被壓縮,它將使用工作線程進行解壓縮並寫入緩存中。否則,它將通過工作線程直接寫入緩存。在緩存資產綁定之後,WWW.LoadFromCacheOrDownload將從緩存的、解壓縮的AssetBundle加載頭信息。然後,API將與加載了AssetBundle.LoadFromFile的AssetBundle行爲相同。此緩存在WWW.LoadFromCacheOrDownload和UnityWebRequest之間共享。與一個API一起下載的任何AssetBundle也可以通過另一個API獲得。

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

由於在WWW對象中緩存AssetBundle的字節的內存開銷,Assetbundle應保持較小(最多)幾兆字節。有關AssetBundle大小調整的更多討論,請參閱AssetBundle usage patterns 一章中的“Asset assignment strategies”部分。

與UnityWebRequest不同,每個調用此API的調用將生成新的工作線程。因此,在具有有限存儲器的平臺(例如移動設備)上,應當使用該API僅下載一次單個AssetBundle,以避免存儲器尖峯。在多次調用此API時,請注意創建過多的線程數。如果需要下載超過5個ASSetBundle,請在腳本代碼中創建和管理下載隊列,以確保只有少數AssetBundle下載同時運行。

3.3.5. Recommendations

一般來說,只要有可能,就應該使用AssetBundle.LoadFromFile。這個API在速度、磁盤使用和運行時內存使用方面是最有效的。

對於必須下載或修補AssetBundles的項目,強烈建議對使用Unity5.3或更高版本的項目使用UnityWebRequest,對於使用Unity5.2或更舊版本的項目使用WWW.LoadFromCacheOrDownload。正如下一章的分發部分所詳細介紹的那樣,可以在項目的安裝程序中包含Bundles,從而實現AssetBundles緩存。

當使用UnityWebRequest*或*WWW.LoadFromCacheOrDownload時,確保下載程序代碼在加載AssetBundle後正確地調用Dispose。另外,C#的使用語句是確保WWW或UnityWebRequest被安全處理的最方便的方法。

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

3.4. Loading Assets From AssetBundles

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

這些API的同步版本總是比異步版本快至少一個幀。

異步加載將加載多個對象的每幀,直到他們的時間切片限制。有關此行爲的基本技術原因,請參閱“Low-level loading details”部分。

加載多個獨立單元時,應使用LoadAllAssets。只有當需要加載AssetBundle中的大多數或所有對象時,纔可以使用它。與其他兩個API相比,LoadAllAssets比對LoadAssets的多個單獨調用稍微快一些。因此,如果要加載的資產數量很大,但在單個時間需要加載少於66%的Assetbundle,請考慮將AssetBundle分割爲多個較小的捆綁包,並使用LoadAllAssets。

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

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

3.4.1. Low-level loading details

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

從Unity5.3開始,對象加載就被並行化了。在工作線程上反序列化、處理和集成多個對象。當一個對象完成加載時,它的喚醒回調將被調用,該對象將在下一個幀中對UnityEngine的其餘部分可用。

同步AssetBundle.Load方法將暫停主線程,直到對象加載完成。它們還會加載時間切片對象,以便對象集成不會佔用一定數量的毫秒幀時間。應用程序屬性設置毫秒數:

  • ThreadPriority.High: Maximum 50 milliseconds per frame

  • ThreadPriority.Normal: Maximum 10 milliseconds per frame

  • ThreadPriority.BelowNormal: Maximum 4 milliseconds per frame

  • ThreadPriority.Low: Maximum 2 milliseconds per frame.

從Unity5.2開始,將加載多個對象,直到達到對象加載的幀-時間限制爲止。假設所有其他因素相等,資產加載API的異步變體將總是比可比的同步版本花費更長的時間,因爲發出異步調用和對象之間有最小的一幀延遲。

3.4.2. AssetBundle dependencies

根據運行時環境的不同,使用兩個不同的API自動跟蹤AssetBundles之間的依賴關係。在UnityEditor中,可以通過AssetDatabase API查詢AssetBundle依賴項。資產綁定分配和依賴項可以通過AssetImport API訪問和更改。在運行時,Unity提供了一個可選的API,通過基於ScriptableObject的AssetBundleManifest API加載在AssetBundle構建過程中生成的依賴信息。

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

正如本文的Serialization and instances部分所述,AssetBundles是由AssetBundle中包含的每個對象的FileGUID和LocalID標識的源數據的來源。

因爲一個對象是在其Instance ID第一次被取消引用時加載的,而且由於一個對象在加載其AssetBundle時被分配了一個有效的Instance ID,所以加載AssetBundles的順序並不重要。相反,在加載對象本身之前,重要的是加載包含對象依賴關係的所有AssetBundles。Unity不會嘗試在加載父AssetBundles時自動加載任何子AssetBundles。

Example:

假設材料A是指紋理B。材料A被打包到資產束1中,紋理B被打包到資產束2中。

在此使用情況下,在將材料A從AssetBundle1中取出之前,必須加載AssetBundle2。

這並不意味着AssetBundle 2必須在AssetBundle 1之前加載,或者紋理B必須從AssetBundle 2中顯式加載。在將材料A從AssetBundle 1加載之前加載AssetBundle 2就足夠了。

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

有關AssetBundle依賴項的詳細信息,請參閱 manual page.

3.4.3. AssetBundle manifests

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

此資產將存儲在與構建AssetBundles的父目錄同名的AssetBundle中。如果一個項目將其AssetBundles構建到位於(Projectroot)/Build/Client/的文件夾中,那麼包含清單的AssetBundle將被保存爲(Projectroot)/build/client/Client.manifest

AssetBundle manifest可以像任何其他AssetBundle一樣加載、緩存和卸載。

AssetBundleManifest對象本身提供GetAllAssetBundles API來列出與清單同時構建的所有AssetBundles,以及查詢特定AssetBundle的依賴項的兩個方法:

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

3.4.4. Recommendations(建議)

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

對於在應用程序交互時必須加載和卸載對象的項目,請參閱AssetBundle usage patterns文章中的“Managing loaded assets”部分,以獲取有關卸載對象和AssetBundle的詳細信息。

官方譯文(未完待續)

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