Unity AssetBundle 說明 - AB包介紹
注:以下信息來自官方文檔,僅做轉(zhuǎn)載和記錄
1. AssetBundle 是什么
AssetBundle 是一個存檔文件,包含可在運行時加載的特定于平臺的資源(模型、紋理、預(yù)制件、音頻剪輯甚至整個場景)。
AssetBundle 可以表達(dá)彼此之間的依賴關(guān)系;例如 AssetBundle A 中的材質(zhì)可以引用 AssetBundle B 中的紋理。
為了通過網(wǎng)絡(luò)進(jìn)行有效傳遞,可以根據(jù)用例要求選用內(nèi)置算法來壓縮 AssetBundle(LZMA 和 LZ4)。
AssetBundle 可用于可下載內(nèi)容(DLC),減小初始安裝大小,加載針對最終用戶平臺優(yōu)化的資源,以及減輕運行時內(nèi)存壓力。
1.1 AssetBundle 中有什么?
首先是磁盤上的實際文件。對于這種情況,我們稱之為 AssetBundle 存檔,在本文檔中簡稱“存檔”。存檔可以被視為一個容器,就像文件夾一樣,可以在其中包含其他文件。這些附加文件包含兩種類型:序列化文件和資源文件。序列化文件包含分解為各個對象并寫入此單個文件的資源。資源文件只是為某些資源(紋理和音頻)單獨存儲的二進(jìn)制數(shù)據(jù)塊,允許我們有效地在另一個線程上從磁盤加載它們。
其次是通過代碼進(jìn)行交互以便從特定存檔加載資源的實際 AssetBundle 對象。此對象包含一個映射,即從已添加到此存檔的資源的所有文件路徑到按需加載的資源所包含的對象之間的映射。
2. AssetBundle 工作流
2.1 為 AssetBundle 分配資源
要為 AssetBundle 分配指定資源,請按照下列步驟操作:
1.從 Project 視圖中選擇要為捆綁包分配的資源
2.在 Inspector 中檢查對象
3.在 Inspector 底部,應(yīng)該會看到一個用于分配 AssetBundle 和變體的部分:
4.左側(cè)下拉選單分配 AssetBundle,而右側(cè)下拉選單分配變量
5.單擊左側(cè)下拉選單,其中顯示“None”,表示當(dāng)前注冊的 AssetBundle 名稱
6.單擊“New…”以創(chuàng)建新的 AssetBundle
7.輸入所需的 AssetBundle 名稱。請注意,AssetBundle 名稱支持某種類型的文件夾結(jié)構(gòu),具體取決于您輸入的內(nèi)容。要添加子文件夾,請用“/”分隔文件夾名稱。例如:AssetBundle 名稱“environment/forest”將在 environment 子文件夾下創(chuàng)建名為 forest 的捆綁包

