1. 程式人生 > >Unity AssetBundles and Resources指引 (四) AssetBundle使用模式

Unity AssetBundles and Resources指引 (四) AssetBundle使用模式

本文內容主要翻譯自下面這篇文章

https://unity3d.com/cn/learn/tutorials/topics/best-practices/guide-assetbundles-and-resources?playlist=30089 A guide to AssetBundles and Resources

 

本部分討論AssetBundle實際應用中一切潛在的問題和解決方案。

1.1 管理載入的資源

在記憶體敏感的環境裡面要嚴格控制載入Object的大小和數量。當Object從當前啟用場景中移除時,Unity不會自動解除安裝他們。清除Asset只有在特殊的時刻被觸發。當然也可以手動觸發。

AssetBundle子什麼也要仔細管理。從檔案載入的AssetBundle佔用的記憶體最小,一般不超過10到40KB。但是當大量AssetBundle出現的時候還是會出現問題。

因為大部分專案執行使用者重複體驗相同的內容,比如重玩一個關卡,所以知道什麼時候載入和解除安裝AssetBundle很重要。如果一個AssetBundle被不正確的解除安裝,會造成Object佔用雙倍記憶體,在某些情況下還會造成別的不可預期的問題。比如貼圖丟失。

理解了AssetBundle.Unload方法的引數true和false的區別對應管理assets和AssetBundle非常重要。這個api解除安裝掉被呼叫的AssetBundle的頭資訊。裡面的引數會指定是否同時解除安裝從AssetBundle裡面例項化的Objects。如果引數為true,則從這個AssetBundle裡面建立的所有objects都被立即解除安裝,即使正在被當前的場景使用。

比如下圖所示

材質M從AssetBundle AB裡面載入的,而且M正在當前場景中被使用。如果Unload(true)被呼叫,則M會從當前場景移除並銷燬掉。但是如果呼叫的是Unload(false),則AB的頭資訊被解除安裝了但是M仍然留在場景裡面起作用。但是M和AB的聯絡唄打斷了。如果AB重新被載入,新的objects拷貝會重新載入進記憶體。

如果AB後來又被載入的話,新的一份AssetBundle的頭資訊拷貝到記憶體。但是M不再是從新的AB裡面載入的。Unity不會給M和新的AB建立連線。

如果這是再次呼叫LoadAsset方法重新載入M,Unity不會任務原來的那個M是從AB裡面資料的例項化,而是從新載入一份新的M。導致場景裡面有兩份一樣的M。

對很多專案來說,這是不期望的行為。大部分專案都才用AssetBundle.Unload(true)和措施來確保物件不會被複制。兩個常用的方案是

  1. 找一個應用程式生命週期某個設計好的時點解除安裝所有的AssetBundle。比如關卡切換或者在過載場景。這是最簡單常用的選項。
  2. 維護單個物件的object的引用計數。當一個AssetBundle的所有物件沒有被使用時解除安裝AssetBundle。

如果一個應用必須採用Unload(false),這些Objects只能用兩種方式解除安裝:

  1. 確保沒有對這個物件的引用,無論是程式碼還是場景裡面。然後呼叫 Resources.UnloadUnusedAssets.
  2. 以非附加的方式載入一個場景。這回自動銷燬當前場景裡面的objects然後呼叫1裡面的方法。

 

如果專案有設計好的點,使用者在這裡等待物件的載入和解除安裝。比如遊戲模式或者關卡之間的切換,這個時點就可以用來解除安裝不需要的Objects並載入新的物件。最簡單的方式就是資源以場景的方式分割。然後把每個場景及其裡面的依賴打包到AssetBundle裡面。程式可以加入一個載入場景,在這裡解除安裝老場景裡面的物件,然後載入包含新場景的AssetBundle。顯然這是一個簡單的流程,一些專案需要更復雜的AssetBundle管理。這裡沒有統一的設計樣式,每個專案都不一樣。當考慮怎麼樣Objects分組打包時,如果物件總是一起載入和更新,就可以考慮打包進一個AssetBundle。

