【轉載】AssetBundle資源打包加載管理

前言

本篇文章是爲了記錄學習了Unity資源加載(Resource & AssetBundle)相關知識後,對於AssetBundle打包加載框架實戰的學習記錄。因爲前一篇學習Unity資源相關知識的文章太長,所以AssetBundle實戰這一塊單獨提出來寫一篇。

基礎知識學習回顧,參考:
Unity Resource Manager

鳴謝

這裏AssetBundle加載管理的框架思路借鑑了Git上的開源庫:
tangzx/ABSystem

同時也學習了KEngine相關部分的代碼:
mr-kelly/KEngine

AssetBundle打包這一套主要是自己基於對新版(5.X以上)的AssetBundle打包機制自行編寫的一套,這一套相對來說很不完善有很多缺點,個人建議讀者參考Git上的其他一些開源庫或者直接使用Unity官方推出的高度可視化和高度自由度打包方案:
AssetBundles-Browser

本文重點分享,參考ABSystem編寫的一套AB加載管理方案實現:
基於AB引用計數的AB加載管理

AssetBundle打包

Note:
這裏的AssetBundle打包主要是針對Unity 5.X以及以後的版本來實現學習的。

相關工具

  1. Unity Profiler(Unity自帶的新能分析工具,這裏主要用於查看內存Asset加載情況)
  2. Unity Studio(Unity AB以及Asset等資源解析查看工具)
  3. DisUnity(解析AB包的工具)

AB打包

AB打包是把資源打包成assetbundle格式的資源。
AB打包需要注意的問題:

  1. 資源冗餘
  2. 打包策略
  3. AB壓縮格式

資源冗餘

這裏說的資源冗餘是指同一份資源被打包到多個AB裏,這樣就造成了存在多份同樣資源。

資源冗餘造成的問題:

  1. 餘造成內存中加載多份同樣的資源佔用內存。
  2. 同一份資源通過多次IO加載,性能消耗。
  3. 導致包體過大。

解決方案:
依賴打包

依賴打包

依賴打包是指指定資源之間的依賴關係,打包時不將依賴的資源重複打包到依賴那些資源的AB裏(避免資源冗餘)。

在老版(Unity 5之前),官方提供的API接口是通過BuildPipeline.PushAssetDependencies和BuildPipeline.PopAssetDependencies來指定資源依賴來解決資源冗餘打包的問題。

在新版(Unity 5以後),官方提供了針對每個Asset在面板上設置AssetBundle Name的形式指定每個Asset需要打包到的最終AB。然後通過 API接口BuildPipeline.BuildAssetBundles()觸發AB打包。Unity自己會根據設置AB的名字以及Asset之間的使用依賴決定是否將依賴的資源打包到最終AB裏。

這裏值得一提的是Unity 5以後提供的增量打包功能。

增量打包(Unity 5以後)

增量打包是指Unity自己維護了一個叫manifest的文件(前面提到過的記錄AB包含的Asset以及依賴的AB關係的文件),每次觸發AB打包,Unity只會修改有變化的部分,並將最新的依賴關係寫入manifest文件。

*.manifest記錄所有AB打包依賴信息的文件,內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ManifestFileVersion: 0
CRC: 961902239
AssetBundleManifest:
  AssetBundleInfos:
    Info_0:
      Name: nonuiprefabs/nui_capsulesingletexture
      Dependencies:
        Dependency_0: materials/mt_singletexturematerial
    Info_1:
      Name: shaders/sd_shaderlist
      Dependencies: {}
    Info_2:
      Name: materials/mt_singletexturematerial
      Dependencies:
        Dependency_0: shaders/sd_shaderlist
        Dependency_1: textures/tx_brick_diffuse
    Info_3:
      Name: textures/tx_brick_diffuse
      Dependencies: {}
    Info_4:
      Name: nonuiprefabs/nui_capsulenormalmappingtexture
      Dependencies: {}
    Info_5:
      Name: textures/tx_brick_normal
      Dependencies: {}
    Info_6:
      Name: materials/mt_normalmappingmaterial
      Dependencies:
        Dependency_0: shaders/sd_shaderlist
        Dependency_1: textures/tx_brick_diffuse
        Dependency_2: textures/tx_brick_normal

 

