1. 程式人生 > >Unity資源管理(一)

Unity資源管理(一)

        序:之前有接做過打包的工作,但是一直也沒有整理下來,現在整理一下資源管理相關的順便整理一下吧。

Resources:Assets目錄下面新建Resources資料夾,其中的所有資源,不論是否被場景用到,都會被打包到遊戲中。

資源載入方式:

1.Resources.Load:載入Resources目錄的一個asset
2.Resources.LoadAsync:Resources.Load的非同步方法
3.Resources.LoadAll:類似Resources.Load,但是用於載入某目錄下所有asset
4.Resources.LoadAssetAtPath:載入Asset/目錄下的資源,只能用於編輯器模式,寫打包工具時可能用到

Resources類只能載入Resources資料夾下的資源,若出現巢狀,都會載入建議在Assets下放一個Resource資料夾就好;Resources載入資源時應使用相對路徑,且不包含副檔名。如 Resources.Load<Texture2D>("images/texture1"); 

解除安裝資源:

1.Resources.UnloadAsset(Object assetToUnload):解除安裝指定的asset,只能用於從磁碟載入的;如果場景中有此asset的引用,Unity會自動重新載入它,CPU開銷小。

2.Resources.UnloadUnusedAssets:解除安裝所有未被引用的asset,可以在畫面切換時呼叫,或定時呼叫釋放全域性未使用資源。被指令碼的靜態變數引用的資源不會被解除安裝。儘量避免在遊戲進行中呼叫,因為該介面開銷較大,容易引起卡頓,可嘗試用Resources.Unload(obj)逐個解除安裝,保證遊戲的流暢度。

AssetBundle:

上圖流程:

       1.Unity 在使用 WWW 方法時會分配一系列的記憶體空間來存放 WWW 例項物件、 WebStream 資料。該資料包括原始的 AssetBundle 資料、解壓後的 AssetBundle 資料以及一個用於解壓的 Decompression Buffer 。(一般情況下, Decompression Buffer 會在原始的 AssetBundle 解壓完成後自動銷燬,但需要注意的是, Unity 會自動保留一個 Decompression Buffer ,不被系統回收,這樣做的好處是不用過於頻繁的開闢和銷燬解壓 Buffer ,從而在一定程度上降低 CPU 的消耗。)

       2.當把AssetBundle 解壓到記憶體後,可以使用WWW .assetBundle屬性來獲取AssetBundle 物件,從而可以得到各種Asset,進而對這些Assets進行載入或者例項化操作。載入過程中,Unity 會將AssetBundle 中的資料流轉變為引擎可以識別的資訊型別(紋理、材質、物件等)。載入完成後,開發者可以對其進行進一步的操作,比如物件的例項化、紋理和材質的複製和替換等。

更新:

       遊戲一開始執行時,通過檔案裡面記錄的版本號,和伺服器上檔案中的版本號比對。如果本地版本號低,下載對應的AB包到可讀寫目錄,並對本地資源進行替換,這樣進入遊戲中載入的就是新下載的AB包資源。

更新流程如下:

       1.  將更新包資源(安裝包中的資源)複製到可讀寫目錄下
       2.  複製完成開始比對雜湊檔案,開始更新資源
       3.  下載新增資源,替換舊資源,刪除原來可讀寫目錄下的無用資源
       4.  初始化assetbundle依賴關係
       5.  完成整個流程

更新注意:

       1.  要有下載失敗重試幾次機制;
       2.  要進行超時檢測;
       3.  要記錄更新日誌,例如哪幾個資源時整個更新流程失敗。