比如一個角色扮演遊戲,就可以把地圖和過場動畫以場景分組打包。但是有些物件所有的場景都需要。比如頭像,遊戲UI和角色模型以及貼圖等,後面的物件可以打包進第二套AssetBundle,並且在遊戲開始時載入,然後整個生命週期都存在。

另外一個問題就是如果從一個已經解除安裝的AssetBundle裡面載入Object,就會載入失敗,在unity的編輯器的Hierarchy面板裡面顯示一個丟失的物件。這個問題經常出在線unity丟失了繪圖上下文而需要重新獲取時。比如一個移動應用被掛載了或者使用者鎖定了他的pc。這時候,unity會重新載入貼圖和著色器到GPU裡面。如果AssetBundle已經失效了(被解除安裝),這個應用程式就會用洋紅色渲染這個物件。

4.2分發

有兩種方式分發AssetBundle到客戶端:一種直接放到安裝包裡面另外一種是安裝後下載。那種方式根據專案型別來選用。移動專案一般採用安裝後下載。控制檯和PC專案一般把AssetBundle隨安裝包一些分發。

正確的架構使得安裝後仍可以打補丁或者修正內容而不管開始AssetBundle時如何分發的。

1.2.1 同項目一起分發

這是最簡單的方式,因為不需要附加的下載程式碼。採用這種方式有兩個主要理由:

  1. 減少專案編譯次數同時可以開發迭代也簡單。因為如果AssetBundle不需要同應用隔離開來的話,可以直接把他們儲存到streamingassets目錄。
  2. 開始就分發可更新資源,就節約了終端使用者的時間。Streamingassets不適合這種需求。如果不想自己寫一個下載和快取管理器,那麼初次可更新內容可以從streaming assets載入到Cache裡面。

1.2.1.1 Streaming Assets

如果想在安裝後就包含內容的最簡單方式就是把該記憶體放到/Assets/StreamingAssets/目錄。在編譯的時候,任何在該目錄的內容都會被拷貝到最終的應用程式裡面。這個目錄可以用來儲存任何內容而不僅僅是AssetBundle。

執行時,完整路徑可以通過Application.streamingAssetsPath屬性來訪問。在大部分的平臺上面他可以通過LoadFromFile來載入裡面的AssetBundle。但是在安卓平臺上面,這個屬性指向一個壓縮的jar檔案。甚至AssetBundles都是被壓縮了。這種情況下,WWW.LoadFromCacheOrDwonLoad可以用來載入AssetBundle。當然也可以自己寫程式碼來解壓jar報,然後抽取AssetBundle到可讀的本地目錄。

注意Streaming Assets在某些平臺是不寫的。如果AssetBundle安裝後需要更新。需要使用WWW. LoadFromCacheOrDwonLoad或者自己寫下載器。

1.2.2 安裝後下載

在移動平臺上常用該方法。這允許安裝後內容可以更新或優化而不用使用者重新下載整個應用。在移動平臺上面,應用審批流程總是很麻煩的。因此更新系統是必不可少的。

最簡單的方式就是講AssetBundle放到web伺服器上面。然後通過WWW或者UnityWebRequest下載。unity會自動儲存下載好的AssetBundles。如果下載的是LZMA壓縮的內容。unity會解壓存放。如果是LZ4壓縮的。則下載後也是壓縮儲存的。如果快取不足,unity會移除最少使用的AssetBundle。

但是WWW. LoadFromCacheOrDwonLoad是有瑕疵的,正如前面所述,這個在下載時候會消耗AssetBundle資料大小的記憶體,可能會導致記憶體問題。有三種方式來避免

  1. 保證AssetBundle夠小。
  2. 5.3或者更新的版本,用UnityWebRequest。
  3. 自定義下載器。

一般建議儘可能使用UnityWebRequest,或者5.2以及前面的版本使用WWW,只有內建的系統在記憶體消耗快取行為或者效能不可接受的情況下才建議使用自定義下載系統。

下面是不適合使用WWW者UnityWebRequest的情況:需要更好地控制AssetBundle快取。專案需要自定義壓縮策略。專案需要平臺相關的API。比如離線下載比如IOS後臺任務可以下載。或者AssetBundle需要通過SSL協議下載。

1.2.3 內建的AssetBundle的快取

