unity asset store下载不了_Unity手游实战:从0开始SLG——资源管理系统-基础篇(三)AssetBundle原理...
先用一句話介紹一下AssetBundle吧。
AssetBundle系統(tǒng)提供了一種壓縮文件的格式,可以把1到多個文件進行索引和序列化。
Unity項目在交付安裝之后,會通過AssetBundle對不包含代碼的資源進行更新。這就允許開發(fā)人員先提交一個小的應(yīng)用程序包,將運行時內(nèi)存壓力降到最低,并有選擇地加載針對不同終端用戶設(shè)備優(yōu)化后的內(nèi)容。
3.1 AssetBundle結(jié)構(gòu)
總的來說,AssetBundle就像傳統(tǒng)的壓縮包一樣,由兩個部分組成:包頭和數(shù)據(jù)段。
包頭包含有關(guān)AssetBundle 的信息,比如標識符、壓縮類型和內(nèi)容清單。清單是一個以O(shè)bjects name為鍵的查找表。每個條目都提供一個字節(jié)索引,用來指示該Objects在AssetBundle數(shù)據(jù)段的位置。在大多數(shù)平臺上,這個查找表是用平衡搜索樹實現(xiàn)的。具體來說,Windows和OSX派生平臺(包括IOS)都采用了紅黑樹。因此,構(gòu)建清單所需的時間會隨著AssetBundle中Assets的數(shù)量增加而線性增加。
數(shù)據(jù)段包含通過序列化AssetBundle中的Assets而生成的原始數(shù)據(jù)。如果指定LZMA為壓縮方案的話,則對所有序列化Assets后的完整字節(jié)數(shù)組進行壓縮。如果指定了LZ4,則單獨壓縮單獨Assets的字節(jié)。如果不使用壓縮,數(shù)據(jù)段將保持為原始字節(jié)流。
在Unity5.3之前,是無法對AssetBundle中單獨Objects進行壓縮的。因此,如果在5.3之前的Unity版本被要求從壓縮的AssetBundle中讀取一個或多個對象時,Unity必須解壓整個AssetBundle。通常,Unity會緩存AssetBundle的解壓縮副本,以提高在相同AssetBundle上的后續(xù)加載請求的加載性能。
3.2 加載AssetBundles
AssetBundles可以通過四個不同的API進行加載。但受限于兩個標準,這四個API的行為是不同的。2個標準如下:
1、AssetBundles的壓縮方式:LZMA、LZ4、還是未壓縮的。
2、AssetBundles的加載平臺。
而四個API分別是:
- AssetBundle.LoadFromMemory(Async optional)
- AssetBundle.LoadFromFile(Async optional)
- UnityWebRequest's DownloadHandlerAssetBundle
- WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)
下面來詳細講一下4個API的區(qū)別。
3.2.1 AssetBundle.LoadFromMemory(Async)
Unity的建議是 不要使用這個API!
LoadFromMemory(Async) 是從托管代碼的字節(jié)數(shù)組里加載AssetBundle。也就是說你要提前用其他的方式將資源的二進制數(shù)組加入到內(nèi)存中。然后該接口會將源數(shù)據(jù)從托管代碼字節(jié)數(shù)組復制到新分配的、連續(xù)的本機內(nèi)存塊中。
但如果AssetBundle使用了LZMA壓縮類型,它將在復制時解壓AssetBundle。而未壓縮和LZ4壓縮類型的AssetBundle將逐字節(jié)的完整復制。
之所以不建議使用該API是因為,此API消耗的最大內(nèi)存量將至少是AssetBundle的兩倍:本機內(nèi)存中的一個副本,和LoadFromMemory(Async)從托管字節(jié)數(shù)組中復制的一個副本。
因此,從通過此API創(chuàng)建的AssetBundle加載的資產(chǎn)將在內(nèi)存中冗余三次:一次在托管代碼字節(jié)數(shù)組中,一次在AssetBundle的棧內(nèi)存副本中,第三次在GPU或系統(tǒng)內(nèi)存中,用于Asset本身。
注意:在Unity5.3.3之前,這個API被稱為AssetBundle.CreateFromMemory。但功能沒有改變。
3.2.2 AssetBundle.LoadFromFile(Async)
LoadFromFile是一種高效的API,用于從本地存儲(如硬盤或SD卡)加載未壓縮或LZ4壓縮格式的AssetBundle。
在桌面獨立平臺、控制臺和移動平臺上,API將只加載AssetBundle的頭部,并將剩余的數(shù)據(jù)留在磁盤上。
AssetBundle的Objects 會按需加載,比如加載方法(例如AssetBundle.Load)被調(diào)用或其InstanceID被間接引用的時候。在這種情況下,不會消耗過多的內(nèi)存。
但在Editor環(huán)境下中,API還是會把整個AssetBundle加載到內(nèi)存中,就像讀取磁盤上的字節(jié)和使用AssetBundle.LoadFromMemoryAsync一樣。
如果在Editor中對項目進行了分析,此API可能會導致在AssetBundle加載期間出現(xiàn)內(nèi)存尖峰。但這不應(yīng)影響設(shè)備上的性能,在做優(yōu)化之前,這些尖峰應(yīng)該在設(shè)備上重新再測試一一遍.
要注意,這個API只針對未壓縮或LZ4壓縮格式,因為前面說過了,如果使用LZMA壓縮的話,它是針對整個生成后的數(shù)據(jù)包進行壓縮的,所以在未解壓之前是無法拿到AssetBundle的頭信息的。
注意:這里曾經(jīng)有過一個歷史遺留問題,即在Unity5.3或更老版本的Android設(shè)備上,當試圖從Streaming Assets路徑加載AssetBundles時,此API將失敗。這個問題已在Unity5.4中已經(jīng)解決。
在Unity5.3之前,這個API被稱為AssetBundle.CreateFromFile。其功能沒有改變。
3.2.3 AssetBundleDownloadHandler
DownloadHandlerAssetBundle的操作是通過UnityWebRequest的API來完成的。
UnityWebRequest API允許開發(fā)人員精確地指定Unity 應(yīng)如何處理下載的數(shù)據(jù),并允許開發(fā)人員消除不必要的內(nèi)存使用。使用UnityWebRequest下載AssetBundle 的最簡單方法是調(diào)用UnityWebRequest.GetAssetBundle。
就實戰(zhàn)項目而言,最有意思的類是DownloadHandlerAssetBundle。它使用工作線程,將下載的數(shù)據(jù)流存儲到一個固定大小的緩沖區(qū)中,然后根據(jù)下載處理程序的配置方式將緩沖數(shù)據(jù)放到臨時存儲或AssetBundle緩存中。
所有這些操作都發(fā)生在非托管代碼中,消除了增加堆內(nèi)存的風險。此外,該下載處理程序并不會保留所有下載字節(jié)的棧內(nèi)存副本,從而進一步減少了下載AssetBundle的內(nèi)存開銷。
LZMA壓縮的AssetBundles將在下載和緩存的時候更改為LZ4壓縮。這個可以通過設(shè)置Caching.CompressionEnable屬性來更改。
如果將緩存信息提供給UnityWebRequest對象的話,一旦有請求的AssetBundle已經(jīng)存在于Unity的緩存中,那么AssetBundle將立即可用,并且此API的行為將會與AssetBundle.LoadFromFile相同操作。
在Unity5.6之前,UnityWebRequest系統(tǒng)使用了一個固定的工作線程池和一個內(nèi)部作業(yè)系統(tǒng)來防止過多的并發(fā)下載,并且線程池的大小是不可配置的。在Unity5.6中,這些安全措施已經(jīng)被刪除,以便適應(yīng)更現(xiàn)代化的硬件,并允許更快地訪問HTTP響應(yīng)代碼和報頭。
3.2.4 WWW.LoadFromCacheOrDownload
這是一個很古老的API了,從Unity2017.1開始,就只是簡單地包裝了UnityWebRequest。因此,使用Unity2017.1或更高版本的開發(fā)者應(yīng)該直接使用UnityWebRequest來工作。Unity已經(jīng)放棄了對改接口的維護,并可能在未來的某個版本中移除。
所以下面說的這些內(nèi)容只適合于Unity5.6或更老的版本。
WWW.LoadFromCacheOrDownload允許從遠程服務(wù)器和本地存儲加載對象。也可以通過文件//URL從本地存儲加載文件。如果AssetBundle存在于Unitycache中,則此API的行為將與AssetBundle.LoadFromFile完全相同。
如果AssetBundle尚未緩存,則WWW.LoadFromCacheOrDownload會將從它的源文件讀取AssetBundle。如果AssetBundle被壓縮過,它會使用工作線程進行解壓縮并寫入緩存中。否則,它將通過工作線程直接寫入緩存。
在緩存AssetBundle之后,WWW.LoadFromCacheOrDownload將從緩存的、解壓縮的AssetBundle加載頭信息。然后,和AssetBundle.LoadFromFile加載AssetBundle行為相同。
此緩存會在WWW.LoadFromCacheOrDownload和UnityWebRequest之間共享。一個API下載的任何AssetBundle也可以通過另一個API獲得。
雖然數(shù)據(jù)將通過固定大小的緩沖區(qū)解壓縮并寫入緩存,但WWW對象會在本機內(nèi)存中保留AssetBundle字節(jié)的完整副本。這個額外副本被保留的原因是因為要支持WWW.bytes字節(jié)屬性。
由于在WWW對象中緩存AssetBundle的字節(jié)的內(nèi)存開銷,所以,實際項目開發(fā)中AssetBundles應(yīng)該要保持較少的體積以便減少內(nèi)存。
與UnityWebRequest不同的是,每次調(diào)用這個API都會產(chǎn)生一個新的工作線程。因此,在手機等內(nèi)存有限的平臺上,最好限定一次只能下載一個AssetBundle,以避免內(nèi)存激增。而在其他平臺也要小心創(chuàng)建過多的線程。
如果需要下載5個以上的AssetBundles,建議在腳本代碼中創(chuàng)建和管理下載隊列,以確保只有少數(shù)幾個AssetBundle同時下載。
3.2.5 建議
1、一般來說,只要有可能,就應(yīng)該使用AssetBundle.LoadFromFile。這個API在速度、磁盤使用和運行時內(nèi)存使用方面是最有效的。
2、對于必須下載或熱更新AssetBundles的項目,強烈建議對使用Unity5.3或更高版本的項目使用UnityWebRequest,對于使用Unity5.2或更老版本的項目使用WWW.LoadFromCacheOrDownload。
3、當使用UnityWebRequest或WWW.LoadFromCacheOrDownload時,要確保下載程序代碼在加載AssetBundle后正確地調(diào)用Dispose。另外,C#的using語句是確保WWW或UnityWebRequest被安全處理的最方便的方法。
4、對于需要獨特的、特定的緩存或下載需求的大項目,可以考慮使用自定義的下載器。編寫自定義下載程序是一項重要并且復雜的任務(wù),任何自定義的下載程序都應(yīng)該與AssetBundle.LoadFromFile保持兼容。
3.3 從AssetBundles中加載Assets
到這里,我們已經(jīng)能夠獲得AssetBundles了,那么接下來就是要從AssetBundles里獲取Assets。
Unity提供了三個不同的API從AssetBundles加載UnityEngine.Objects,這些API都綁定到AssetBundle對象上,并且這些API具有同步和異步變體:
- LoadAsset (LoadAssetAsync)
- LoadAllAssets (LoadAllAssetsAsync)
- LoadAssetWithSubAssets (LoadAssetWithSubAssetsAsync)
并且這些API的同步版本總是比異步版本快至少一個幀(其實是因為異步版本為了確保異步,都至少延遲了1幀),異步加載每幀會加載多個對象,直到他們的時間切片切出。
加載多個獨立的UnityEngine.Objects時應(yīng)使用LoadAllAsset。并且只有在需要加載AssetBundle中的大多數(shù)或所有對象時,才應(yīng)該使用它。與其他兩個API相比,LoadAllAsset比對LoadAsset的多個單獨調(diào)用略快一些。因此,如果要加載的Asset數(shù)量很大,但如果需要一次性加載不到三分之二的AssetBundle的話,則考慮將AssetBundle拆分為多個較小的包,再使用LoadAllAsset。
加載包含多個嵌入式對象的復合Asset時,應(yīng)使用LoadAssetWithSubAsset,例如嵌入動畫的FBX模型或嵌入多個精靈的sprite圖集。也就是說,如果需要加載的對象都來自同一Asset,但與許多其他無關(guān)對象一起存儲在AssetBundle中,則使用此API。
任何其他情況,請使用LoadAsset或LoadAssetAsync。
3.3.1 低層級的加載細節(jié)
Object加載是在主線程上執(zhí)行,但數(shù)據(jù)從工作線程上的存儲中讀取。任何不觸碰Unity系統(tǒng)中線程敏感部分(腳本、圖形)的工作都將在工作線程上轉(zhuǎn)換。例如,VBO將從網(wǎng)格創(chuàng)建,紋理將被解壓,等等。
從Unity5.3開始,Object加載就被并行化了。在工作線程上反序列化、處理和集成多個Object。當一個Object完成加載時,它的Awake回調(diào)將被調(diào)用,該對象的其余部分將在下一個幀中對UnityEngine可用。
同步AssetBundle.Load方法將暫停主線程,直到Object加載完成。但它們也會加載時間切片的Object,以便Object集成不會占用太多的毫秒幀時間。應(yīng)用程序?qū)傩栽O(shè)置毫秒數(shù)的屬性為Application.backgroundLoadingPriority。
ThreadPriority.High: 每幀最多50毫秒
ThreadPriority.Normal: 每幀最多10毫秒
ThreadPriority.BelowNormal: 每幀最多4毫秒
ThreadPriority.Low: 每幀最多2毫秒。
從Unity5.2開始,加載多個對象時候,會一直進行直到達到對象加載的幀時間限制為止。假設(shè)所有其他因素相等,asset 加載API的異步變體將總是比同步版本花費更長的時間,因為發(fā)出異步調(diào)用和對象之間有最小的一幀延遲。
3.3.2 AssetBundle 依賴項
根據(jù)運行時環(huán)境的不同,使用兩個不同的API自動跟蹤AssetBundles之間的依賴關(guān)系。在UnityEditor中,可以通過AssetDatabaseAPI查詢AssetBundle依賴項。AssetBundles分配和依賴項可以通過AssetImportAPI訪問和更改。在運行時,Unity提供了一個可選的API,通過基于ScriptableObject的AssetBundleManifest API加載在AssetBundle構(gòu)建過程中生成的依賴信息。
當一個或多個AssetBundle 的UnityEngine.Objects引用了一個或者多個其他AssetBundle 的UnityEngine.Objects,那么這個AssetBundle就會依賴于另外的AssetBundle。AssetBundles充當由它包含的每個對象的FileGUID和LocalID標識的源數(shù)據(jù)。
因為一個對象是在其Instance ID第一次被間接引用時加載的,而且由于一個對象在加載其AssetBundle時被分配了一個有效的Instance ID,所以加載AssetBundles的順序并不重要。相反,在加載對象本身之前,重要的是加載包含對象依賴關(guān)系的所有AssetBundles。Unity不會嘗試在加載父AssetBundle時自動加載任何子AssetBundle。
示例:
假設(shè)material A引用texture B。material A被打包到AssetBundle1中,texture B被打包到AssetBundle2中:
在本用例中,AssetBundle2必須在material A從AssetBundle1中加載之前先加載。
這并不意味著AssetBundle 2必須在AssetBundle 1之前加載,或者texture B必須從AssetBundle 2中顯式加載。在將material A從AssetBundle 1加載之前加載AssetBundle 2就足夠了。
簡單來說就是AssetBundle之間的加載沒有先后,但是Asset的加載有。
有關(guān)AssetBundle依賴項的詳細信息,請參閱手冊頁。地址:https://docs.unity3d.com/Manual/AssetBundles-Dependencies.html?_ga=2.168691873.1408835506.1571651191-1030292064.1564583003
3.3.3. AssetBundle manifests
當使用BuildPiine.BuildAssetBundles API執(zhí)行AssetBundle構(gòu)建管線時,Unity會序列化一個包含每個AssetBundle依賴項信息的對象。此數(shù)據(jù)存儲在單獨的AssetBundle中,其中包含AssetBundleManifest類型的單個對象。
此Asset將存儲在與構(gòu)建AssetBundles的父目錄同名的AssetBundle中。如果一個項目將其AssetBundles構(gòu)建到位于(Projectroot)/Build/Client/的文件夾中,那么包含清單的AssetBundle將被保存為(Projectroot)/build/client/Client.manifest。
包含Manifest的AssetBundle可以像任何其他AssetBundle一樣加載、緩存和卸載。
AssetBundleManifest對象本身提供GetAllAssetBundles API來列出與清單同時構(gòu)建的所有AssetBundles,以及查詢特定AssetBundle的依賴項的兩個方法:
- AssetBundleManifest.GetAllDependencies返回AssetBundle的所有層次依賴項,其中包括AssetBundle的直接子級、其子級的依賴項等。
- AssetBundleManifest.GetDirectDependations只返回AssetBundle的直接子級
請注意,這兩個API分配的都是字符串數(shù)組。因此,最好是在性能要求不敏感的時候使用。
3.3.4. 建議
在多數(shù)情況下,最好在玩家進入應(yīng)用程序的性能關(guān)鍵區(qū)域(如主游戲關(guān)卡或世界)之前加載盡可能多的所需對象。這在移動平臺上尤為重要,因為在移動平臺上,訪問本地存儲的速度很慢,并且在運行時加載和卸載對象會觸發(fā)垃圾回收。
總結(jié)
以上是生活随笔為你收集整理的unity asset store下载不了_Unity手游实战:从0开始SLG——资源管理系统-基础篇(三)AssetBundle原理...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++中的深拷贝
- 下一篇: C++ multimap的插入