【Unity游戲開發】AssetBundle雜記--AssetBundle的二三事


一、簡介

  馬三在公司大部分時間做的都是游戲業務邏輯和編輯器工具等相關工作,因此對Unity AssetBundle這塊的知識點並不是很熟悉,自己也是有打算想了解並熟悉一下AssetBundle,掌握一下Unity資源管理方面的知識,因此有了這篇博客。

  我會在博客中整理出一些自己學習時收集到的一些比較好的AssetBundle學習資料,並且結合現在正在參與開發的商業項目隨時寫下一些自己的拙見。本篇博客權當是馬三自己的筆記和一些雜談,會不斷地隨時隨地的更新一些內容。

二、收集整理的一些AssetBundle資料

1.Unity AssetBundle5講系列

Unity5-ABSystem(一):AssetBundle原理

Unity5-ABSystem(二):AssetBundle導出

Unity5-ABSystem(三):AssetBundle加載

Unity5-ABSystem(四):AssetBundle依賴

Unity5-ABSystem(五):AssetBundle內存

2.慕容小匹夫系列

Unity3D 5.3 新版AssetBundle使用方案及策略

進擊的AssetBundles和它的工具們

3.何三思譯Unity AssetBundle官方文檔系列

【Unity3D技術文檔翻譯】第1.0篇 AssetBundles

【Unity3D技術文檔翻譯】第1.1篇 AssetBundle 工作流

【Unity3D技術文檔翻譯】第1.2篇 為打包 AssetBundles 准備資產

【Unity3D技術文檔翻譯】第1.3篇 創建 AssetBundles

【Unity3D技術文檔翻譯】第1.4篇 AssetBundle 依賴關系

【Unity3D技術文檔翻譯】第1.5篇 本地使用 AssetBundles

【Unity3D技術文檔翻譯】第1.6篇 使用 AssetBundle Manager

【Unity3D技術文檔翻譯】第1.7篇 AssetBundles 補丁更新

【Unity3D技術文檔翻譯】第1.8篇 AssetBundles 問題及解決方法

【Unity3D技術文檔翻譯】第1.9篇 使用 Unity AssetBundle Browser tool (AssetBundle系列完結)

4.Unity AssetBundle官方文檔

AssetBundlesManual

A guide to AssetBundles and Resources

5.Unity Assetbundles官方說明系列

Unity5.4 Assetbundles官方說明一(AssetBundles打包詳解)

Unity5.4 Assetbundles官方說明二(AssetBundle壓縮與解壓)

Unity5.4 Assetbundles官方說明三(AssetBundle資源包的內部結構)

Unity5.4 Assetbundles官方說明四(AssetBundles的下載和加載)

Unity5.4 Assetbundles官方說明五(從AssetBundles的加載和卸載資源對象)

Unity5.4 Assetbundles官方說明六(保留下載的AssetBundle)

Unity5.4 Assetbundles官方說明七(在AssetBundle中存儲和加載二進制數據)

Unity5.4 Assetbundles官方說明八(數據安全方面的處理)

Unity5.4 Assetbundles官方說明九(資源包中包含腳本文件)

Unity5.4 Assetbundles官方說明十(官方疑難問題解答)

Unity5.4 Assetbundles十一:遇到的坑和整理的打包和加載流程(資源包更新的簡易框架)

6.未規划分類

Unity AssetBundle 從入門到掌握(適合初學者)

Unity5 如何做資源管理和增量更新

Unity資源處理機制(Assets/WWW/AssetBundle/...)讀取和加載資源方式詳解

Unity3D中實現按資源名稱自動化命名打包AssetBundle

Unity動態加載和內存管理(三合一)

Unity手游之路<十二>手游資源熱更新策略探討

Unity5 資源打包控制

Unity AssetBundle加載音頻,無法播放音效並報錯的坑

Unity打包AssetBundle自動分析資源依賴關系(包括UGUI圖集打包)

Unity最佳實踐-AssetBundle使用模式

Unity 5.x AssetBundle零冗余解決方案

資源依賴正確性測試

Unity AssetBundle 再回顧

AssetBundle資源打包加載管理

7.AssetBundle分組策略總結