在使用WWW和UnityWebRequest時下載的AssetBundle被儲存在內建的快取裡面。兩個API都有一個過載的版本,接收一個AssetBundle版本號,這個數字不是儲存在AssetBundle裡面。而且不是AssetBundle系統生成的。

快取系統跟蹤這個版本號。呼叫api的時候,快取系統首先檢查資源十分存在,如果有,則比較上一次快取的版本號。如果匹配,則從快取載入,如果不匹配,或者沒有快取,則unity去下載一個新版本,並同這個版本號關聯起來。

AssetBundle使用名字來認證的而不是下載的URL。這意味著相同名字的AssetBundle可以存在不同的位置。比如一個AssetBundle可以儲存在多個伺服器上面或者CDN上。只要這個名字是一樣的。快取系統就認為他們是相同的AssetBundle。

給AssetBundle分配版本號是開發者的責任。大部分應用使用unity5的assetbundleManifestApi。這個API生成通過AssetBundle的內容MD5雜湊值為該AssetBundle生成一個版本號。當AssetBundle改變的時候,他的雜湊值也變化了。這就意味著AssetBundle需要下載。注意unity快取系統並沒有刪除就得AssetBundle,直到cache被填滿。unity後續版本可能會修復這個問題。

1.2.4 自定義下載(略)

1.3 Asset 分配策略

怎麼分配專案的資源到不同的AssetBundle不是那麼簡單。一個簡單的誘人的方案就是每個物件放進自己的AssetBundle或者只用一個單獨的AssetBundle。但是這些方案都有缺點:

有過少的AssetBundle:增加了執行時記憶體消耗。增加了載入時間,需要很大的下載。

如果是過多的AssetBundle呢?增加了編譯時間,增加了開發的複雜性,增加了總共的下載時間。

怎麼把物件分組到AssetBundle的關鍵思考點在於邏輯實體物件型別和和併發的內容。

要知道一個專案可能會混合這些分類策略。比如一個專案會把不同平臺的ui打包到各自的AssetBundle。把不互動的內容按場景打包。廈門是一些好的建議:

  1. 把經常更新的內容和不變的內容打包到不同的AssetBundle。
  2. 把同時載入的內容打包到一起。比如一個模型和他的紋理以及動畫。
  3. 如果一個物件依賴多個不同的AssetBundle裡面的物件,把這些資源移到一個單獨的AssetBundle。
  4. 把子物件和父物件分組到一起。
  5. 如果兩個物件不會一起載入。則打包到不同的AssetBundle。
  6. 如果物件是同一個物件的不同的匯入設定版本。考慮使用AssetBundle變數替代。

如果上述規則遵守了,然後把一個AssetBundle每次只有少於50%的內容被載入的繼續分割。把一些小的AssetBundle(少於5到10個assets)進行重組一個AssetBundle。

1.3.1 邏輯實體分組

這個策略是按照功能分組。應用的不同部分被打包進不同的AssetBundle。

比如打包所有UI的紋理和佈局在一起。打包所有角色的貼圖模型和動畫在一起。

1.3.2 型別分組

這是最簡單的策略。相似型別的打包進相同的AssetBundle。比如聲音檔案打包到一個AssetBundle。不同的語言檔案打包進一個AssetBundle。

1.3.3 併發內容分組

同時載入的內容打包一起。

1.4 用AssetBundles打補丁

AssetBundle打補丁簡單的只要重新下載一個新的AssetBundle替代舊有的就可以了。只要傳遞一個不同的版本號給API。更困難問題是檢測什麼時候需要打補丁。一個打補丁系統需要2個資訊。

  1. 當前下載的AssetBundle和版本資訊列表。
  2. 伺服器上面的AssetBundle和版本資訊列表。

補丁系統需要下載服務端列表,同本地比較,丟失的或者版本不匹配的都要重新下載。Unity5 AssetBundle系統在編譯AssetBundle完成之後生成一個附加的AssetBundle。這個AssetBundle包含 AssetBundleManifest物件。這物件包含了AssetBundle和雜湊列表。也可以自己定製檢測AssetBundle改變系統,比如用Json系統和標準的C#校驗和類MD5.

