介紹
- AB作為之前Unity主推的資源管理工作流,可以把模型、貼圖、預制體、聲音、甚至整個場景都打入壓縮包中,然后在游戲過程中再加載。使用他的主要目的有以下幾點:1.統(tǒng)一的資源管理、2.做分包、3.熱更資源。他也有一個最大的缺點就是需要開發(fā)者自己寫一套資源管理系統(tǒng)來管理依賴、引用關(guān)系。
- 可尋址資源系統(tǒng)是現(xiàn)在Unity現(xiàn)在主推的資源管理工作流,他的基石是AB,所以他擁有之前AB所有的功能。并且他已經(jīng)幫助開發(fā)者實現(xiàn)了資源管理系統(tǒng)。
打包對比
AB打包方式
-
AB使用BuildPipeline.BuildAssetBundles(string outputPath , BuildAssetBundleOptions options, BuildTarget target)接口對所有標記為ab資產(chǎn)的資源進行打包。
outputPath:打包后的資源輸出路徑
-
options:使用什么方式對資產(chǎn)進行打包。
-
BuildAssetBundleOptions.None
:此捆綁包選項使用LZMA格式壓縮。這樣打包后的資源占用磁盤空間最小,但是使用時必須解壓縮整個捆綁包一次,加載速度會更慢。 -
BuildAssetBundleOptions.UncompressedAssetBundle
:此包選項以完全未壓縮數(shù)據(jù)的方式構(gòu)建包。解壓縮的缺點是文件下載量較大。但是,一旦下載,加載時間將最快。 -
BuildAssetBundleOptions.ChunkBasedCompression
:此捆綁包選項使用一種稱為LZ4的壓縮方法,與LZMA相比,其壓縮文件大小更大,但與LZMA不同,不需要使用整個捆綁包進行解壓縮后才能使用。LZ4使用基于塊的算法,該算法允許以片段或“塊”的形式加載AB。
使用
ChunkBasedCompression
具有與未壓縮的包相當?shù)募虞d時間,并具有減小磁盤大小的額外好處。 -
target:此參數(shù)決定了當前構(gòu)建的捆綁包用于哪一個目標平臺。
-
AB文件
這是一個缺少.manifest擴展名的文件,以及您在運行時要加載的資源,以加載資產(chǎn)。
AB文件是一個存檔,在內(nèi)部包含多個文件。此存檔的結(jié)構(gòu)可能會略有變化,具體取決于它是普通AB還是場景AB。這是普通AB的結(jié)構(gòu):
場景AB與普通的AB不同,因為它已針對場景及其內(nèi)容的流加載進行了優(yōu)化。
-
清單文件
對于每個生成的捆綁包,包括附加的清單捆綁包,都會生成一個關(guān)聯(lián)的清單文件。清單文件可以使用任何文本編輯器打開,并且包含諸如捆綁包的循環(huán)冗余校驗(CRC)數(shù)據(jù)和依賴項數(shù)據(jù)之類的信息。對于普通的AB,其清單文件將如下所示: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: {}
Addressable打包方式
-
Addressable跟AB的打包方式有所不同,因為它可以選擇種中播放模式,如下圖所示
1574170947802.png- Fast Mode: 快速模式:直接加載文件而不打包,快速但Profiler獲取的信息較少;在此模式下,我們實際上時使用 AssetDatabase.LoadAssetAtPath 直接加載文件。
- Virtual Mode:虛擬模式:在不打包的情況下模擬靠近AB的操作;與FastMode不同,您可以查看哪個AB包含資產(chǎn)。此模式最終還加載了AssetDatabase.LoadAssetAtPath 。
- Packed Mode:打包模式:實際上是從AB打包和加載;在這種模式下,實際構(gòu)建并加載AB。
如果在編輯器模式下使用Addressable推薦使用Virtual Mode來模擬打包加載,在真正要上線期間再使用Packed Play Mode 來進行打包加載。
加載資源對比
AB加載方式
我們用AB來加載資源的過程中,主要包含兩個過程,第一步是先要加載本地或者網(wǎng)絡上的AB資產(chǎn)包,再通過操作這個AB來加載它里面所包含的資源。
-
加載AB有以下三種方式:
-
public static AssetBundle LoadFromFile(string path, uint crc, ulong offset);
path 磁盤上文件的路徑。 crc 未壓縮內(nèi)容的可選CRC-32校驗。如果它不為零,則在加載內(nèi)容之前將其與校驗和進行比較,如果不匹配則給出錯誤。 offset 可選的字節(jié)偏移量。該值指定從何處開始讀取AB。 這是本地磁盤加載到內(nèi)存中所以是加載ab包最快的方式,它可以加載任意壓縮形式的ab包。
public static AssetBundle LoadFromMemory(byte[] binary, uint crc);
binary 具有AB數(shù)據(jù)的字節(jié)數(shù)組。 crc 未壓縮內(nèi)容的可選CRC-32校驗和。如果它不為零,則在加載內(nèi)容之前將其與校驗和進行比較,如果不匹配則給出錯誤。 ? 使用此方法從字節(jié)數(shù)組創(chuàng)建AB。當下載帶有加密的數(shù)據(jù)并且需要從未加密的字節(jié)創(chuàng)建AB時,這很有用。
-
public static Networking.UnityWebRequest GetAssetBundle(string uri, uint version, uint crc);
uri 要下載的資產(chǎn)捆綁包的URI version 版本號,它將與要下載的資產(chǎn)捆綁包的緩存版本進行比較。如果不一樣則則將重新下載資產(chǎn)捆綁。 crc 版本哈希。如果此哈希與該資產(chǎn)捆綁的緩存版本的哈希不匹配,則將重新下載資產(chǎn)捆綁。 創(chuàng)建一個優(yōu)化的UnityWebRequest,用于通過HTTP GET下載Unity資產(chǎn)捆綁包。
-
-
從AB中加載資源的方式
public <T> LoadAsset<T> (string name);
name:資源名稱
-
T:資源類型
該接口可以同步的加載資源
public AssetBundleRequest LoadAssetAsync(string name);
-
name:資源名稱
該接口可以異步的加載資源
Addressable加載方式
我們首先把資源標記為可尋址,可尋址資源系統(tǒng)會給我們一個它的地址,然后我們可以根據(jù)這個地址去異步加載資源。可尋址資源系統(tǒng)的加載資源方式都是異步的,所以我們需要監(jiān)聽它加載完成再使用。
-
通過代碼編寫
public static AsyncOperationHandle <T> LoadAssetAsync<T>(object key);
- key:資源的地址
- AsyncOperationHandle :監(jiān)聽異步操作的句柄
public static void InstantiateAsync(object key);
-
key:資源的地址
該接口可以直接通過地址來實例化物體
-
通過AssetReference 訪問可尋址資產(chǎn)而不需要知道它的地址
-
我們在繼承自monobehaviour的腳步中添加一個成員:
public AssetReference ar;
-
然后在該腳本組件的視圖中就可以選擇可尋址資產(chǎn)了。
1564148138753.png這樣我們就無需知道該可尋址資源的地址也可以直接加載它。
//先加載后面自己控制實例化 ar.LoadAssetAsync<T>().Completed += LoadComplete; //直接實例化 ar.InstantiateAsync();
以上就是可尋址資源系統(tǒng)最簡單的加載一個資源的方法。
-
-
我們也可以通過傳入好幾個地址或者標簽來加載一系列資源。在可尋址資源系統(tǒng)中我們可以定義一系列標簽,然后再給可尋址資源貼上相應標簽,然后我們通過標簽來加載資源時會直接加載所有貼上該標簽的資源。**
public static AsyncOperationHandle<IList<T>> LoadAssetsAsync<T>(object key, Action<T> callback);
- 返回值 AsyncOperationHandle:異步句柄
- key:標簽
- callback:每個資源被加載完成后會調(diào)用的回調(diào)
public static AsyncOperationHandle<IList<T>> LoadAssetsAsync<T>(IList<object> keys, Action<T> callback, MergeMode mode);
- 返回值 AsyncOperationHandle : 異步句柄
- keys:所有要加載的資源的地址
- callback:每個資源被加載完成后會調(diào)用的回調(diào)
- mode:最后返回的所有資源
卸載資源方式對比
AB卸載方式
該圖可以清晰的描述AB加載后的內(nèi)存分布和不同的卸載方式對應卸載哪一些區(qū)域。
- 我們通過本地或者網(wǎng)絡加載后都會有一份內(nèi)存鏡像,我們需要通過它再來加載資源,如果我們調(diào)用了AssetBundle.Unload(false)那么內(nèi)存鏡像被釋放,我們也不能再通過這個ab包來加載資源,但是之前加載的資源不會被釋放,我們可以通過Re'sources.UnloadAsset(obj)來釋放加載的資源,如果想要釋放所有從ab包加載的資源那么就需要調(diào)用AssetBundle.Unload(true),如果這個時候還有其他資源引用了ab包加載的資源,那么就會造成引用丟失造成顯示不正常。
Addressable卸載方式
由于Addressable是在AB的基礎上使用的系統(tǒng),所以它的內(nèi)存分布跟AB差不多,但是由于我們不能拿到資源對應的ab對象,只能通過Addressable給出的接口釋放資源。Addressable內(nèi)部已經(jīng)給我們做了引用計數(shù)管理,所有當我們釋放對應資源時,只有當引用計數(shù)為0才會真正的卸載對應ab對象。
-
不能實例化的資源卸載方式
1.public static void Release<T>(T obj);
- obj:資源
? 直接釋放T類型的資源,并且減少引用計數(shù)
2.public static void Release(AsyncOperationHandle handle);
- handle:句柄
? 釋放由該句柄加載出來的資源,并減少引用計數(shù)
-
能實例化的資源卸載方式
1.public static bool ReleaseInstance(GameObject instance);
-
instance:實例化的資源
銷毀游戲物體,并且減少引用計數(shù)。如果用該方法卸載不是由尋址系統(tǒng)實例化的物體,那么釋放不會產(chǎn)生影響。
2.public static bool ReleaseInstance(AsyncOperationHandle handle);
-
handle:句柄
釋放由該句柄加載出來的資源,并減少引用計數(shù)
這邊需要注意一點,實例化資源時可以傳一個參數(shù)trackHandle默認為true。如果他為true那么銷毀實例化的資源用上面2個接口都可以,如果為false那么只能用第二個接口。
-
熱更資源方式對比
AB熱更資源方式
熱更資源在Addressable推出之前每個項目都有自己的做法,但是大致上都可以抽象為以下流程。
- 先給每一個ab記錄一個版本號,這個版本號可以是md5、hash值或者版本數(shù)字。但是最好是一個版本數(shù)字,防止項目遷移之后資源計算出的md5或者hash發(fā)生改變從而導致重新下載一遍相同資源。然后將所有包名對應的版本號記錄在一個資源配置文件里。
- 第一次進入游戲沒有這個資源配置文件,先下載資源配置文件,再把配置文件里的所有ab下載到本地。
- 之后登陸游戲時對比本地資源配置文件里的資源版本號和遠程資源配置文件里的資源版本號,如果版本號小了,那么記錄這個ab。最后把所有需要更新的ab下載并且更新本地文件。
- 資源配置文件除了可以記錄版本,還可以附加一些信息比如ab大小,所以我們下載時可以彈窗提示玩家需要更新多少大的文件。
Addressable熱更資源方式
Addressable也支持資源熱更,但是還是有很多不足的地方。我用了它的熱更功能然后看了它熱更功能的實現(xiàn)后發(fā)現(xiàn)它更加適合邊下邊玩,而不是在游戲剛進去時更新完所有資源。原因有以下幾點:
- 因為我們把下載ab的實現(xiàn)交給了addressable,然后它的實現(xiàn)是當你在加載資源時找到這個資源的ab包,然后通過UnityWebRequestAssetBundle判斷該ab包是不是已經(jīng)下載如果下載那么直接從緩存目錄加載,不然就下載到緩存目錄再加載。所以我們要先加載資源才會去下載ab包。
- 目前沒有提供接口來知道那些ab包需要熱更。
雖然它本身不支持游戲初始化時下載所有資源,但是因為addressable是開源的,我們可以直接修改源碼來達到我們自己想要的效果。比如我們想在游戲剛開始得到所有需要更新ab的大小,我們可以通過以下代碼來實現(xiàn)
AddressablesImpl.cs
//添加2個新方法
//初始化完之后調(diào)用GetRemoteBundleSizeAsync方法
AsyncOperationHandle<long> GetRemoteBundleSizeWithChain(IList<string> bundles)
{
return ResourceManager.CreateChainOperation(InitializationOperation, op => GetRemoteBundleSizeAsync(key));
}
//通過ab包名得到ab包大小
public AsyncOperationHandle<long> GetRemoteBundleSizeAsync(IList<string> bundles)
{
//如果還沒初始化完那么等待初始化完
if(!InitializationOperation.IsDone)
return GetRemoteBundleSizeWithChain(key);
IList<IResourceLocation> locations = new IList<IResourceLocation>();
for(var i = 0; i < bundles.Count, i++)
{
IList<IResourceLocation> tmpLocations;
var key = bundles[i];
//尋找傳入的包名對應的ab包,如果沒找到那么警告
if(!GetResourceLocations(key, typeof(object), out locations))
return ResourceManager.CreateCompletedOperation<Long>(0, new InvalidKeyException(key).Message);
locations.Add(tmpLocations[0]);
}
//總的包大小
long size = 0;
for(var i = 0; i < locations.Count; i++)
{
if(locations[i].Data != null)
{
var sizeData = locations[i].Data as ILocationSizeData;
if(sizeData != null)
{
//計算包大小
size += sizeData.ComputeSize(locations[i]);
}
}
}
//返回總的包大小
return ResourceManager.CreateCompletedOperation<Long>(size, string.Empty)
}
//在對應的Addressables外觀類里也添加GetRemoteBundleSizeAsync方法
//使用方法
//在addressable初始化完成后遍歷所有地址,如果地址的結(jié)尾是.bundle,那么他對應了一個ab包,然后把它緩存到列表,再使用添加的接口來獲得所有需要更新包的大小。
Addressables.InitializeAsync().Completed += opHandle =>
{
var map = opHandle.Result as ResourceLocationMap;
List<string> bundles = new List<string>();
foreach (object mapKey in map.keys)
{
string key = mapKey as string;
if(key != null && key.EndsWith(".bundle"))
{
bundles.Add(key);
}
}
Addressables.GetRemoteBundleSizeAsync(key).Completed += asyncOpHandle => print(asyncOpHandle.Result);
};