2.2 構(gòu)建 AssetBundle
在 Assets 文件夾中創(chuàng)建一個名為 Editor 的文件夾,并將包含以下內(nèi)容的腳本放在該文件夾中:
using UnityEditor;
using System.IO;
public class CreateAssetBundles
{
[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
string assetBundleDirectory = "Assets/AssetBundles";
if(!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}
BuildPipeline.BuildAssetBundles(assetBundleDirectory, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
}
}
此腳本將在 Assets 菜單底部創(chuàng)建一個名為“Build AssetBundles”的菜單項,該菜單項將執(zhí)行與該標(biāo)簽關(guān)聯(lián)的函數(shù)中的代碼。單擊 Build AssetBundles 時,將隨構(gòu)建對話框一起顯示一個進(jìn)度條。此過程將會獲取帶有 AssetBundle 名稱標(biāo)簽的所有資源,并將它們放在 assetBundleDirectory 定義的路徑中的文件夾中。
2.3 加載 AssetBundle 和資源
打算從本地存儲加載的用戶可以使用 AssetBundles.LoadFromFile API 。該 API 使用如下所示:
public class LoadFromFileExample extends MonoBehaviour
{
function Start()
{
var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject");
Instantiate(prefab);
}
}
LoadFromFile 獲取捆綁包文件的路徑。
如果是自己托管 AssetBundle 并需要將它們下載到游戲中,應(yīng)使用 UnityWebRequest API。下面是一個示例:
IEnumerator InstantiateObject()
{
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject cube = bundle.LoadAsset<GameObject>("Cube");
GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
Instantiate(cube);
Instantiate(sprite);
}
GetAssetBundle(string, int) 獲取 AssetBundle 的位置以及要下載的捆綁包的版本。在這個例子中,我們?nèi)匀恢赶蛞粋€本地文件,但字符串 uri 可以指向托管 AssetBundle 的任何 url。
UnityWebRequest 有一個特定的句柄來處理 AssetBundle:DownloadHandlerAssetBundle,可根據(jù)請求獲取 AssetBundle。
無論使用哪種方法,現(xiàn)在都可以訪問 AssetBundle 對象了。對該對象需要使用 LoadAsset<T>(string),此函數(shù)將獲取嘗試加載的資源的類型 T 以及對象的名稱(作為捆綁包內(nèi)部的字符串)。這將返回從 AssetBundle 加載的任何對象??梢韵袷褂?Unity 中的任何對象一樣使用這些返回的對象。例如,如果要在場景中創(chuàng)建游戲?qū)ο?,只需調(diào)用 Instantiate(gameObjectFromAssetBundle)。
3. 為 AssetBundle 準(zhǔn)備資源
使用 AssetBundle 時,可以任意將任何資源分配給所需的任何捆綁包。但是,在設(shè)置捆綁包時需要考慮某些策略。以下分組策略旨在用于您認(rèn)為適合的具體項目??梢愿鶕?jù)需要隨意混合和搭配這些策略。
3.1 邏輯實體分組
邏輯實體分組是指根據(jù)資源所代表的項目功能部分將資源分配給 AssetBundle。這包括各種不同部分,比如用戶界面、角色、環(huán)境以及在應(yīng)用程序整個生命周期中可能經(jīng)常出現(xiàn)的任何其他內(nèi)容。
示例:
- 捆綁用戶界面屏幕的所有紋理和布局?jǐn)?shù)據(jù)
- 捆綁一個/一組角色的所有模型和動畫
- 捆綁在多個關(guān)卡之間共享的景物的紋理和模型
邏輯實體分組非常適合于可下載內(nèi)容 (DLC),因為通過這種方式將所有內(nèi)容隔離后,可以對單個實體進(jìn)行更改,而無需下載其他未更改的資源。
為了能夠正確實施此策略,最大訣竅在于,負(fù)責(zé)為各自捆綁包分配資源的開發(fā)人員必須熟悉項目使用每個資源的準(zhǔn)確時機和場合。
3.2 類型分組
根據(jù)此策略,可以將相似類型的資源(例如音頻軌道或語言本地化文件)分配到單個 AssetBundle。
要構(gòu)建供多個平臺使用的 AssetBundle,類型分組是最佳策略之一。例如,如果音頻壓縮設(shè)置在 Windows 和 Mac 平臺上完全相同,則可以自行將所有音頻數(shù)據(jù)打包到 AssetBundle 并重復(fù)使用這些捆綁包,而著色器往往使用更多特定于平臺的選項進(jìn)行編譯,因此為 Mac 構(gòu)建的著色器捆綁包可能無法在 Windows 上重復(fù)使用。此外,這種方法非常適合讓 AssetBundle 與更多 Unity 播放器版本兼容,因為紋理壓縮格式和設(shè)置的更改頻率低于代碼腳本或預(yù)制件。
3.3 并發(fā)內(nèi)容分組
并發(fā)內(nèi)容分組是指將需要同時加載和使用的資源捆綁在一起??梢詫⑦@些類型的捆綁包用于基于關(guān)卡的游戲(其中每個關(guān)卡包含完全獨特的角色、紋理、音樂等)。有時可能希望確保其中一個 AssetBundle 中的資源與該捆綁包中的其余資源同時使用。依賴于并發(fā)內(nèi)容分組捆綁包中的單個資源會導(dǎo)致加載時間顯著增加。您將被迫下載該單個資源的整個捆綁包。
并發(fā)內(nèi)容分組捆綁包最常見的用例是針對基于場景的捆綁包。在此分配策略中,每個場景捆綁包應(yīng)包含大部分或全部場景依賴項。
請注意,項目絕對可以也應(yīng)該根據(jù)您的需求混用這些策略。對任何給定情形使用最優(yōu)資源分配策略可以大大提高項目的效率。
例如,一個項目可能決定將不同平臺的用戶界面 (UI) 元素分組到各自的 Platform-UI 特定捆綁包中,但按關(guān)卡/場景對其交互式內(nèi)容進(jìn)行分組。
3.4 提示
無論遵循何種策略,下面這些額外提示都有助于掌控全局:
- 將頻繁更新的對象與很少更改的對象拆分到不同的 AssetBundle 中
- 將可能同時加載的對象分到一組。例如模型及其紋理和動畫
- 如果發(fā)現(xiàn)多個 AssetBundle 中的多個對象依賴于另一個完全不同的 AssetBundle 中的單個資源,請將依賴項移動到單獨的 AssetBundle。如果多個 AssetBundle 引用其他 AssetBundle 中的同一組資源,一種有價值的做法可能是將這些依賴項拉入一個共享 AssetBundle 來減少重復(fù)。
- 如果不可能同時加載兩組對象(例如標(biāo)清資源和高清資源),請確保它們位于各自的 AssetBundle 中。
- 如果一個 AssetBundle 中只有不到 50% 的資源經(jīng)常同時加載,請考慮拆分該捆綁包
- 考慮將多個小型的(少于 5 到 10 個資源)但經(jīng)常同時加載內(nèi)容的 AssetBundle 組合在一起
- 如果一組對象只是同一對象的不同版本,請考慮使用 AssetBundle 變體
4. 構(gòu)建 AssetBundle
BuildPipeline.BuildAssetBundles 函數(shù)說明。
"Assets/AssetBundles":這是 AssetBundle 要輸出到的目錄??梢詫⑵涓臑樗璧娜魏屋敵瞿夸?,只需確保在嘗試構(gòu)建之前文件夾實際存在。
4.1 BuildAssetBundleOptions
雖然可以根據(jù)需求變化和需求出現(xiàn)而自由組合 BuildAssetBundleOptions,但有三個特定的 BuildAssetBundleOptions 可以處理 AssetBundle 壓縮:
BuildAssetBundleOptions.None:此捆綁包選項使用 LZMA 格式壓縮,這是一個壓縮的 LZMA 序列化數(shù)據(jù)文件流。LZMA 壓縮要求在使用捆綁包之前對整個捆綁包進(jìn)行解壓縮。此壓縮使文件大小盡可能小,但由于需要解壓縮,加載時間略長。值得注意的是,在使用此 BuildAssetBundleOptions 時,為了使用捆綁包中的任何資源,必須首先解壓縮整個捆綁包。
解壓縮捆綁包后,將使用 LZ4 壓縮技術(shù)在磁盤上重新壓縮捆綁包,這不需要在使用捆綁包中的資源之前解壓縮整個捆綁包。最好在包含資源時使用,這樣,使用捆綁包中的一個資源意味著將加載所有資源。這種捆綁包的一些用例是打包角色或場景的所有資源。
由于文件較小,建議僅從異地主機初次下載 AssetBundle 時才使用 LZMA 壓縮。下載文件后,文件將被緩存為 LZ4 壓縮包。BuildAssetBundleOptions.UncompressedAssetBundle:此捆綁包選項采用使數(shù)據(jù)完全未壓縮的方式構(gòu)建捆綁包。未壓縮的缺點是文件下載大小增大。但是,下載后的加載時間會快得多。BuildAssetBundleOptions.ChunkBasedCompression:此捆綁包選項使用稱為 LZ4 的壓縮方法,因此壓縮文件大小比 LZMA 更大,但不像 LZMA 那樣需要解壓縮整個包才能使用捆綁包。LZ4 使用基于塊的算法,允許按段或“塊”加載 AssetBundle。解壓縮單個塊即可使用包含的資源,即使 AssetBundle 的其他塊未解壓縮也不影響。
使用 ChunkBasedCompression 時的加載時間與未壓縮捆綁包大致相當(dāng),額外的優(yōu)勢是減小了占用的磁盤大小。
4.2 BuildTarget
BuildTarget.Standalone:這里我們告訴構(gòu)建管線,我們要將這些 AssetBundle 用于哪些目標(biāo)平臺。
但是,如果不想在構(gòu)建目標(biāo)中進(jìn)行硬編碼,請充分利用 EditorUserBuildSettings.activeBuildTarget,它將自動找到當(dāng)前設(shè)置的目標(biāo)構(gòu)建平臺,并根據(jù)該目標(biāo)構(gòu)建 AssetBundle。
一旦正確設(shè)置構(gòu)建腳本,最后便可以開始構(gòu)建資源包了。如果是按照上面的腳本示例進(jìn)行的操作,請單擊 Assets > Build AssetBundles 以開始該過程。
現(xiàn)在已經(jīng)成功構(gòu)建了 AssetBundle,您可能會注意到 AssetBundles 目錄包含的文件數(shù)量超出了最初的預(yù)期。確切地說,是多出了 2*(n+1) 個文件。讓我們花點時間詳細(xì)了解一下 BuildPipeline.BuildAssetBundles 產(chǎn)生的結(jié)果。
對于在編輯器中指定的每個 AssetBundle,可以看到一個具有 AssetBundle 名稱+“.manifest”的文件。
隨后會有一個額外捆綁包和清單的名稱不同于先前創(chuàng)建的任何 AssetBundle。相反,此包以其所在的目錄(構(gòu)建 AssetBundle 的目錄)命名。這就是清單捆綁包。我們以后會對此進(jìn)行詳細(xì)討論并介紹使用方法。
4.3 AssetBundle 文件
這里不包括 .manifest 擴展名的文件,其中包含在運行時為了加載資源而需要加載的內(nèi)容。
AssetBundle 文件是一個存檔,在內(nèi)部包含多個文件。此存檔的結(jié)構(gòu)根據(jù)它是 AssetBundle 還是場景 AssetBundle 可能會略有不同。以下是普通 AssetBundle 的結(jié)構(gòu):