邏輯實體分組
一個UI界面或者所有UI界面一個包(這個界面里面的貼圖和布局信息一個包)
一個角色或者所有角色一個包(這個角色里面的模型和動畫一個包)
所有的場景所共享的部分一個包(包括貼圖和模型)
按照類型分組
所有聲音資源打成一個包,所有shader打成一個包,所有模型打成一個包,所有材質打成一個包
按照使用分組
把在某一時間內使用的所有資源打成一個包。可以按照關卡分,一個關卡所需要的所有資源包括角色、貼圖、聲音等打成一個包。也可以按照場景分,一個場景所需要的資源一個包
注意
經常更新的資源放在一個單獨的包里面,跟不經常更新的包分離
把需要同時加載的資源放在一個包里面
可以把其他包共享的資源放在一個單獨的包里面
把一些需要同時加載的小資源打包成一個包
如果對於一個同一個資源有兩個版本,可以考慮通過后綴來區分,例如v1、v2、v3

三、AssetBundle踩坑與經驗集錦

  1、先說一個遇到的坑,當大量(幾百個)AssetBundle加載的時候(可能是WWW加載的時候,也可能是AssetBundle.LoadAsset的時候),Android手機上會閃退。看崩潰log是多線程文件訪問的時候崩潰了。解決方法是減少同時加載的AB數量(這個是純邏輯控制),使用AssetBundle.LoadFromFile接口。  

  2、打包AssetBundle使用LZ4壓縮(BuildPipeline.BuildAssetBundles,第二個參數傳遞BuildAssetBundleOptions.ChunkBasedCompression),默認是LZMA壓縮的,具有最高的壓縮比。而替換為LZ4壓縮,壓縮比沒有LZMA高,但是加載速度大幅提高。加載AssetBundle使用AssetBundle.LoadFromFile(Async),在Unity4的時候,只能使用WWW的接口來加載AB,因為CreateFromFile不支持壓縮的AB。而Unity5的LoadFromFile是支持任意壓縮格式的AB的。所以沒有太大必要使用WWW了,而且這個接口像WWW.LoadFromCacheOrDownload接口一樣,加載不壓縮或者LZ4壓縮格式的AB的時候是不會有額外的內存開銷的。具體關於內存、加載速度的細節可以參考上面第三篇文章里面的介紹。  

3、資源規划好一個獨立的資源工程。規划好一系列的文件夾,在導入相應資源的時候自動通過AssetImporter設置好AB的名字。監測資源導入可以用AssetPostprocessor 。帶動畫的模型需要創建好prefab,而不帶動畫只是用於換裝的模型可以直接導出,不需要創建prefab,因為這些模型我們只是取它的mesh數據。如果有打包圖集,需要注意它和AB的匹配關系,舉例來說,如果三張圖片指定了同一個圖集,而又分別指定了不同的AB名,則三個AB里面都包含了此圖集(三張圖片),這樣就會造成嚴重的資源浪費。  

4、AssetBundle.LoadFromFile接口在Android平台下也是可以直接訪問StreamingAssets文件夾里面的內容的。5.4版本可以直接使用Application.streamingAssetsPath。而之前的版本需要使用 Application.dataPath + "!assets/" + filePath;  因為streamingAssetsPath帶了jar://,這個是給WWW用的URL路徑,而LoadFromFile接口需要的是實際路徑(不帶jar://也不帶file://)。注意 !assets/ 這個地方嘆號后面沒有/。網上搜索到的各種寫法都有,只有這個是正確的,注意此處細節。

 

四、Unity AssetBundle爬坑手記

文章轉載自:http://www.cnblogs.com/ybgame/p/3973177.html  

這篇文章從AssetBundle的打包,使用,管理以及內存占用各個方面進行了比較全面的分析,對AssetBundle使用過程中的一些坑進行填補指引以及噴!
 
AssetBundle是Unity推薦的資源管理方式,官方列舉了諸如熱更新,壓縮,靈活等等優點,但AssetBundle的坑是非常深的,很多隱藏細節讓你使用起來需要十分謹慎,一不小心就會掉入深坑,打包沒規划好,20MB的資源“壓縮”到了30MB,或者大量的包導致打包以及加載時的各種低效,或者莫名其妙地丟失關聯,或者內存爆掉,以及各種加載失敗,在網上研究了大量關於AssetBundle的文章,但每次看完之后,還是有不少疑問,所以只能通過實踐來解答心中的疑問,為確保結果的准確性,下面的測試在編輯器下,Windows,IOS下都進行了測試比較。
 