問題:
雖然Unity5提供了增量打包並記錄了依賴關係,但從上面的*.manifest可以看出,依賴關係只記錄了依賴的AB的名字沒有具體到特定的Asset。

最好的證明就是上面我把用到的Shader都打包到sd_shaderlist裏。在我打包的資源裏,有兩個shader被用到了(SingleTextShader和NormalMappingShader),這一點可以通過UnityStudio解壓查看sd_shaderlist看到:
sd_shaderlistsd_shaderlist

從上面可以看出Unity的增量打包只是解決了打包時更新哪些AB的判定,而打包出來的*.manifest文件並不能讓我們得知用到了具體哪一個Asset而是AssetBundle。

解決用到哪些Asset這一環節依然是需要我們自己解決的問題,只有存儲了用到哪些Asset的信息,我們才能在加載AB的時候對特定Asset做操作(緩存,釋放等)。

Note:
每一個AB下面都對應一個.manifest文件,這個文件記錄了該AB的asset包含情況以及依賴的AB情況,但這些manifest文件最終不會不打包到遊戲裏的,只有最外層生成的*.manifest文件(記錄了所有AB的打包信息)纔會被打包到遊戲裏,所以纔有像前面提到的通過讀取.manifest文件獲取對應AB所依賴的所有AB信息進行加載依賴並最終加載出所需Asset的例子

依賴Asset信息打包

存儲依賴的Asset信息可以有多種方式:

  1. 通過加載依賴AB,依賴Unity自動還原的機制實現依賴Asset加載還原(這正是本博客實現AssetBundle打包以及加載管理的方案)
  2. 存儲掛載相關信息到Prefab上
    在設置好AB名字,打包AB之前,將打包對象上用到的信息通過編寫[System.Serializable]可序列化標籤抽象數據掛載到該Asset對象上,然後打包AB,打包完AB後在運行時利用打包的依賴信息進行依賴Asset加載還原,從而做到對Asset的生命週期掌控。
    DPInfoMonoDPInfoMono
  3. 創建打包Asset到同名AB裏,加載的時候讀取
    通過AssetDatabase.CreateAsset()和AssetImporter.GetAtPath()將依賴的信息寫入新的Asset並打包到相同AB裏,加載時加載出來使用。
    MaterialAssetInfoMaterialAssetInfo

打包策略

除了資源冗餘,打包策略也很重要。打包策略是指決定各個Asset如何分配打包到指定AB裏的策略。打包策略會決定AB的數量,資源冗餘等問題。AB數量過多會增加IO負擔。資源冗餘會導致包體過大,內存中存在多份同樣的Asset,熱更新資源大小等。

打包策略:

  1. Logical entities(按邏輯(功能)分類 — 比如按UI,按模型使用,按場景Share等功能分類)
    優點:可以動態只更新特定Entity
  2. Object Types(類型分類 — 主要用於同類型文件需要同時更新的Asset)
    優點:只適用於少部分需要經常變化更新的小文件
  3. Concurrent content(加載時機分類 — 比如按Level分類,主要用於遊戲裏類容固定(Level Based)不會動態變化加載的遊戲類型)
    優點:適合Level based這種一層內容一層不變的遊戲類型
    缺點:不適合用於動態創建對象的遊戲類型

打包策略遵循幾個比較基本的準則:

  1. Split frequently-updated Objects into different AssetBundles than Objects that usually remain unchanged(頻繁更新的Asset不要放到不怎麼會修改的Asset裏而應分別放到不同的AB裏)
  2. Group together Objects that are likely to be loaded simultaneously(把需要同時加載的Asset儘量打包到同一個AB裏)

從上面可以看出,不同的打包策略適用於不同的遊戲類型,根據遊戲類型選擇最優的策略是關鍵點。

AB壓縮格式

AB壓縮不壓縮問題,主要考慮的點如下:

  1. 加載時間
  2. 加載速度
  3. 資源包體大小
  4. 打包時間
  5. 下載AB時間
    這裏就不針對壓縮問題做進一步介紹了,主要根據遊戲對於各個問題的需求看重點來決定選擇(內存與加載性能的抉擇)