場景 AssetBundle 與普通 AssetBundle 的不同之處在于,它針對場景及其內(nèi)容的串流加載進(jìn)行了優(yōu)化。
4.4 清單文件
對于生成的每個捆綁包(包括附加的清單捆綁包),都會生成關(guān)聯(lián)的清單文件。清單文件可以使用任何文本編輯器打開,并包含諸如循環(huán)冗余校驗 (CRC) 數(shù)據(jù)和捆綁包的依賴性數(shù)據(jù)之類的信息。對于普通 AssetBundle,它們的清單文件將如下所示:
ManifestFileVersion: 0
CRC: 2422268106
Hashes:
AssetFileHash:
serializedVersion: 2
Hash: 8b6db55a2344f068cf8a9be0a662ba15
TypeTreeHash:
serializedVersion: 2
Hash: 37ad974993dbaa77485dd2a0c38f347a
HashAppended: 0
ClassTypes:
- Class: 91
Script: {instanceID: 0}
Assets:
Asset_0: Assets/Mecanim/StateMachine.controller
Dependencies: {}
其中顯示了包含的資源、依賴項和其他信息。
生成的清單捆綁包將有一個清單,但看起來更可能如下所示:
ManifestFileVersion: 0
AssetBundleManifest:
AssetBundleInfos:
Info_0:
Name: scene1assetbundle
Dependencies: {}
這將顯示 AssetBundle 之間的關(guān)系以及它們的依賴項。就目前而言,只需要了解這個捆綁包中包含 AssetBundleManifest 對象,這對于確定在運行時加載哪些捆綁包依賴項非常有用。
4.5 AssetBundle 依賴項
如果一個或多個 UnityEngine.Objects 包含對位于另一個捆綁包中的 UnityEngine.Object 的引用,則 AssetBundle 可以變?yōu)橐蕾囉谄渌?AssetBundle。如果 UnityEngine.Object 包含對任何 AssetBundle 中未包含的 UnityEngine.Object 的引用,則不會發(fā)生依賴關(guān)系。在這種情況下,在構(gòu)建 AssetBundle 時,捆綁包所依賴的對象的副本將復(fù)制到捆綁包中。如果多個捆綁包中的多個對象包含對未分配給捆綁包的同一對象的引用,則每個對該對象具有依賴關(guān)系的捆綁包將創(chuàng)建其自己的對象副本并將其打包到構(gòu)建的 AssetBundle 中。
如果 AssetBundle 中包含依賴項,則在加載嘗試實例化的對象之前,務(wù)必加載包含這些依賴項的捆綁包。Unity 不會嘗試自動加載依賴項。
5. 本機使用 AssetBundle
可以使用四種不同的 API 來加載 AssetBundle。它們的行為根據(jù)加載捆綁包的平臺和構(gòu)建 AssetBundle 時使用的壓縮方法(未壓縮、LZMA 和 LZ4)而有所不同。
我們必須使用的四個 API 是:
- AssetBundle.LoadFromMemoryAsync
- AssetBundle.LoadFromFile
- WWW.LoadfromCacheOrDownload
- UnityWebRequest 的 DownloadHandlerAssetBundle (Unity 5.3 或更高版本)
AssetBundle.LoadFromMemoryAsync
此函數(shù)采用包含 AssetBundle 數(shù)據(jù)的字節(jié)數(shù)組。也可以根據(jù)需要傳遞 CRC 值。如果捆綁包采用的是 LZMA 壓縮方式,將在加載時解壓縮 AssetBundle。LZ4 壓縮包則會以壓縮狀態(tài)加載。
以下是如何使用此方法的一個示例:
using UnityEngine;
using System.Collections;
using System.IO;
public class Example : MonoBehaviour
{
IEnumerator LoadFromMemoryAsync(string path)
{
AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
yield return createRequest;
AssetBundle bundle = createRequest.assetBundle;
var prefab = bundle.LoadAsset<GameObject>("MyObject");
Instantiate(prefab);
}
}
但是,這不是實現(xiàn) LoadFromMemoryAsync 的唯一策略。File.ReadAllBytes(path) 可以替換為獲得字節(jié)數(shù)組的任何所需過程。
AssetBundle.LoadFromFile
從本地存儲中加載未壓縮的捆綁包時,此 API 非常高效。如果捆綁包未壓縮或采用了數(shù)據(jù)塊 (LZ4) 壓縮方式,LoadFromFile 將直接從磁盤加載捆綁包。使用此方法加載完全壓縮的 (LZMA) 捆綁包將首先解壓縮捆綁包,然后再將其加載到內(nèi)存中。
如何使用 LoadFromFile 的一個示例:
public class LoadFromFileExample extends MonoBehaviour
{
function Start()
{
var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject");
Instantiate(prefab);
}
}
注意:在使用 Unity 5.3 或更早版本的 Android 設(shè)備上,嘗試從流媒體資源 (Streaming Assets) 路徑加載 AssetBundle 時,此 API 將失敗。這是因為該路徑的內(nèi)容將駐留在壓縮的 .jar 文件中。Unity 5.4 和更高版本則可以將此 API 調(diào)用與流媒體資源一起使用。
WWW.LoadFromCacheOrDownload
此 API 對于從遠(yuǎn)程服務(wù)器下載 AssetBundle 或加載本地 AssetBundle 非常有用。這是一個陳舊且不太理想的 UnityWebRequest API 版本。
從遠(yuǎn)程位置加載 AssetBundle 將自動緩存 AssetBundle。如果 AssetBundle 被壓縮,則將啟動工作線程來解壓縮捆綁包并將其寫入緩存。一旦捆綁包被解壓縮并緩存,它就會像 AssetBundle.LoadFromFile 一樣加載。
如何使用 LoadFromCacheOrDownload 的一個示例:
using UnityEngine;
using System.Collections;
public class LoadFromCacheOrDownloadExample : MonoBehaviour
{
IEnumerator Start ()
{
while (!Caching.ready)
yield return null;
var www = WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle", 5);
yield return www;
if(!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
yield return;
}
var myLoadedAssetBundle = www.assetBundle;
var asset = myLoadedAssetBundle.mainAsset;
}
}
由于在 WWW 對象中緩存 AssetBundle 字節(jié)所需的內(nèi)存開銷,建議所有使用 WWW.LoadFromCacheOrDownload 的開發(fā)人員都應(yīng)該確保自己的 AssetBundle 保持較小的大小 - 最多只有幾兆字節(jié)。此外,還建議在有限內(nèi)存平臺(如移動設(shè)備)上運行的開發(fā)人員確保其代碼一次只下載一個 AssetBundle,以此避免內(nèi)存峰值。
如果緩存文件夾沒有任何空間來緩存其他文件,LoadFromCacheOrDownload 將以迭代方式從緩存中刪除最近最少使用的 AssetBundle,直到有足夠的空間來存儲新的 AssetBundle。如果無法騰出空間(因為硬盤已滿,或者緩存中的所有文件當(dāng)前都處于使用狀態(tài)),LoadFromCacheOrDownload() 將不會使用緩存,而將文件流式傳輸?shù)絻?nèi)存中
為了強制執(zhí)行 LoadFromCacheOrDownload,需要更改版本參數(shù)(第二個參數(shù))。僅當(dāng)傳遞給函數(shù)的版本與當(dāng)前緩存的 AssetBundle 的版本匹配,才會從緩存加載 AssetBundle。
UnityWebRequest
UnityWebRequest 有一個特定 API 調(diào)用來處理 AssetBundle。首先,需要使用 UnityWebRequest.GetAssetBundle 來創(chuàng)建 Web 請求。返回請求后,請將請求對象傳遞給 DownloadHandlerAssetBundle.GetContent(UnityWebRequest)。GetContent 調(diào)用將返回 AssetBundle 對象。
下載捆綁包后,還可以在 DownloadHandlerAssetBundle 類上使用 assetBundle 屬性,從而以 AssetBundle.LoadFromFile 的效率加載 AssetBundle。
以下示例說明了如何加載包含兩個游戲?qū)ο蟮?AssetBundle 并實例化這些游戲?qū)ο?。要開始這個過程,我們只需要調(diào)用 StartCoroutine(InstantiateObject());
IEnumerator InstantiateObject()
{
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName; UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject cube = bundle.LoadAsset<GameObject>("Cube");
GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");
Instantiate(cube);
Instantiate(sprite);
}
使用 UnityWebRequest 的優(yōu)點在于,它允許開發(fā)人員以更靈活的方式處理下載的數(shù)據(jù),并可能消除不必要的內(nèi)存使用。這是比 UnityEngine.WWW 類更新和更優(yōu)的 API。
從 AssetBundle 加載資源
現(xiàn)在已經(jīng)成功下載 AssetBundle,因此是時候最終加載一些資源了。
通用代碼片段:
T objectFromBundle = bundleObject.LoadAsset<T>(assetName);
T 是嘗試加載的資源類型。
決定如何加載資源時有幾個選項。我們有 LoadAsset、LoadAllAssets 及其各自的異步對應(yīng)選項 LoadAssetAsync 和 LoadAllAssetsAsync。
同步從 AssetBundle 加載資源的方法如下:
加載單個游戲?qū)ο螅?/p>
GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);
加載所有資源:
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();
現(xiàn)在,在前面顯示的方法返回要加載的對象類型或?qū)ο髷?shù)組的情況下,異步方法返回 AssetBundleRequest。在訪問資源之前,需要等待此操作完成。加載資源:
AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName);
yield return request;
var loadedAsset = request.asset;
以及:
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync();
yield return request;
var loadedAssets = request.allAssets;
加載資源后,就可以開始了!可以像使用 Unity 中的任何對象一樣使用加載的對象。
加載 AssetBundle 清單
加載 AssetBundle 清單可能非常有用。特別是在處理 AssetBundle 依賴關(guān)系時。
要獲得可用的 AssetBundleManifest 對象,需要加載另外的 AssetBundle(與其所在的文件夾名稱相同的那個)并從中加載 AssetBundleManifest 類型的對象。
加載清單本身的操作方法與 AssetBundle 中的任何其他資源完全相同:
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
現(xiàn)在,可以通過上面示例中的清單對象訪問 AssetBundleManifest API 調(diào)用。從這里,可以使用清單獲取所構(gòu)建的 AssetBundle 的相關(guān)信息。此信息包括 AssetBundle 的依賴項數(shù)據(jù)、哈希數(shù)據(jù)和變體數(shù)據(jù)。
別忘了在前面的部分中,我們討論過 AssetBundle 依賴項以及如果一個捆綁包對另一個捆綁包有依賴性,那么在從原始捆綁包加載任何資源之前,需要加載哪些捆綁包?清單對象可以動態(tài)地查找加載依賴項。假設(shè)我們想要為名為“assetBundle”的 AssetBundle 加載所有依賴項。
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies("assetBundle"); //傳遞想要依賴項的捆綁包的名稱。
foreach(string dependency in dependencies)
{
AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}
現(xiàn)在已經(jīng)加載 AssetBundle、AssetBundle 依賴項和資源,因此是時候討論如何管理所有這些已加載的 AssetBundle 了。
管理已加載的 AssetBundle
從活動場景中刪除對象時,Unity 不會自動卸載對象。資源清理在特定時間觸發(fā),也可以手動觸發(fā)。
了解何時加載和卸載 AssetBundle 非常重要。不正確地卸載 AssetBundle 會導(dǎo)致在內(nèi)存中復(fù)制對象或其他不良情況,例如缺少紋理。
關(guān)于 AssetBundle 管理最重要的事情就是何時調(diào)用
AssetBundle.Unload(bool); 以及應(yīng)該將 true 還是 false 傳遞給函數(shù)調(diào)用。Unload 是一個非靜態(tài)函數(shù),可用于卸載 AssetBundle。此 API 會卸載正在調(diào)用的 AssetBundle 的標(biāo)頭信息。該參數(shù)指示是否還要卸載通過此 AssetBundle 實例化的所有對象。
AssetBundle.Unload(true) 卸載從 AssetBundle 加載的所有游戲?qū)ο螅捌湟蕾図棧?。這不包括復(fù)制的游戲?qū)ο螅ɡ鐚嵗挠螒驅(qū)ο螅?,因為它們不再屬?AssetBundle。發(fā)生這種情況時,從該 AssetBundle 加載的紋理(并且仍然屬于它)會從場景中的游戲?qū)ο笙?,因?Unity 將它們視為缺少紋理。
假設(shè)材質(zhì) M 是從 AssetBundle AB 加載的,如下所示。
如果調(diào)用 AB.Unload(true),活動場景中的任何 M 實例也將被卸載并銷毀。
如果改作調(diào)用 AB.Unload(false),那么將會中斷 M 和 AB 當(dāng)前實例的鏈接關(guān)系。