首先你為什么要選擇AssetBundle,縱使他有千般好處,但一般選擇AssetBundle的原因就是,要做熱更新,動態更新游戲資源,或者你Resource下的資源超過了它的極限(2GB還是4GB?),如果你沒有這樣的需求,那么建議你不要使用這個壞東西,鬧心~~
 
當你選擇了AssetBundle之后,以及我開始噴AssetBundle之前,我們需要對AssetBundle的工作流程做一個簡單的介紹:
AssetBundle可以分為打包AssetBundle以及使用AssetBundle
 
打包需要在UnityEditor下編寫一些簡單的代碼,來取出你要打包的資源,然后調用打包方法進行打包
1 Object obj = AssetDatabase.LoadMainAssetAtPath("Assets/Test.png");
2 BuildPipeline.BuildAssetBundle(obj, null,
3                                   Application.streamingAssetsPath + "/Test.assetbundle",
4                                  BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
5                                  | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.StandaloneWindows);

在使用的時候,需要用WWW來加載Bundle,然后再用加載出來的Bundle來Load資源。

WWW w = new WWW("file://" + Application.streamingAssetsPath + "/Test.assetbundle");
myTexture = w.assetBundle.Load("Test");
【一,打包】
 
接下來我們來看一下打包:
 
1.資源的搜集
 
    在打包前我們可以通過遍歷目錄的方式來自動化地進行打包,可以有選擇性地將一些目錄打包成一個Bundle,這塊也可以用各種配置文件來管理資源,也可以用目錄規范來管理
    我這邊是用一個目錄規范對資源進行大的分類,分為公共以及游戲內,游戲外幾個大模塊,然后用一套簡單命名規范來指引打包,例如用OBO(OneByOne)作為目錄后綴來指引將目錄下所有資源獨立打包,默認打成一個包,用Base前綴來表示這屬於公共包,同級目錄下的其他目錄需要依賴於它
 
    使用Directory的GetFiles和GetDirectories可以很方便地獲取到目錄以及目錄下的文件
Directory.GetFiles("Assets/MyDirs", "*.*", SearchOption.TopDirectoryOnly);
    Directory.GetDirectories(Application.dataPath + "/Resources/Game", "*.*", SearchOption.AllDirectories);
2.資源讀取
    GetFiles搜集到的資源路徑可以被加載,加載之前需要判斷一下后綴是否.meta,如果是則不取出該資源,然后將路徑轉換至Assets開頭的相對路徑,然后加載資源。
string newPath = "Assets" + mypath.Replace(Application.dataPath, "");
    newPath = newPath.Replace("\\", "/");
    Object obj = AssetDatabase.LoadMainAssetAtPath(newPath);
3.打包函數
 
    我們調用BuildPipeline.BuildAssetBundle來進行打包:
    BuildPipeline.BuildAssetBundle有5個參數,第一個是主資源,第二個是資源數組,這兩個參數必須有一個不為null,如果主資源存在於資源數組中,是沒有任何關系的,如果設置了主資源,可以通過Bundle.mainAsset來直接使用它
    第三個參數是路徑,一般我們設置為  Application.streamingAssetsPath + Bundle的目標路徑和Bundle名稱
    第四個參數有四個選項,BuildAssetBundleOptions.CollectDependencies會去查找依賴,BuildAssetBundleOptions.CompleteAssets會強制包含整個資源,BuildAssetBundleOptions.DeterministicAssetBundle會確保生成唯一ID,在打包依賴時會有用到,其他選項沒什么意義
    第五個參數是平台,在安卓,IOS,PC下,我們需要傳入不同的平台標識,以打出不同平台適用的包, 注意,Windows平台下打出來的包,不能用於IOS

 在打對應的包之前應該先選擇對應的平台再打包

4.打包的決策
 
    在打包的時候,我們需要對包的大小和數量進行一個平衡,所有資源打成一個包,一個資源打一個包,都是比較極端的做法,他們的問題也很明顯,更多情況下我們需要靈活地將他們組合起來
    打成一個包的缺點是加載了這個包,我們不需要的東西也會被加載進來,占用額外內存,而且不利於熱更新
    打成多個包的缺點是,容易造成冗余,首先影響包的讀取速度,然后包之間的內容可能會有重復,且太多的包不利於資源管理
    哪些模塊打成一個包,哪些模塊打成多個包,需要根據實際情況來,例如游戲中每個怪物都需要打成一個包,因為每個怪物之間是獨立的,例如游戲的基礎UI,可以打成一個包,因為他們在各個界面都會出現
 
    PS.想打包進AssetBundle中的二進制文件,文件名的后綴必須為“.bytes”