1.5 常見陷阱

1.5.1 Asset複製

當一個Object打包進一個AssetBundle時,unity5 會自動檢查所有的依賴Objects。依賴資訊被用來決死你個這些Objects是否包含到一個AssetBundle。

顯式指定一個物件打包到一個AssetBundle時,這個物件只被打包到指定的AssetBundle。顯式指定是指在編輯器裡面AssetBundle屬性欄設定了一個非空值。

如果一個Objects沒有顯式指定一個AssetBundle,則會被打包進所有依賴他的AssetBundle裡面。

如果兩個Object被打到不停地AssetBundle,但是這兩個都引用一個共同的Object,這個Object卻沒有設定打包資訊,那麼這個Object會被拷貝到兩個AssetBundle裡面。這複製的兩個也會單獨例項化。即會增加應用的AssetBundle的尺寸。如果程式載入兩個父物件,也有有2份這個子物件的單獨記憶體拷貝。

解決方法:

  1. 確保打包到不同的AssetBundle沒有共享的依賴。有共享依賴的Objects打包到同一個AssetBundle。但是如果專案有非常多的共享依賴,這就變得不可行了。他會造成一個單一的AssetBundle。帶來下載和效率問題。
  2. 保證有共享依賴的AssetBundle不會同時載入。但仍然會加大程式尺寸。
  3. 把依賴的Objects打到自己的AssetBundle裡面。這消除了複製問題,但是帶來了複雜性。應用程式必須跟蹤依賴。

Unity5用AssetDataBase來跟蹤依賴。他位於UnityEditor名稱空間。這個Api只能在Editor環境而不能在執行時使用。AssetImporter API可用來查詢某個Object被賦予哪個AssetBundle。聯合使用這兩個API。可以寫一個編輯器指令碼確保所有的直接和間接依賴都賦予AssetBundles或者共享的依賴都被賦值到某個AssetBundle。建議專案使用這兩個API。

1.5.2 圖集精靈複製

接下來討論unity 5在計算自動生成的圖集精靈的依賴時的奇怪行為。Unity5.2.2p4修復了這個行為。

Unity5.2.2p4,5.3或更新

任何自動生成的精靈圖集會被賦給包含這個生成sprite物件的AssetBundle。如果這個精靈Objects打包到多個AssetBundle,那麼這個圖集就不會打包到一個AssetBundle,而是被複制。如果精靈物件沒有賦值給一個AssetBundle,那麼就不會被打包。為了保證不復制,需要檢查這個圖集下所有的精靈都會被打包到同一個AssetBundle。

Unity5.2.2p4或者更老的版本

自動生成的圖集不會賦值到某個AssetBundle。因此他們會包含到任何引用他們精靈的AssetBundle中。因為這個問題建議升級到更高的unity版本。對於不能升級的專案,建議:

1簡單的方式就是不要用內建的精靈打包器。

2難一點的方式就是把所有的使用這個圖集的物件打包到同一個AssetBundle。

1.6 AssetBundle Variants

1.7 壓縮與否?

是否壓縮需要仔細考慮:

載入時間是否是關鍵因素?未壓縮的載入時間會快很多。但是從遠端下載壓縮的資源也會比未壓縮的的塊。

編譯時間是否是關鍵因素?LZMA和LZ4編譯時非常慢。如果專案有很多AssetBundle,則要花掉大量的時間。

應用程式尺寸是否關鍵因素?

記憶體消耗是否是關鍵因素?

下載時間是否是關鍵因素?

1.8 AssetBundle和WEBGL

unity強烈建議WEBGL專案不要使用壓縮的AssetBundle。因為unity5.3在主執行緒上解壓AssetBundle。(下載AssetBundle會通過XMLHttpRequest委託給瀏覽器。不在主執行緒上)這意味著在WEBGL裡面載入壓縮的資源非常消耗。

記住這些,你就會避免LZMA格式,而採用LZ4格式。因為後者的解壓很高效。如果你在意資源大小,那就採用lz4來分發,並且配置服務端支援gzip壓縮。

轉自https://www.cnblogs.com/qzzlw/p/5957581.html