資源載入:

       通過AssetBundle載入資源:首先需要獲取AssetBundle物件,然後通過該物件載入需要的資源。

       獲取AssetBundle物件分為兩種方式,可以通過下面兩種方式:

       一、先獲得WWW物件,通過WWW.assetBundle獲取AssetBundle物件

             1.  public WWW(string url);

             載入Bundle檔案並獲取WWW物件,每次載入完成後會在記憶體中建立較大的WebStream(解壓後的內容通常為原Bundle       檔案的4~5倍大小,紋理資源比例可能更大),因此後續的AssetBundle.Load可以直接在記憶體中進行。

             2.  public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0);
            從快取或網路上下載後加載Bundle並獲取WWW物件,同時將解壓形式的Bundle內容存入磁碟中作為快取(如果該       Bundle已在快取中,則省去這一步)。完成後只會在記憶體中建立較小的SerializedFile,而後續的AssetBundle.Load需要通過IO       從磁碟中的快取獲取。

              通過上面這兩個介面獲取WWW物件後,即可通過WWW.assetBundle獲取AssetBundle物件。

               總結:第一種WWW方式,後續的Load都在記憶體中進行,相比第二種方式IO操作開銷小;第一種不會形成快取檔案,而第二種需要額外的磁碟空間存放快取;第一種能通過WWW.texture,WWW.bytes,WWW.audioClip等介面直接載入外部資源,而後者只能用於載入AssetBundle。但是,第一種每次載入都涉及到解壓操作,而後者在第二次載入時就省去了解壓的開銷;第一種在記憶體中會有較大的WebStream,而後者在記憶體中只有通常較小的SerializedFile。(此項為一般情況,但並不絕對,對於序列化資訊較多的Prefab,很可能出現SerializedFile比WebStream更大的情況)

     二、直接獲取AssetBundle:

              1. public static AssetBundle CreateFromFile(string path);

              從磁碟載入一個AssetBundle,相比其他方式速度最快,但是隻能載入uncompressed的AssetBundle。Unity 5.x改為              LoadFromFile,而且可以載入compressed的;

               2.  public static AssetBundleCreateRequest CreateFromMemory(byte[] binary);通過Bundle的二進位制資料,非同步建立            AssetBundle物件。完成後會在記憶體中建立較大的WebStream。呼叫時,Bundle的解壓是非同步進行的,因此對於未壓縮的           Bundle檔案,該介面與CreateFromMemoryImmediate等價。

               3.  public static AssetBundle AssetBundle.CreateFromMemoryImmediate(byte[] binary):                                                     AssetBundle.CreateFromMemory的同步版本。

                Unity5.3下分別改名為LoadFromFile,LoadFromMemory,LoadFromMemoryAsync並增加了LoadFromFileAsync,且機制也有一定的變化
      獲得AssetBundle物件後可以通過下面API來從中載入資源

      1.  public Object LoadAsset(string name, Type type);

      通過給定的名字和資源型別,載入資源。載入時會自動載入其依賴的資源,即Load一個Prefab時,會自動Load其引用的Texture資源。

       2.  public Object[] LoadAllAssets(Type type);

       一次性載入Bundle中給定資源型別的所有資源。

       3.  public AssetBundleRequest LoadAssetAsync(string name, Type type);

       該介面是Load的非同步版本。

資源解除安裝:

       1.  WWW物件:呼叫物件的Dispose函式或將其置為null;

       2.  WebStream:在解除安裝WWW物件以及對應的AssetBundle物件後,這部分記憶體即會被引擎自動解除安裝;

       3.  SerializedFile:解除安裝AssetBundle後,這部分記憶體會被引擎自動解除安裝;

       4.  GameObject :可通過Object.Destory()解除安裝;

       5.  Prefab:只能通過DestroyImmediate()來解除安裝,解除安裝後必須重新載入AssetBundle才能重新載入該Prefab。由於記憶體開銷較小,通常不建議進行鍼對性地解除安裝;

       6.  普通資源(除Prefab):除了Resources類提供的解除安裝介面外,AssetBundle.Unload(false)在解除安裝AssetBundle物件時,將加載出來的資源一起解除安裝;

       7.  AssetBundle:

              1>AssetBundle.Unload(false):解除安裝AssetBundle物件時保留記憶體中已載入的資源,在解除安裝AssetBundle物件後,如果          重新建立該物件,並載入之前載入過的資源到記憶體時,會出現冗餘,即兩份相同的資源。

               2> AssetBundle.Unload(true):解除安裝AssetBundle物件時解除安裝記憶體中已載入的資源,由於該方法容易引起資源引用丟             失,因此並不建議經常使用。