【二,解包】
    解包的第一步是將Bundle加載進來,new一個WWW傳入一個URL即可加載Bundle,我們可以傳入一個Bundle的網址,從網絡下載,也可以傳入本地包的路徑,一般我們用file://開頭+Bundle路徑,來指定本地的Bundle,用 http://https://開頭+Bundle網址來指定網絡Bundle
    
string.Format("file://{0}/{1}", Application.streamingAssetsPath, bundlePath);
 
    在安卓下路徑不一樣,如果是安卓平台的本地Bundle,需要用jar:file://作為前綴,並且需要設置特殊的路徑才能加載
 
string.Format("jar:file://{0}!/assets/{1}", Application.dataPath, bundlePath);
 
    傳入指定的URL之后,我們可以用WWW來加載Bundle,加載Bundle需要消耗一些時間,所以我們一般在協同里面加載Bundle,如果加載失敗,你可以在www.error中得到失敗的原因
 
復制代碼
IEnumerator LoadBundle(string url)
{
    WWW www = = new WWW(url);
    yield return www;

    if (www.error != null)
    {
    Debug.LogError("Load Bundle Faile " + url + " Error Is " + www.error);
    yield break;
    }

    //Do something ...
}
復制代碼
    除了創建一個WWW之外,還有另一個方法可以加載Bundle, WWW.LoadFromCacheOrDownload(url, version),使用這個函數對內存的占用會小很多,但每次重新打包都需要將該Bundle對應的版本號更新(第二個參數version),否則可能會使用之前的包,而不是最新的包,LoadFromCacheOrDownload會將Bundle從網絡或程序資源中,解壓到一個磁盤高速緩存,一般可以理解為解壓到本地磁盤,如果本地磁盤已經存在該版本的資源,就直接使用解壓后的資源。對於AssetBundle所有對內存占用的情況,后面會有一小節專門介紹它
 
    LoadFromCacheOrDownload會記錄所有Bundle的使用情況,並在適當的時候刪除最近很少使用的資源包,它允許存在兩個版本號不同但名字一樣的資源包,這意味着你更新這個資源包之后,如果沒有更新代碼中的版本號,你可能取到的會是舊版本的資源包,從而產生其他的一些BUG。另外,當你的磁盤空間不足的時候(硬盤爆了),LoadFromCacheOrDownload只是一個普通的new WWW!后面關於內存介紹的小節也會對這個感嘆號進行介紹的
 
    拿到Bundle之后,我們就需要Load里面的資源,有Load,LoadAll以及LoadAsyn可供選擇
 
復制代碼
    //將所有對象加載資源
    Object[] objs = bundle.LoadAll();
 
    //加載名為obj的資源
    Object obj = bundle.Load("obj");
 
    //異步加載名為resName,類型為type的資源
    AssetBundleRequest res = bundle.LoadAsync(resName, type);
        yield return res;
    var obj = res.asset;