如果稍后再次加載 AB 并且調(diào)用 AB.LoadAsset(),則 Unity 不會將現(xiàn)有 M 副本重新鏈接到新加載的材質(zhì)。而是將加載 M 的兩個副本。


通常,使用
AssetBundle.Unload(false) 不會帶來理想情況。大多數(shù)項目應(yīng)該使用 AssetBundle.Unload(true) 來防止在內(nèi)存中復(fù)制對象。大多數(shù)項目應(yīng)該使用
AssetBundle.Unload(true) 并采用一種方法來確保對象不會重復(fù)。兩種常用方法是:
在應(yīng)用程序生命周期中具有明確定義的卸載瞬態(tài) AssetBundle 的時間點,例如在關(guān)卡之間或在加載屏幕期間。
維護(hù)單個對象的引用計數(shù),僅當(dāng)未使用所有組成對象時才卸載 AssetBundle。這允許應(yīng)用程序卸載和重新加載單個對象,而無需復(fù)制內(nèi)存。
如果應(yīng)用程序必須使用 AssetBundle.Unload(false),則只能以兩種方式卸載單個對象:
在場景和代碼中消除對不需要的對象的所有引用。完成此操作后,調(diào)用 Resources.UnloadUnusedAssets。
以非附加方式加載場景。這樣會銷毀當(dāng)前場景中的所有對象并自動調(diào)用 Resources.UnloadUnusedAssets。
6.更新 AssetBundle
更新 AssetBundle 很簡單,只需要下載新的 AssetBundle 并替換現(xiàn)有的 AssetBundle。如果使用 WWW.LoadFromCacheOrDownload 或 UnityWebRequest 來管理應(yīng)用程序的緩存 AssetBundle,則將不同的版本參數(shù)傳遞給所選 API 將觸發(fā)新 AssetBundle 的下載。
在修補系統(tǒng)中要解決的更難的問題是檢測要替換的 AssetBundle。修補系統(tǒng)需要兩個信息列表:
- 當(dāng)前已下載的 AssetBundle 及其版本控制信息的列表
- 服務(wù)器上的 AssetBundle 及其版本控制信息的列表
修補程序應(yīng)下載服務(wù)器端 AssetBundle 列表并比較這些 AssetBundle 列表。應(yīng)重新下載缺少的 AssetBundle 或已更改版本控制信息的 AssetBundle。
也可以編寫一個自定義系統(tǒng)來檢測 AssetBundle 的更改。自己編寫系統(tǒng)的大多數(shù)開發(fā)人員會選擇對 AssetBundle 文件列表使用行業(yè)標(biāo)準(zhǔn)數(shù)據(jù)格式(例如 JSON)和并使用標(biāo)準(zhǔn) C# 類(例如 MD5)來計算校驗和。
Unity 使用以確定方式排序的數(shù)據(jù)構(gòu)建 AssetBundle。因此,具有自定義下載程序的應(yīng)用程序可以實現(xiàn)差異修補。
Unity 不提供任何內(nèi)置的差異修補機制,并且 WWW.LoadFromCacheOrDownload 和 UnityWebRequest 在使用內(nèi)置緩存系統(tǒng)時都不會執(zhí)行差異修補。如果需要差異修補,則必須編寫自定義下載程序。
7.常見問題說明
本部分將介紹使用 AssetBundle 的項目中常見的幾個問題。
資源重復(fù)
當(dāng)對象構(gòu)建到 AssetBundle 中時,Unity 5 的 AssetBundle 系統(tǒng)會查找對象的所有依賴項。這是使用資源數(shù)據(jù)庫完成的。此依賴關(guān)系信息用于確定包含在 AssetBundle 中的對象集。
顯式分配給 AssetBundle 的對象將僅構(gòu)建到該 AssetBundle 中。當(dāng)對象的 AssetImporter 將其 assetBundleName 屬性設(shè)置為非空字符串時,表示“顯式指定”該對象。
未顯式分配到 AssetBundle 中的任何對象將包含在所有 AssetBundle 中,這些 AssetBundle 會包含一個或多個引用該未標(biāo)記對象的對象。
如果將兩個不同的對象分配給兩個不同的 AssetBundle,但兩者都引用了一個共同的依賴項對象,那么該依賴項對象將被復(fù)制到兩個 AssetBundle 中。復(fù)制的依賴項也將被實例化,這意味著依賴項對象的兩個副本將被視為具有不同標(biāo)識符的不同對象。這將增加應(yīng)用程序的 AssetBundle 的總大小。如果應(yīng)用程序加載對象的兩個父項,則還會導(dǎo)致將兩個不同的對象副本加載到內(nèi)存中。
有幾種方法可以解決這個問題:
1.確保構(gòu)建到不同 AssetBundle 中的對象不共享依賴項。任何共享依賴項的對象都可以放在同一個 AssetBundle 中,而不會復(fù)制它們的依賴項。
- 對于具有許多共享依賴項的項目,此方法通常不可行。這種情況下可能生成單獨的 AssetBundle,必須高度頻繁進(jìn)行重建和重新下載,因此很不方便或高效。
2.對 AssetBundle 進(jìn)行分段,確保不會同時加載兩個共享依賴項的 AssetBundle。 - 此方法可能適用于某些類型的項目,例如基于關(guān)卡的游戲。但是,仍然會不必要地增加項目的 AssetBundle 大小,并增加構(gòu)建時間和加載時間。
3.確保所有依賴項資源都構(gòu)建到自己的 AssetBundle 中。這樣可以完全消除重復(fù)資源的風(fēng)險,但也帶來了復(fù)雜性。應(yīng)用程序必須跟蹤 AssetBundle 之間的依賴關(guān)系,并確保在調(diào)用任何 AssetBundle.LoadAsset API 之前加載了正確的 AssetBundle。
Unity 5 通過位于 UnityEditor 命名空間中的 AssetDatabase API 來跟蹤對象依賴項。正如命名空間的名稱所示,此 API 僅在 Unity Editor 中可用,而不能在運行時使用??墒褂?AssetDatabase.GetDependencies 查找特定對象或資源的所有直接依賴項。請注意,這些依賴項可能還有自己的依賴項。此外,可使用 AssetImporter API 查詢分配了任何特定對象的 AssetBundle。
通過組合 AssetDatabase 和 AssetImporter API,可以編寫一個 Editor 腳本,確保將所有 AssetBundle 的直接或間接依賴項都分配給 AssetBundle,或者不會有兩個 AssetBundle 共享尚未分配給 AssetBundle 的依賴項。由于復(fù)制資源的內(nèi)存成本,建議所有項目都采用這樣的腳本。
精靈圖集重復(fù)
以下部分將介紹 Unity 5 的資源依賴性計算代碼在與自動生成的精靈圖集結(jié)合使用時出現(xiàn)的奇怪行為。
任何自動生成的精靈圖集都將與生成精靈圖集的精靈對象一起分配到同一個 AssetBundle。如果精靈對象被分配給多個 AssetBundle,則精靈圖集將不會被分配給 AssetBundle 并且將被復(fù)制。如果精靈對象未分配給 AssetBundle,則精靈圖集也不會分配給 AssetBundle。
為了確保精靈圖集不重復(fù),請確保標(biāo)記到相同精靈圖集的所有精靈都被分配到同一個 AssetBundle。
Unity 5.2.2p3 及更低版本
自動生成的精靈圖集永遠(yuǎn)不會分配給 AssetBundle。因此,它們將被包含在任何包含其組成精靈的 AssetBundle 中,還會包含在任何引用其組成精靈的 AssetBundle 中。
由于這個問題,強烈建議將使用 Unity Sprite Packer 的所有 Unity 5 項目都升級到 Unity 5.2.2p4、5.3 或任何更高版本的 Unity。
對于無法升級的項目,此問題有兩種解決方法:
1.簡單:避免使用 Unity 的內(nèi)置 Sprite Packer。外部工具生成的精靈圖集將是正常的資源,可以正確分配給 AssetBundle。
2.困難:將所有使用自動加入圖集的精靈的對象分配給與精靈相同的 AssetBundle。
- 這將確保生成的精靈圖集不被視為任何其他 AssetBundle 的間接依賴項,并且不會被復(fù)制。
- 此解決方案保留了使用 Unity Sprite Packer 的工作流程,但會降低開發(fā)人員將資源分離到不同 AssetBundle 的能力,并會強制在引用圖集的任何組件上的任何數(shù)據(jù)發(fā)生變化時重新下載整個精靈圖集,即使該圖集本身沒有變化也是如此。
Android 紋理
由于 Android 生態(tài)系統(tǒng)中存在嚴(yán)重的設(shè)備碎片,因此通常需要將紋理壓縮為多種不同的格式。雖然所有 Android 設(shè)備都支持 ETC1,但 ETC1 不支持具有 Alpha 通道的紋理。如果應(yīng)用程序不需要 OpenGL ES 2 支持,解決該問題的最簡單方法是使用所有 Android OpenGL ES 3 設(shè)備都支持的 ETC2。
大多數(shù)應(yīng)用程序需要在不支持 ETC2 的舊設(shè)備上發(fā)布。解決這個問題的一種方法是使用 Unity 5 的 AssetBundle 變體。(有關(guān)其他方案的詳細(xì)信息,請參閱 Unity 的 Android 優(yōu)化指南。)
要使用 AssetBundle 變體,必須將無法使用 ETC1 完全壓縮的所有紋理隔離到僅包含紋理的 AssetBundle 中。接下來,使用供應(yīng)商特有的紋理壓縮格式(如 DXT5、PVRTC 和 ATITC),創(chuàng)建這些 AssetBundle 的足夠多變體來支持 Android 生態(tài)系統(tǒng)中不支持 ETC2 的部分。對于每個 AssetBundle 變體,應(yīng)將包含的紋理的 TextureImporter 設(shè)置更改為適合變體的壓縮格式。
在運行時,可以使用 SystemInfo.SupportsTextureFormat API 檢測對不同紋理壓縮格式的支持情況。應(yīng)使用此信息來選擇和加載含有以受支持格式壓縮的紋理的 AssetBundle 變體。
iOS 文件句柄過度使用
Unity 5.3.2p2 中修復(fù)了以下部分中描述的問題。最新版本的 Unity 不受此問題的影響。
在 Unity 5.3.2p2 之前的版本中,Unity 將在加載 AssetBundle 的整個時間內(nèi)保留 AssetBundle 的打開文件句柄。這在大多數(shù)平臺上都不是問題。但是,iOS 將一個進(jìn)程可以同時打開的文件句柄數(shù)量限制為 255。如果加載 AssetBundle 導(dǎo)致超出此限制,則加載調(diào)用將失敗,并顯示“Too Many Open File Handles”錯誤。
對于試圖將內(nèi)容劃分為數(shù)百或數(shù)千個 AssetBundle 的項目而言,這是一個常見問題。
對于無法升級到修補版 Unity 的項目來說,臨時解決方案如下:
- 通過合并相關(guān)的 AssetBundle 減少正在使用的 AssetBundle 數(shù)量
- 使用 AssetBundle.Unload(false) 關(guān)閉 AssetBundle 的文件句柄,并手動管理加載的對象的生命周期