壓縮格式:

        1.  LZMA格式

        在預設情況下,打包生成的AssetBundle都會被壓縮。在U3D中AssetBundle的標準壓縮格式便是LZMA(流式序列化檔案)。因此在預設情況下,打出的AssetBundle包處於LZMA格式的壓縮狀態,在使用AssetBundle前需要先解壓縮。使用LZMA格式壓縮的AssetBundle的包體積最小(高壓縮比),但是相應的會增加解壓縮時的時間。

        2.  LZ4格式

        Unity 5.3之後的版本增加了LZ4格式壓縮,由於LZ4的壓縮比一般,因此經過壓縮後的AssetBundle包體的體積較大(該演算法基於chunk)。但是,使用LZ4格式的好處在於解壓縮的時間相對要短。若要使用LZ4格式壓縮,只需要在打包的時候開啟BuildAssetBundleOptions.ChunkBasedCompression即可。

         3.  不壓縮

         我們也可以不對AssetBundle進行壓縮。沒有經過壓縮的包體積最大,但是訪問速度最快。若要使用不壓縮的策略,只需要在打包的時候開啟BuildAssetBundleOptions.UncompressedAssetBundle即可。

打包:

        Unity5.x打包:在資源的Inpector介面最下方可設定一個abName,每個abName(包含路徑)對應一個Bundle,即abName相同的資源會打在一個Bundle中。如果所依賴的資源設定了不同的abName,則會與之建立依賴關係,避免出現冗餘。

        介面:public static AssetBundleManifest BuildAssetBundles(string outputPath, BuildAssetBundleOptions      assetBundleOptions = BuildAssetBundleOptions.None,BuildTarget targetPlatform = BuildTarget.WebPlayer);

打包顆粒度:

       AssetBundle粒度過大,會影響打包、上傳、下載的效率,並且浪費使用者流量,同時在runtime時候IO這個Assetbundle會非常吃力,導致卡頓嚴重。

        Assetbundle粒度過小,整個工程有數百上千個Assetbundle,維護上會產生極大的不便,另外Runtime例項化一個角色時也會發生多次IO,這嚴重影響遊戲效能。在Unity 5.3 ~ 5.5 版本中,Android平臺上在不Unload的情況下,每個AssetBundle的載入,其每個檔案的SerializedFile記憶體佔用均為512KB(遠高於其他平臺),所以當記憶體中貯存了大量AssetBundle時,其SerializedFile的記憶體佔用將會非常巨大同時。不過該問題已在Unity5.6中進行完善。

       劃分Assetbundle的粒度主要考慮4個方面:資源型別、冗餘大小、程式效能、後期維護。在最終資源粒度劃分成型前,大概經歷了3個階段,每個階段所考慮的要素不同。

一、探索階段

       在熱更新模組設計初期就劃分出合適的粒度是比較困難的,在這個階段主要的目的是模組的實現,只需按照型別進行簡單劃分即可。資源的型別有:音樂(音效)、配置檔案、特效、Item、NPC、Monster、Role、UI、Scene、指令碼(Lua)。

       打包的策略可以參考如下幾項:

       1.  通常情況下,1M左右的AssetBundle包載入效能最好,冗餘也可以接受,但是在Unity 5.3版本之後,對於AB檔案的檔案大小其實不必再限定於1MB之內。使用LZ4壓縮,基於其Chunk的載入特點,AB載入很快,且記憶體佔用要比之前小很多。所以LZ4的AB其實可以考慮更加粗粒度一些。
       2.  根據依賴樹進行的最優打包策略,公共資源單獨打ab,獨立資源打到一起。
       3.  shader字型等其他細碎並且需要常駐記憶體的資源打包到一起,啟動遊戲的時候常駐記憶體。
       4.  根據專案實際需求將需要經常熱更新的資源進行單獨打包。

二、優化階段

        以常見的MMORPG專案來說,這個階段熱更新模組已經通過了內部測試。但是在實際使用過程中,會發現部分Assetbundle過大,載入時間較長,出現了明顯的卡頓,這時可以考慮拆分Monster、Role、Scene這3個焦點Assetbundle。

        1.  Monster的拆分標準是骨骼重定向,unity支援多個模型共用一套骨骼,從而共享一套模型動畫。使用這個機制會節省大量資源,按照這個原則,我們把所有共享骨骼的模型放在一個Assetbundle裡,這樣既減小了IO的壓力,又使冗餘做到了最小。
        2.  Role的拆分同樣遵循上述規則,同時由於role的武器是單獨的模型(支援換裝),也就是一個型別的Role對應兩個AssetBundle,Role本身+武器。
        3.  Scene的拆分比較簡單,之前是所有的Scene一個Assetbundle,現在改為一種型別scene一個Assetbundle。