復制代碼
    我們經常會把各種游戲對象做成一個Prefab,那么Prefab也會是我們Bundle中常見的一種資源,使用Prefab時需要注意一點, 在Bundle中加載的Prefab是不能直接使用的,它需要被實例化之后,才能使用,而對於這種Prefab,實例化之后,這個Bundle就可以被釋放了
 
    //需要先實例化
    GameObject obj = GameObject.Instantiate(bundle.Load("MyPrefab")) as GameObject;
 
    對於從Bundle中加載出來的Prefab,可以理解為我們直接從資源目錄下拖到腳本上的一個Public變量,是未被實例化的Prefab,只是一個模板
 
    如果你用上面的代碼來加載資源,當你的資源慢慢多起來的時候,你可能會發現一個很坑爹的問題,你要加載的資源加載失敗了,例如你要加載一個GameObject,但是整個加載過程並沒有報錯,而當你要使用這個GameObject的時候,出錯了,而同樣的代碼,我們在PC上可能沒有發現這個問題,當我們打安卓或IOS包時,某個資源加載失敗了。
 
    出現這種神奇的問題,首先是懷疑打包的問題,包太大了?刪掉一些內容,不行!重新打一個?還是不行!然后發現來來回回,都是這一個GameObject報的錯,難道是這個GameObject里面部分資源有問題?對這個GameObject各種分析,把它大卸八塊,處理成一個很簡單的GameObject,還是不行!難道是名字的問題?把這個GameObject的名字改了一下,可以了!
 
    本來事情到這就該結束了,但是,這也太莫名其妙了吧!而且,最重要的是,哥就喜歡原來的名字!!把這個資源改成新的名字,怎么看怎么變扭,怎么看都沒有原來的名字好看,所以繼續折騰了起來~
 
    首先單步跟蹤到這個資源的Load,資源被成功Load出來了,但是Load出來的東西有點怪怪的,明顯不是一個GameObject,而是一個莫名其妙的東西,可能是Unity生成的一個中間對象,也許是一個索引對象,反正不是我要的東西,打包的GameObject怎么會變成這個玩意呢?於是在加載Bundle的地方,把Bundle LoadAll了一下,然后查看這個Bundle里面的內容
 
 
    在這里我們可以看到,有一個叫RoomHallView和RoomMainView的GameObject,並且,LoadAll之后的資源比我打包的資源要多很多,看樣子所有關聯到的資源都被自動打包進去了,數組的427是RoomHallView的GameObject,而431才是RoomMainView的GameObject。可以看到名字叫做RoomMainView和RoomHallView的對象有好幾個,GameObject,Transform,以及一個只有名字的對象,它的類型是一個ReferenceData。
 
    仔細查看可以發現,RoomHallView的GameObject是排在數組中所有名為RoomHallView對象的最前面,而RoomMainView則是ReferenceData排在前面,當我們Load或者LoadAsyn時,是一次數組的遍歷,當遍歷到名字匹配的對象時,則將對象返回,LoadAsyn會對類型進行匹配,但由於我們傳入的是Object,而幾乎所有的對象都是Object,所以返回的結果就是第一個名字匹配的對象
 
     在Load以及LoadAsyn時,除了名字,把要加載對象的類型也傳入,再調試,原來的名字也可以正常被讀取到了,這個細節非常的坑,因為在官網並沒有提醒,而且示例的sample也沒有說應該注意這個地方,並且出現問題的幾率很小。所以一旦出現,就坑死了
 
bundle.Load("MyPrefab", typeof(GameObject))
 
    另外, 不要在IOS模擬器上測試AssetBundle,你會收到bad url的錯誤
【三,依賴】
 
    依賴和打包息息相關,之所以把依賴單獨分開來講,是因為這玩意太坑了.......
 
【1.打包依賴】
 
    在我們打包的時候,將兩個資源打包成單獨的包,那么兩個資源所共用的資源,就會被打包成兩份,這就造成了冗余,所以我們需要將公共資源抽出來,打成一個Bundle,然后后面兩個資源,依賴這個公共包,那么還有另外一種方法,就是把它們三打成一個包,但這不利於后期維護。
我們使用BuildPipeline.PushAssetDependencies()和BuildPipeline.PopAssetDependencies()來開啟Bundle之間的依賴關系,當我們調用PushAssetDependencies之后,會開啟依賴模式,當我們依次打包 A B C時,如果A包含了B的資源,B就不會再包含這個資源,而是直接依賴A的,如果A和B包含了C的資源,那么C的這個資源舊不會被打包進去,而是依賴A和B。這時候只要有同樣的資源,就會向前依賴,當我們希望,B和C依賴A,但B和C之間不互相依賴,就需要嵌套Push Pop了,當我們調用PopAssetDependencies就會結束依賴
 
復制代碼
  string path = Application.streamingAssetsPath;
  BuildPipeline.PushAssetDependencies();
 
  BuildTarget target = BuildTarget.StandaloneWindows;
  
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/UI_tck_icon_houtui.png"), null,
                                 path + "/package1.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, target);
 
 
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/New Material.mat"), null,
                                 path + "/package2.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, target);
 
 
  BuildPipeline.PushAssetDependencies();
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Cube.prefab"), null,
                                 path + "/package3.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, BuildTarget.StandaloneWindows);
  BuildPipeline.PopAssetDependencies();
 
 
 
  BuildPipeline.PushAssetDependencies();
  BuildPipeline.BuildAssetBundle(AssetDatabase.LoadMainAssetAtPath("Assets/Cubes.prefab"), null,
                                 path + "/package4.assetbundle",
                                 BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets
                                 | BuildAssetBundleOptions.DeterministicAssetBundle, target);
  BuildPipeline.PopAssetDependencies();
 
  BuildPipeline.PopAssetDependencies();