AB打包相關API

  1. Selection(獲取Unity Editor當前勾選對象相關信息的接口)

    1
    
    Object[] assetsselections = Selection.GetFiltered(Type, SelectionMode);
    
  2. AssetDatabase(操作訪問Unity Asset的接口)

    1
    2
    3
    4
    
    // 獲取選中Asset的路徑
    assetpath = AssetDatabase.GetAssetPath(assetsselections[i]);
    // 獲取選中Asset的GUID
    assetguid = AssetDatabase.AssetPathToGUID(assetpath);
    
  3. AssetImporter(獲取設置Asset的AB名字的接口)

    1
    2
    3
    4
    5
    
    // 獲取指定Asset的Asset設置接口
    AssetImporter assetimporter = AssetImporter.GetAtPath(assetpath);
    // 設置Asset的AB信息
    assetimporter.assetBundleVariant = ABVariantName;
    assetimporter.assetBundleName = ABName;
    
  4. BuildPipeline(AB打包接口)

    1
    2
    3
    
    BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions, BuildTarget); 
    
    BuildPipeline.BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform);
    

可以看到AB打包Unity 5.X提供了兩個主要的接口:

  1. 前者是依賴於設置每個Asset的AB名字,然後一個接口完成增量打包的方案。
    優點:
    自帶增量打包
    缺點:
    需要有一套資源AB命名規則。
    開發者可控度低。
  2. 後者是提供給開發者自定義哪些Asset打包到指定AB裏的一個接口。
    優點:
    可自行實現指定需求的打包規則。
    開發者可控度高。
    缺點:
    不自帶增量打包需要自己實現。

Note:
AssetBundle-Browser也是基於前者的一套打包方案,只不過AssetBundle-Browser實現了高度的Asset資源打包可視化操作與智能分析。

AB打包框架實戰