三、測試階段

       此階段主要對專案進行載入速度、IO佔用、資源冗餘來做整體測試,發現區域性問題並針對性解決。

記憶體佔用

1.  對於需要常駐記憶體的Bundle檔案來說,

       優先考慮減小記憶體佔用,因此對於存放非Prefab資源(特別是紋理)的Bundle檔案,可以考慮使用WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile載入,從而避免WebStream常駐記憶體;而對於存放較多Prefab資源的Bundle,則考慮使用new WWW載入,因為這類Bundle用WWW.LoadFromCacheOrDownload載入時產生的SerializedFile可能會比new WWW產生的WebStream更大。

2.  對於載入完後即解除安裝的Bundle檔案

       分兩種情況:優先考慮速度(載入場景時)和優先考慮流暢度(遊戲進行時)。

       1)載入場景的情況下,需要注意的是避免WWW物件的逐個載入導致的CPU空閒,可以考慮使用載入速度較快的WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile,但需要避免後續大量地進行Load資源的操作,引起IO開銷(可以嘗試直接LoadAll)。

       2) 遊戲進行的情況下,則需要避免使用同步操作引起卡頓,因此可以考慮使用new WWW配合AssetBundle.LoadAsync來進行平滑的資源載入,但需要注意的是,對於Shader、較大的Texture等資源,其初始化操作通常很耗時,容易引起卡頓,因此建議將這類資源在載入場景時進行預載入。

3.  只在Bundle需要加密的情況下,考慮使用CreateFromMemory,因為該介面載入速度較慢。

資源冗餘

       Unity 5.x版本里會自動收集並分析其依賴的資源,如果該資源依賴的某個資源沒有被顯式指定打包到ab中,就將其依賴的這個資源打包進該資源所在的ab裡。如果已經被指定打包進其他ab裡,那麼這兩個ab之間就會構成依賴關係,載入ab時,先載入其依賴的ab即可。

       雖然這種依賴管理機制使用方便,但是會引起資源冗餘問題:如果兩個ab包A和B,其中的一些資源都依賴了一個沒有被指定要打包的資源C,那麼C就會同時被打進ab A和B中,增大ab和安裝包的體積。而這個被A,B依賴的資源C又可以分為兩種型別,一種是Assets下外部匯入的資源,即開發者匯入或建立的資源;另一種則是Unity內建的資源,例如內建的Shader,Default-Material和UGUI一些元件如Image用的一些紋理資源等等。下面分析這兩種情況處理:

       一、被依賴的外部資源

       將那些被多個ab包依賴的資源打包到一個公共ab包中。處理過程如下:

       1.  使用EditorUtility.CollectDependencies()得到ab依賴的所有資源的路徑,其中會收集到不需打包進ab中的指令碼、dll、編輯器資源,需要手動剔除;
       2.  統計資源被所有ab引用的次數,將被多個ab引用的資源打包為公共ab包。

       二、Unity內建資源

            可以通過提取出Unity內建的資源,在打ab前進行預處理,修改引用為提取出的資源。

            使用AssetDataBase.LoadAllAssetsAtPath() 可以加載出Resources/unity_builtin_extra下所有的Object,可以發現共有4種      型別的資源:Shader,Material,Texture以及Sprite。對於內建Shader,可以直接從Unity官方網站下載;而對於後三種,可        以通過AssetDataBase提供的相關API來進行建立:
                Object[] UnityAssets = AssetDatabase.LoadAllAssetsAtPath("Resources/unity_builtin_extra");
                foreach (var asset in UnityAssets)
                {
                     // create asset...
                }

................................................

參考:https://blog.uwa4d.com/archives/ABTheory.html

            https://blog.uwa4d.com/archives/ABTheory.html