復制代碼
 
    上面的代碼演示了如何使用依賴,這個測試使用了一個紋理,一個材質,一個正方體Prefab,還有兩個正方體組成的Prefab,材質使用了紋理,而兩組正方體都使用了這個材質,上面的代碼用Push開啟了依賴,打包紋理,然后打包材質(材質自動依賴了紋理),然后嵌套了一個Push,打包正方體(正方體依賴前面的材質和紋理),然后Pop,接下來再嵌套了一個Push,打包那組正方體(不依賴前面的正方體,依賴材質和紋理)
 
    如果我們只開啟最外面的Push Pop,而不嵌套Push Pop,那么兩個正方體組成的Prefab就會依賴單個正方體的Prefab,依賴是一把雙刃劍,它可以去除冗余,但有時候我們又需要那么一點點冗余
 
【2.依賴丟失】
 
    當我們的Bundle之間有了依賴之后,就不能像前面那樣簡單地直接Load對應的Bundle了,我們需要 把Bundle所依賴的Bundle先加載進來,這個加載只是WWW或者 LoadFromCacheOrDownload,並不需要對這個Bundle進行Load,如果BundleB依賴BundleA,當我們要加載BundleB的資源時,假設BundleA沒有被加載進來,或者已經被Unload了,那么BundleB依賴BundleA的部分就會丟失,例如每個正方體上都掛着一個腳本,當我們不嵌套Push Pop時,單個正方體的Bundle沒有被加載或者已經被卸載,我們加載的那組正方體上的腳本就會丟失, 腳本也是一種資源,當一個腳本已經被打包了,依賴這個包的資源,就不會被再打進去
 
Cubes和Cube都掛載同一個腳本,TestObje,Cubes依賴Cube,將Cube所在的Bundle Unload,再Load Cubes的Bundle,Cubes的腳本丟失,腳本,紋理,材質等一切資源,都是如此
 
【3.更新依賴】
 
    在打包的時候我們需要指定BuildAssetBundleOptions.DeterministicAssetBundle選項,這個選項會為每個資源生成一個唯一的ID,當這個資源被重新打包的時候,確定這個ID不會改變,包的依賴是根據這個ID來的,使用這個選項的好處是,當資源需要更新時,依賴於該資源的其他資源,不需要重新打包
 
    A -> B -> C
 
    當A依賴B依賴C時,B更新,需要重新打包C,B,而A不需要動,打包C的原因是,因為B依賴於C,如果不打包C,直接打包B,那么C的資源就會被重復打包,而且B和C的依賴關系也會斷掉
 