這裏本人實現的打包方案是使用的後者。(這一套並不完善(未寫完,也有不少問題,可以簡單看哈借鑑哈思路。)

主要實現了以下功能:

  1. 支持設置四種Asset打包規則(通過設置AB名字)
    • sharerule(作爲共享資源,單獨打成AB)
    • entirerule(作爲一個整體,所有Asset用到的資源打包成一個AB)
    • mutilplerule(作爲一個集合,同目錄下同類型Asset打包成一個AB)
    • normalrule(作爲一個普通資源,誰依賴使用它,他就打包到使用它的Asset所屬打包規則的AB裏)
  2. 資源劃分類型,針對不同資源類型,可以自定義限制打包規則設置以及Unity格式設定檢查等。
  3. 可以對內置資源打包做單獨處理(比如內置shader,內置default sprite等)
  4. 每次打包生成的依賴文件信息提取出來後最後合併得出最終的依賴信息。
  5. Shader打包資源時自動記錄,最後打一次Shader AB即可。

缺點與問題:

  1. 不支持增量打包,每一次打包都是單獨分析的。(增量打包這一點很重要)
  2. 依賴分析是根據AssetDatabase.GetDependencies()來做的分析,看網上有提到說
    EditorUtility.CollectDependencies()獲取的數據纔是更正確的,依賴分析可能有問題。(這個比較好改,直接換成後者即可)
  3. normalrule設計的時候沒有考慮到單個normalrule資源被多個同類型資源引用的情況(單個資源單次打包不能指定到多個AB裏),這裏分析出來的數據會有誤(導致normalrule的資源只會被打包到某一個引用他的資源對象AB裏)。(比較嚴重的問題,前期依賴分析設計考慮欠佳導致的)
  4. 因爲沒有增量打包,每一次都是單獨打包,所以只支持同類型或者單個資源分析打包。
  5. 修改某個資源的打包規則會導致需要把所有用到該資源的資源全部重新打包。(很嚴重的問題)

基於上面的很多缺點,導致本人最終也放棄了繼續寫該方案的打包。但從這一次打包代碼編寫中加深了對AssetBundle打包的理解。後面說完AB加載管理會一起附上最後的源代碼Git鏈接。

Note:
個人建議讀者參考其他開源庫或者使用Assetbundle Browser。

AssetBundle加載管理

實戰學習AB加載之前讓我們通過一張圖先了解下AB與Asset與GameObject之間的關係:
AssetBundleFrameworkAssetBundleFramework

依賴加載還原

還記得前面說到的依賴的Assest信息打包嗎?
這裏就需要加載出來並使用進行還原了。
這裏接不細說加載還原了,主要就是通過存儲的依賴信息把依賴的Asset加載進來並設置回去的過程(可以是手動設置回去也可以是Unity自動還原的方式)。

這裏主要要注意的是前面那張大圖上給出的各種資源類型在Asset加載還原時採用的方式。
資源加載還原的方式主要有兩種:

  1. 複製+引用
    UI — 複製(GameObject) + 引用(Components,Tranform等)
    Material — 複製(材質自身) + 引用(Texture和Shader)

  2. 引用
    Sprite — 引用
    Audio — 引用
    Texture — 引用
    Shader — 引用
    Material — 引用

AB加載相關API

  1. AssetBundle(AB接口)
    1
    2
    3
    4
    5
    6
    
    // 加載本地壓縮過的AB
    AssetBundle.LoadFromFile(abfullpath)
    // 加載AB裏的指定Asset
    AssetBundle.LoadAsset(assetname);
    // 加載AB裏的所有Asset
    AssetBundle.LoadAllAssets();
    

AB回收相關API

  1. AssetBundle(AB接口)

    1
    2
    3
    4
    
    // 回收AssetBundle並連帶加載實例化出來的Asset以及GameObject一起回收
    AssetBundle.Unload(true);
    // 只回收AssetBundle
    AssetBundle.Unload(false);
    
  2. Resource(資源接口)

    1
    2
    3
    4
    
    // 回收指定Asset(這裏的Asset不能爲GameObject)
    Resource.UnloadAsset(asset);
    // 回收內存以所有不再有任何引用的Asset
    Resources.UnloadUnusedAssets();
    
  3. GC(內存回收)

    1
    2
    
    // 內存回收
    GC.Collect();
    

AB回收的方式有兩種:

  1. AssetBundle.Unload(false)(基於Asset層面的重用,通過遍歷判定的方式去判定Asset是否回收)
  2. AssetBundle.Unload(true)(採取索引技術,基於AB層面的管理,只有AB的引用計數爲0時我們直接通過AssetBundle.Unload(true)來卸載AB和Asset資源)

接下來結合這兩種方式,實戰演練加深理解。

基於Asset重用的AB加載

核心思想:

  1. 打包時存儲了依賴的Asset信息,加載時利用存儲的依賴信息並還原
  2. 緩存加載進來的Asset的控制權進行重用,卸載AB(AssetBundle.Unload(false))
  3. 加載時通過Clone(複製類型)或者返回緩存Asset(引用類型)進行Asset重用

一下以之前打包的CapsuleNormalMappingTexture.prefab進行詳細說明:
CapsuleNormalMappingPrefab.PNGCapsuleNormalMappingPrefab.PNG
可以看出CapsuleNormalMappingTexture.prefab用到了如下Asset:

  1. NormalMappingMaterial(材質)
  2. Custom/Texture/NormalMappingShader(Shader)
  3. Brick_Diffuse和Brick_Normal(紋理)

開始加載CapsuleNormalMappingTexture.prefab:
首先讓我們看看加載了CapsuleNormalMappingTexture.prefab前的Asset加載情況:
NonUIPrefabLoadedBeforeProfilerNonUIPrefabLoadedBeforeProfiler
可以看出只有Shader Asset被預先加載進來了(因爲我預先把所有Shader都加載進來了)
第一步:
加載CapsuleNormalMappingTexture.prefab對應的AB,因爲Prefab是採用複製加引用所以這裏需要返回一個通過加載Asset後Clone的一份對象。

1
2
3
4
5
6
nonuiprefabab = loadAssetBundle(abfullname);
var nonuiprefabasset = loadMainAsset(nonuiprefabab);
nonuigo = GameObject.Instantiate(nonuiprefabasset) as GameObject;
// Asest一旦加載進來,我們就可以進行緩存,相應的AB就可以釋放掉了
// 後續會講到相關AB和Asset釋放API
nonuiprefabab.Unload(false);

 

第二步:
還原依賴材質
DPInfoMonoDPInfoMono

1
2
3
4
5
6
7
8
9
NonUIPrefabDepInfo nonuidpinfo = nonuigo.GetComponent<NonUIPrefabDepInfo>();
var dpmaterials = nonuidpinfo.mDPMaterialInfoList;
for (int i = 0; i < dpmaterials.Count; i++)
{
    for(int j = 0; j < dpmaterials[i].mMaterialNameList.Count; j++)
    {
        MaterialResourceLoader.getInstance().addMaterial(dpmaterials[i].mRenderer, dpmaterials[i].mMaterialNameList[j]);
    }
}

 

第三步:
還原依賴材質的Shader和Texture依賴引用
MaterialAssetInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 根據材質_InfoAsset.asset進行還原材質原始Shader以及Texture信息
var materialinfoasseetname = string.Format("{0}_{1}InfoAsset.asset", materialname, ResourceHelper.CurrentPlatformPostfix);
var materialassetinfo = loadSpecificAsset(materialab, materialinfoasseetname.ToLower()) as MaterialAssetInfo;
// 加載材質依賴的Shader
var materialshader = materialassetinfo.mShaderName;
var shader = ShaderResourceLoader.getInstance().getSpecificShader(materialshader);
material.shader = shader;

// 獲取Shader使用的Texture信息進行還原
var materialdptextureinfo = materialassetinfo.mTextureInfoList;
for (int i = 0; i < materialdptextureinfo.Count; i++)
{
    // 加載指定依賴紋理
    Texture shadertexture = TextureResourceLoader.getInstance().loadTexture(materialdptextureinfo[i].Value);
    //設置材質的對應Texture
    material.SetTexture(materialdptextureinfo[i].Key, shadertexture);
}

 

接下讓我們看看加載了CapsuleNormalMappingTexture.prefab後的Asset加載情況:
NonUIPrefabLoadedAfterProfilerNonUIPrefabLoadedAfterProfiler
可以看出引用的材質和紋理Asest都被加載到內存裏了(Shader因爲我預先把所有Shader都加載進來了所以就直接重用了沒有被重複加載)
第四步:
對緩存的Asset進行判定是否回收(這裏以材質爲例,判定方式可能多種多樣,我這裏是通過判定是否有有效引用)
啓動一個攜程判定特定Material是否不再有有效組件(所有引用組件爲空或者都不再使用任何材質)時回收Material Asset

1
Resources.UnloadAsset(materialasset);

 

接下來讓我們看看卸載實例對象後,材質被回收的情況:
MaterialRecycleAssetNumerMaterialRecycleAssetNumer
MaterialRecycleMaterialRecycle
可以看到沒有被引用的材質Asset被回收了,但內存裏的Asset數量卻明顯增加了。
這裏多出來的是我們還沒有回收的Texture以及Prefab的GameObject以及Components Asset依然還在內存裏。
AfterMaterialRecyleTextureStatusAfterMaterialRecyleTextureStatus
AfterMaterialRecyleGameObjectStatusAfterMaterialRecyleGameObjectStatus
AfterMaterialRecyleTrasformStatusAfterMaterialRecyleTrasformStatus
第五步:
通過切換場景觸發置空所有引用將還未回收的Asset變成UnsedAsset或者直接觸發Texture Asset回收,然後通過Resources.UnloadUnusedAssets()回收所有未使用的Asset

1
2
3
4
5
6
7
8
9
mNonUIPrefabAssetMap = null;

foreach(var texture in mTexturesUsingMap)
{
    unloadAsset(texture.Value);
}
mTexturesUsingMap = null;

Resources.UnloadUnusedAssets();

 

AfterAssetsRecyleAssetNumberAfterAssetsRecyleAssetNumber
AfterAssetsRecyleTextureStatusAfterAssetsRecyleTextureStatus
AfterAssetsRecyleGameObjectStatusAfterAssetsRecyleGameObjectStatus
AfterAssetsRecyleTransformStatusAfterAssetsRecyleTransformStatus
可以看到所有的Texture, GameObject, Transform都被回收了,並且Asset的數量回到了最初的數值。

基於AB引用計數的AB加載管理

這是本文重點分享的部分

方案1:
核心思想:

  1. 基於AB的引用計數 + AssetBundle.Unload(true)
  2. 給每一種資源(e.g. 特效,模型,圖片,窗口等)加載都編寫統一的資源加載接口(父類抽象)進行自身加載使用的AB引用計數,每個資源負責自身的資源加載管理和返還(主動調用)
  3. 由一個單例管理者統一管理所有加載AB的引用計數信息,負責判定是否可回收

優點:

  1. 嚴格的AB引用計數加載管理和釋放
  2. 可以做到對資源對象的重用減少GC
  3. 對資源對象的重用可以減少AB的重複加載

缺點:

  1. 底層管理的內容比較多(比如基於資源對象的重用),上層靈活度欠缺(相當於對象池已經寫在了最底層)
  2. 需要主動去調用返還接口(針對不同資源加載釋放時機都需要編寫一套對應的代碼)

方案2:
核心思想:

  1. 基於AB的引用計數 + AssetBundle.Unload(true)
  2. 綁定加載AB的生命週期判定到Object上(e.g. GameObject,Image,Material等),上層無需關心AB的引用計數,只需綁定AB到對應的對象上即可
  3. 通過單例管理者統一管理判定依賴使用AB的Object列表是否都爲空來判定是否可以回收,無需上層管理AB引用計數返還

優點:

  1. 上層只需關心AB加載綁定,無需關心AB引用計數返還問題,上層使用靈活度高

缺點:

  1. AB的返還判定跟綁定的Object有關,Object被回收後,AB容易出現重複加載(可以在上層寫部分對象池來減少AB的重複加載)

考慮到希望上層靈活度高一些,個人現在傾向於第二種方案。

接下來基於第二種方案來實戰編寫資源AB加載的框架。
AB打包這一塊打算先使用之前自己寫的一套沒有增量更新但支持一定打包規則指定的打包方案。

AB加載管理框架

以下實現主要參考了下面兩個開源項目:
Unity3D AssetBundle 打包與管理系統
KEngine

框架設計

支持功能:

  1. 支持AB和AssetDatabase(限編輯器)資源加載(方便快速迭代開發,開發時用AssetDatabase(也支持快速切換AB模式))
  2. 支持AB同步和異步加載(統一採用callback風格)
  3. 支持三種基本的資源加載類型(NormalLoad — 正常加載(可通過Tick檢測判定正常卸載) Preload — 預加載(切場景纔會卸載) PermanentLoad — 永久加載(常駐內存永不卸載))
  4. 已加載資源允許變更資源加載類型,但只允許從低往高變(NormalLoad -> Preload -> PermanentLoad),不允許從高往低(PermanentLoad -> Preload -> NormalLoad)
  5. 基於UnityEngine.Object的AB索引生命週期綁定
  6. 底層統一管理AB索引計數,管理資源加載釋放
  7. 支持卸載頻率,卸載幀率門檻,單次卸載數量等設置。採用Long Time Unused First Unload(越久沒用越先卸載)原則卸載。
  8. 上層編寫對應資源類型Manager資源加載接口(負責提供各資源類型的加載)
  9. Manager of Manager架構設計,面向接口設計Manager,通過中介者統一註冊獲取(ModuleManager.GetManager的形式)
  10. 底層模塊之間的解耦暫時還沒想好如何設計(TODO)

簡單繪製一下類圖:
ModuleDesignModuleDesign

更多學習參考:
淺談 Unity 開發中的分層設計

類設計:
中介者解耦Manager的類:
ModuleManager(單例類)
ModuleInterface(模塊接口類)
ModuleType(模塊枚舉類型)

資源加載類:
ABLoadMethod(資源加載方式枚舉類型 — 同步 or 異步)
ABLoadState(資源加載狀態 — 錯誤,加載中,完成之類的)
ABLoadType(資源加載類型 — 正常加載,預加載,永久加載)
ResourceModuleManager(資源加載模塊統一管理類)
AssetBundleLoader(AB資源加載任務類)
AssetBundleInfo(AB信息以及加載狀態類,AB訪問,索引計數以及AB依賴關係抽象都在這一層)
AssetBundlePath(AB資源路徑相關 — 處理多平臺路徑問題)
ABDebugWindow.cs(Editor運行模式下可視化查看AB加載詳細信息的輔助工具窗口)

AssetBundle管理方式是索引計數+檢查有效使用對象,然後AssetBundle.Unload(true)的方式。
這裏的引用計數+檢查有效使用對象是指:

  1. AB被依賴加載的引用計數
  2. AB被加載後綁定到特定Object上的owner對象有效性檢查

包內AB加載方式:
AssetBundle.LoadFromFile()
AssetBundle.LoadFromFileAsync()

加載管理方案:

  1. 加載指定資源
  2. 加載自身AB(自身AB加載完通知資源加載層移除該AB加載任務避免重複的加載任務被創建),自身AB加載完判定是否有依賴AB
  3. 有則加載依賴AB(增加依賴AB的引用計數)(依賴AB採用和自身AB相同的加載方式(ABLoadMethod),但依賴AB統一採用ABLoadType.NormalLoad加載類型)
  4. 自身AB和所有依賴AB加載完回調通知邏輯層可以開始加載Asset資源(AB綁定對象在這一步)
  5. 判定AB是否滿足引用計數爲0,綁定對象爲空,且爲NormalLoad加載方式則卸載該AB(並釋放依賴AB的計數減一)(通知資源管理層AB卸載,重用AssetBundleInfo對象)
  6. 切場景,遞歸判定卸載PreloadLoad加載類型AB資源

AB加載管理相關概念:

  1. 依賴AB與被依賴者採用同樣的加載方式(ABLoadMethod),但加載方式依賴AB統一採用ABLoadType.NormalLoad
  2. 依賴AB通過索引計數管理,只要原始AB不被卸載,依賴AB就不會被卸載
  3. 已加載的AB資源加載類型只允許從低往高變(NormalLoad -> Preload -> PermanentLoad),不允許從高往低(PermanentLoad -> Preload -> NormalLoad)

Note:

  1. 一個AB加載任務對應一個AssetBundleLoader
  2. 一個已加載AB信息對應一個AssetBundleInfo

Demo

  1. AB依賴信息查看界面:
    AssetBundleDepInfoUIAssetBundleDepInfoUI

  2. AB運行時加載管理詳細信息界面:
    AssetBundleLoadManagerUIAssetBundleLoadManagerUI

  3. 測試界面:
    AssetBundleTestUIAssetBundleTestUI

  4. 點擊加載窗口預製件按鈕後:

    1
    2
    3
    4
    5
    6
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("MainWindow",
    (abi) =>
    {
        mMainWindow = abi.instantiateAsset("MainWindow");
        mMainWindow.transform.SetParent(UIRootCanvas.transform, false);
    });
    

    AssetBundleLoadManagerUIAfterLoadWindowAssetBundleLoadManagerUIAfterLoadWindow
    可以看到窗口mainwindow依賴於loadingscreen,導致我們加載窗口資源時,loadingscreen作爲依賴AB被加載進來了(引用計數爲1),窗口資源被綁定到實例出來的窗口對象上(綁定對象MainWindow)

  5. 點擊測試異步和同步加載按鈕後:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    if(mMainWindow == null)
    {
        onLoadWindowPrefab();
    }
    
    // 測試大批量異步加載資源後立刻同步加載其中一個該源
    var image = mMainWindow.transform.Find("imgBG").GetComponent<Image>();
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("tutorialcellspritesheet",
    (abi) =>
    {
        var sp = abi.getAsset<Sprite>(image, "TextureShader");
        image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("Ambient",
    (abi) =>
    {
        var sp = abi.getAsset<Sprite>(image, "Ambient");
        image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("BasicTexture",
    (abi) =>
    {
        var sp = abi.getAsset<Sprite>(image, "BasicTexture");
        image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("Diffuse",
    (abi) =>
    {
        var sp = abi.getAsset<Sprite>(image, "Diffuse");
        image.sprite = sp;
    },
    ABLoadType.NormalLoad,
    ABLoadMethod.Async);
    

    AssetBundleLoadManagerUIAfterLoadSpritesAssetBundleLoadManagerUIAfterLoadSprites
    可以看到我們切換的所有Sprite資源都被綁定到了imgBG對象上,因爲不是作爲依賴AB加載進來的所以每一個sprite所在的AB引用計數依然爲0.

  6. 點擊銷燬窗口實例對象後:

    1
    
    GameObject.Destroy(mMainWindow);
    

    AssetBundleLoadManagerUIAfterDestroyWindowAssetBundleLoadManagerUIAfterDestroyWindow
    窗口銷燬後可以看到之前加載的資源所有綁定對象都爲空了,因爲被銷燬了(MainWindow和imgBG都被銷燬了)
    o

  7. 等待回收檢測回收後:
    AssetBundleLoadManagerUIAfterUnloadABAssetBundleLoadManagerUIAfterUnloadAB
    上述資源在窗口銷燬後,滿足了可回收的三大條件(1. 索引計數爲0 2. 綁定對象爲空 3. NormalLoad加載方式),最終被成功回收。

    Note:

    讀者可能注意到shaderlist索引計數爲0,也沒綁定對象,但沒有被卸載,這是因爲shaderlist是被我預加載以常駐資源的形式加載進來的(PermanentLoad),所以永遠不會被卸載。

    1
    2
    3
    4
    5
    6
    
    ModuleManager.Singleton.getModule<ResourceModuleManager>().requstResource("shaderlist",
    (abi) =>
    {
        abi.loadAllAsset<UnityEngine.Object>();
    },
    ABLoadType.PermanentLoad);          // Shader常駐
    

    可以看到上面我們正確的實現了資源加載的管理。詳細的內容這裏就不再多說,感興趣的直接去下載源碼吧,這裏給出Git鏈接:
    TonyTang1990/AssetBundleLoadManager

    Note:

    1. Git上後續實現和更新了更多的內容,這裏就不一一介紹了,詳情參見上面的Git鏈接

資源輔助工具

資源輔助工具五件套:

  • AB刪除判定工具

    ​ DeleteRemovedAssetBundleDeleteRemovedAssetBundle

  • 資源依賴查看工具

    AssetDependenciesBrowser

  • ​ AssetDependenciesBrowser

  • 內置資源依賴統計工具(只統計了.mat和.prefab,場景建議做成Prefab來統計)

    ​ BuildInResourceReferenceAnalyzeBuildInResourceReferenceAnalyze

  • 內置資源提取工具

    ​ BuildInResourceExtraction

  • BuildInResourceExtraction

  • Shader變體蒐集工具

    ​ ShaderVariantsCollectionShaderVariantsCollection

AB實戰總結

  1. AB打包和加載是一個相互相存的過程。
  2. Unity 5提供的增量打包只是提供了更新部分AB的打包機制,*.manifest文件裏也只提供依賴的AB信息並非Asset,所以想基於Asset的重用還需要我們自己處理。
  3. Unity並非所有的Asset都是採用複製而是部分採用複製,部分採用引用的形式,只有正確掌握了這一點,我們才能確保Asset真確的重用和回收。
  4. 打包策略是根據遊戲類型,根據實際情況而定。
  5. AB壓縮格式選擇取決於內存和加載性能以及包體大小等方面的抉擇。
  6. 資源管理策略而言,主要分爲基於Asset管理(AssetBundle.Unload(false))還是基於AB管理(AssetBundle.Unload(true)),前者容易出現內存中多份重複資源,後者需要確保嚴格的管理機制(比如索引計數))

Reference

tangzx/ABSystem
mr-kelly/KEngine
AssetBundles-Browser

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