【四,內存】
 
    在使用WWW加載Bundle時,會開辟一塊內存,這塊內存是Bundle文件解壓之后的內存,這意味着這塊內存很大,通過Bundle.Unload可以釋放掉這塊內存,Unload true和Unload false 都會釋放掉這塊內存,而這個Bundle也不能再用,如果要再用,需要重新加載Bundle, 需要注意的是,依賴這個Bundle的其他Bundle,在Load的時候,會報錯
 
    得到Bundle之后,我們用Bundle.Load來加載資源,這些資源會從Bundle的內存被復制出來,作為Asset放到內存中,這意味着,這塊內存,也很大,Asset內存的釋放,與Unity其他資源的釋放機制一樣,可以通過Resources.UnloadUnuseAsset來釋放沒有引用的資源,也可以通過Bundle.Unload(true)來強制釋放Asset,這會導致所有引用到這個資源的對象丟失該資源
 
 
    上面兩段話可以得出一個結論,在new WWW(url)的時候,會開辟一塊內存存儲解壓后的Bundle,而在資源被Load出來之后,又會開辟一塊內存來存儲Asset資源,WWW.LoadFromCacheOrDownload(url)的功能和new WWW(url)一樣,但LoadFromCacheOrDownload是將Bundle解壓到磁盤空間而不是內存中,所以LoadFromCacheOrDownload返回的WWW對象,本身並不會占用過多的內存(只是一些索引信息,每個資源對應的磁盤路徑,在Load時從磁盤取出),針對手機上內存較小的情況, 使用 WWW. LoadFromCacheOrDownload代替new WWW可以有效地節省內存。但LoadFromCacheOrDownload大法也有不靈驗的時候,當它不靈驗時,L oadFromCacheOrDownload 返回的WWW對象將占用和new WWW一樣的內存,所以 不管你的Bundle是如何創建出來的,都需要在不使用的時候,及時地Unload掉
 
    另外使用LoadFromCacheOrDownload需要注意的問題是——第二個參數,版本號,Bundle重新打包之后,版本號沒有更新,取出的會是舊版本的Bundle,並且一個Bundle緩存中可能會存在多個舊版本的Bundle,例如1,2,3 三個版本的Bundle
 
 
    在Bundle Load完之后,不需要再使用該Bundle了,進行Unload,如果有其他Bundle依賴於該Bundle,則應該等依賴於該Bundle的Bundle不需要再Load之后,Unload這個Bundle,一般出現在大場景切換的時候。
    
    我們知道在打包Bundle的時候,有一個參數是mainAsset,如果傳入該參數,那么資源會被視為主資源打包,在得到Bundle之后,可以用AssetBundle.mainAsset直接使用,那么是否在WWW獲取Bundle的時候,就已經將mainAsset預先Load出來了呢?不是! 在我們調用 AssetBundle.mainAsset取出mainAsset時,它的get方法會阻塞地去Load mainAsset,然后返回,AssetBundle.mainAsset等同於Load("mainAssetName")  
 
    PS.重復Load同一個資源並不會開辟新的內存來存儲這個資源
 
【五,其他】
 
    在使用AssetBundle的開發過程中,我們經常會對資源進行調整,調整之后需要對資源進行打包才能生效,對開發效率有很大的影響,所以在開發中我們使用Resource和Bundle兼容的方式
 
    首先將資源管理封裝到一個Manager中,從Bundle中Load資源還是從Resource里面Load資源,都由它決定,這樣可以保證上層邏輯代碼不需要關心當前的資源管理類型
 
    當然,我們所有要打包的對象,都在Resource目錄下,並且使用嚴格的目錄規范,然后使用腳本對象,來記錄每個資源所在的Bundle,以及所對應的Resource目錄,在資源發生變化的時候,更新腳本對象,Manager在運行時使用腳本對象的配置信息,這里的腳本對象我們是使用代碼自動生成的,當然,你也可以用配置表,效果也是一樣的
 
    版本管理也可以交由腳本對象來實現,每次打包的資源,需要將其版本號+1,腳本對象可存儲所有資源的版本號,版本號可以用於LoadFromCacheOrDownload時傳入,也可以手動寫入配置表,在我設計的腳本對象中,每個資源都會有一個所屬Bundle,Resource下相對路徑,版本號等三個屬性
 
    在版本發布的時候,你需要先打包一次Bundle,並且將Resource目錄改成其他的名字,然后再打包,確保Resource目錄下的資源沒有被重復打包,而如果你想打的是Resource版本,則需要將StreamingAssets下的Bundle文件刪除
 
    腳本對象的使用如下:
    1.先設計好存儲結構
    2.寫一個繼承於ScriptObject的類,用可序列化的容器存儲數據結構(List或數組),Dictionary等容器無法序列化,public之后在
    
復制代碼
[Serializable]
public class ResConfigData
{
    public string ResName; //資源名字
    public string BundleName; //包名字
    public string Path; //資源路徑
    public int Vesrion; //版本號
}
 
[System.Serializable]
public class ResConfig : ScriptableObject
{
    public List<ResConfigData> ConfigDatas = new List<ResConfigData>();
}
復制代碼
 
    4.在指定的路徑讀取對象,讀取不到則創建對象
 
復制代碼
ResConfig obj = (ResConfig)AssetDatabase.LoadAssetAtPath(path, typeof(ResConfig));
if (obj == null)
{
   obj = ScriptableObject.CreateInstance<ResConfig>();
   AssetDatabase.CreateAsset(obj, path);
}
復制代碼
 
    3.寫入數據,直接修改obj的數組,並保存(不保存下次啟動Unity數據會丟失)
 
EditorUtility.SetDirty(obj);
 
    由於數組操作不方便,所以我們可以將數據轉化為方便各種增刪操作的Dictionary容器存儲,在保持時將其寫入到持久化的容器中

五、未完待續


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM