介紹
- AB作為之前Unity主推的資源管理工作流兴溜,可以把模型、貼圖哑梳、預(yù)制體劲阎、聲音、甚至整個(gè)場(chǎng)景都打入壓縮包中鸠真,然后在游戲過(guò)程中再加載哪工。使用他的主要目的有以下幾點(diǎn):1.統(tǒng)一的資源管理、2.做分包弧哎、3.熱更資源雁比。他也有一個(gè)最大的缺點(diǎn)就是需要開發(fā)者自己寫一套資源管理系統(tǒng)來(lái)管理依賴、引用關(guān)系撤嫩。
- 可尋址資源系統(tǒng)是現(xiàn)在Unity現(xiàn)在主推的資源管理工作流偎捎,他的基石是AB,所以他擁有之前AB所有的功能序攘。并且他已經(jīng)幫助開發(fā)者實(shí)現(xiàn)了資源管理系統(tǒng)茴她。
打包對(duì)比
AB打包方式
-
AB使用BuildPipeline.BuildAssetBundles(string outputPath , BuildAssetBundleOptions options, BuildTarget target)接口對(duì)所有標(biāo)記為ab資產(chǎn)的資源進(jìn)行打包。
outputPath:打包后的資源輸出路徑
-
options:使用什么方式對(duì)資產(chǎn)進(jìn)行打包程奠。
-
BuildAssetBundleOptions.None
:此捆綁包選項(xiàng)使用LZMA格式壓縮丈牢。這樣打包后的資源占用磁盤空間最小,但是使用時(shí)必須解壓縮整個(gè)捆綁包一次瞄沙,加載速度會(huì)更慢己沛。 -
BuildAssetBundleOptions.UncompressedAssetBundle
:此包選項(xiàng)以完全未壓縮數(shù)據(jù)的方式構(gòu)建包。解壓縮的缺點(diǎn)是文件下載量較大距境。但是申尼,一旦下載,加載時(shí)間將最快垫桂。 -
BuildAssetBundleOptions.ChunkBasedCompression
:此捆綁包選項(xiàng)使用一種稱為L(zhǎng)Z4的壓縮方法师幕,與LZMA相比,其壓縮文件大小更大诬滩,但與LZMA不同霹粥,不需要使用整個(gè)捆綁包進(jìn)行解壓縮后才能使用灭将。LZ4使用基于塊的算法,該算法允許以片段或“塊”的形式加載AB后控。
使用
ChunkBasedCompression
具有與未壓縮的包相當(dāng)?shù)募虞d時(shí)間宗侦,并具有減小磁盤大小的額外好處。 -
target:此參數(shù)決定了當(dāng)前構(gòu)建的捆綁包用于哪一個(gè)目標(biāo)平臺(tái)忆蚀。
-
AB文件
這是一個(gè)缺少.manifest擴(kuò)展名的文件矾利,以及您在運(yùn)行時(shí)要加載的資源,以加載資產(chǎn)馋袜。
AB文件是一個(gè)存檔男旗,在內(nèi)部包含多個(gè)文件。此存檔的結(jié)構(gòu)可能會(huì)略有變化欣鳖,具體取決于它是普通AB還是場(chǎng)景AB察皇。這是普通AB的結(jié)構(gòu):
場(chǎng)景AB與普通的AB不同,因?yàn)樗厌槍?duì)場(chǎng)景及其內(nèi)容的流加載進(jìn)行了優(yōu)化泽台。
-
清單文件
對(duì)于每個(gè)生成的捆綁包什荣,包括附加的清單捆綁包,都會(huì)生成一個(gè)關(guān)聯(lián)的清單文件怀酷。清單文件可以使用任何文本編輯器打開稻爬,并且包含諸如捆綁包的循環(huán)冗余校驗(yàn)(CRC)數(shù)據(jù)和依賴項(xiàng)數(shù)據(jù)之類的信息。對(duì)于普通的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的打包方式有所不同桅锄,因?yàn)樗梢赃x擇種中播放模式,如下圖所示
- Fast Mode: 快速模式:直接加載文件而不打包样眠,快速但Profiler獲取的信息較少友瘤;在此模式下,我們實(shí)際上時(shí)使用 AssetDatabase.LoadAssetAtPath 直接加載文件檐束。
- Virtual Mode:虛擬模式:在不打包的情況下模擬靠近AB的操作辫秧;與FastMode不同,您可以查看哪個(gè)AB包含資產(chǎn)被丧。此模式最終還加載了AssetDatabase.LoadAssetAtPath 盟戏。
- Packed Mode:打包模式:實(shí)際上是從AB打包和加載;在這種模式下晚碾,實(shí)際構(gòu)建并加載AB抓半。
如果在編輯器模式下使用Addressable推薦使用Virtual Mode來(lái)模擬打包加載喂急,在真正要上線期間再使用Packed Play Mode 來(lái)進(jìn)行打包加載格嘁。
加載資源對(duì)比
AB加載方式
我們用AB來(lái)加載資源的過(guò)程中,主要包含兩個(gè)過(guò)程廊移,第一步是先要加載本地或者網(wǎng)絡(luò)上的AB資產(chǎn)包糕簿,再通過(guò)操作這個(gè)AB來(lái)加載它里面所包含的資源探入。
-
加載AB有以下三種方式:
-
public static AssetBundle LoadFromFile(string path, uint crc, ulong offset);
path 磁盤上文件的路徑。 crc 未壓縮內(nèi)容的可選CRC-32校驗(yàn)懂诗。如果它不為零蜂嗽,則在加載內(nèi)容之前將其與校驗(yàn)和進(jìn)行比較,如果不匹配則給出錯(cuò)誤殃恒。 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校驗(yàn)和亥鬓。如果它不為零完沪,則在加載內(nèi)容之前將其與校驗(yàn)和進(jìn)行比較,如果不匹配則給出錯(cuò)誤嵌戈。 ? 使用此方法從字節(jié)數(shù)組創(chuàng)建AB覆积。當(dāng)下載帶有加密的數(shù)據(jù)并且需要從未加密的字節(jié)創(chuàng)建AB時(shí),這很有用熟呛。
-
public static Networking.UnityWebRequest GetAssetBundle(string uri, uint version, uint crc);
uri 要下載的資產(chǎn)捆綁包的URI version 版本號(hào)宽档,它將與要下載的資產(chǎn)捆綁包的緩存版本進(jìn)行比較。如果不一樣則則將重新下載資產(chǎn)捆綁庵朝。 crc 版本哈希雌贱。如果此哈希與該資產(chǎn)捆綁的緩存版本的哈希不匹配,則將重新下載資產(chǎn)捆綁偿短。 創(chuàng)建一個(gè)優(yōu)化的UnityWebRequest欣孤,用于通過(guò)HTTP GET下載Unity資產(chǎn)捆綁包。
-
-
從AB中加載資源的方式
public <T> LoadAsset<T> (string name);
name:資源名稱
-
T:資源類型
該接口可以同步的加載資源
public AssetBundleRequest LoadAssetAsync(string name);
-
name:資源名稱
該接口可以異步的加載資源
Addressable加載方式
我們首先把資源標(biāo)記為可尋址昔逗,可尋址資源系統(tǒng)會(huì)給我們一個(gè)它的地址降传,然后我們可以根據(jù)這個(gè)地址去異步加載資源」磁可尋址資源系統(tǒng)的加載資源方式都是異步的婆排,所以我們需要監(jiān)聽(tīng)它加載完成再使用。
-
通過(guò)代碼編寫
public static AsyncOperationHandle <T> LoadAssetAsync<T>(object key);
- key:資源的地址
- AsyncOperationHandle :監(jiān)聽(tīng)異步操作的句柄
public static void InstantiateAsync(object key);
-
key:資源的地址
該接口可以直接通過(guò)地址來(lái)實(shí)例化物體
-
通過(guò)AssetReference 訪問(wèn)可尋址資產(chǎn)而不需要知道它的地址
-
我們?cè)诶^承自monobehaviour的腳步中添加一個(gè)成員:
public AssetReference ar;
-
然后在該腳本組件的視圖中就可以選擇可尋址資產(chǎn)了笔链。
這樣我們就無(wú)需知道該可尋址資源的地址也可以直接加載它段只。
//先加載后面自己控制實(shí)例化 ar.LoadAssetAsync<T>().Completed += LoadComplete; //直接實(shí)例化 ar.InstantiateAsync();
以上就是可尋址資源系統(tǒng)最簡(jiǎn)單的加載一個(gè)資源的方法。
-
-
我們也可以通過(guò)傳入好幾個(gè)地址或者標(biāo)簽來(lái)加載一系列資源鉴扫。在可尋址資源系統(tǒng)中我們可以定義一系列標(biāo)簽赞枕,然后再給可尋址資源貼上相應(yīng)標(biāo)簽,然后我們通過(guò)標(biāo)簽來(lái)加載資源時(shí)會(huì)直接加載所有貼上該標(biāo)簽的資源。**
public static AsyncOperationHandle<IList<T>> LoadAssetsAsync<T>(object key, Action<T> callback);
- 返回值 AsyncOperationHandle:異步句柄
- key:標(biāo)簽
- callback:每個(gè)資源被加載完成后會(huì)調(diào)用的回調(diào)
public static AsyncOperationHandle<IList<T>> LoadAssetsAsync<T>(IList<object> keys, Action<T> callback, MergeMode mode);
- 返回值 AsyncOperationHandle : 異步句柄
- keys:所有要加載的資源的地址
- callback:每個(gè)資源被加載完成后會(huì)調(diào)用的回調(diào)
- mode:最后返回的所有資源
卸載資源方式對(duì)比
AB卸載方式
該圖可以清晰的描述AB加載后的內(nèi)存分布和不同的卸載方式對(duì)應(yīng)卸載哪一些區(qū)域。
- 我們通過(guò)本地或者網(wǎng)絡(luò)加載后都會(huì)有一份內(nèi)存鏡像,我們需要通過(guò)它再來(lái)加載資源厚柳,如果我們調(diào)用了AssetBundle.Unload(false)那么內(nèi)存鏡像被釋放,我們也不能再通過(guò)這個(gè)ab包來(lái)加載資源项滑,但是之前加載的資源不會(huì)被釋放,我們可以通過(guò)Re'sources.UnloadAsset(obj)來(lái)釋放加載的資源涯贞,如果想要釋放所有從ab包加載的資源那么就需要調(diào)用AssetBundle.Unload(true)枪狂,如果這個(gè)時(shí)候還有其他資源引用了ab包加載的資源,那么就會(huì)造成引用丟失造成顯示不正常宋渔。
Addressable卸載方式
由于Addressable是在AB的基礎(chǔ)上使用的系統(tǒng)摘完,所以它的內(nèi)存分布跟AB差不多,但是由于我們不能拿到資源對(duì)應(yīng)的ab對(duì)象傻谁,只能通過(guò)Addressable給出的接口釋放資源孝治。Addressable內(nèi)部已經(jīng)給我們做了引用計(jì)數(shù)管理,所有當(dāng)我們釋放對(duì)應(yīng)資源時(shí)审磁,只有當(dāng)引用計(jì)數(shù)為0才會(huì)真正的卸載對(duì)應(yīng)ab對(duì)象谈飒。
-
不能實(shí)例化的資源卸載方式
1.public static void Release<T>(T obj);
- obj:資源
? 直接釋放T類型的資源,并且減少引用計(jì)數(shù)
2.public static void Release(AsyncOperationHandle handle);
- handle:句柄
? 釋放由該句柄加載出來(lái)的資源态蒂,并減少引用計(jì)數(shù)
-
能實(shí)例化的資源卸載方式
1.public static bool ReleaseInstance(GameObject instance);
-
instance:實(shí)例化的資源
銷毀游戲物體杭措,并且減少引用計(jì)數(shù)。如果用該方法卸載不是由尋址系統(tǒng)實(shí)例化的物體钾恢,那么釋放不會(huì)產(chǎn)生影響手素。
2.public static bool ReleaseInstance(AsyncOperationHandle handle);
-
handle:句柄
釋放由該句柄加載出來(lái)的資源,并減少引用計(jì)數(shù)
這邊需要注意一點(diǎn)瘩蚪,實(shí)例化資源時(shí)可以傳一個(gè)參數(shù)trackHandle默認(rèn)為true泉懦。如果他為true那么銷毀實(shí)例化的資源用上面2個(gè)接口都可以,如果為false那么只能用第二個(gè)接口疹瘦。
-
熱更資源方式對(duì)比
AB熱更資源方式
熱更資源在Addressable推出之前每個(gè)項(xiàng)目都有自己的做法崩哩,但是大致上都可以抽象為以下流程。
- 先給每一個(gè)ab記錄一個(gè)版本號(hào)言沐,這個(gè)版本號(hào)可以是md5邓嘹、hash值或者版本數(shù)字。但是最好是一個(gè)版本數(shù)字险胰,防止項(xiàng)目遷移之后資源計(jì)算出的md5或者h(yuǎn)ash發(fā)生改變從而導(dǎo)致重新下載一遍相同資源汹押。然后將所有包名對(duì)應(yīng)的版本號(hào)記錄在一個(gè)資源配置文件里。
- 第一次進(jìn)入游戲沒(méi)有這個(gè)資源配置文件起便,先下載資源配置文件棚贾,再把配置文件里的所有ab下載到本地窖维。
- 之后登陸游戲時(shí)對(duì)比本地資源配置文件里的資源版本號(hào)和遠(yuǎn)程資源配置文件里的資源版本號(hào),如果版本號(hào)小了鸟悴,那么記錄這個(gè)ab陈辱。最后把所有需要更新的ab下載并且更新本地文件奖年。
- 資源配置文件除了可以記錄版本细诸,還可以附加一些信息比如ab大小,所以我們下載時(shí)可以彈窗提示玩家需要更新多少大的文件陋守。
Addressable熱更資源方式
Addressable也支持資源熱更震贵,但是還是有很多不足的地方。我用了它的熱更功能然后看了它熱更功能的實(shí)現(xiàn)后發(fā)現(xiàn)它更加適合邊下邊玩水评,而不是在游戲剛進(jìn)去時(shí)更新完所有資源猩系。原因有以下幾點(diǎn):
- 因?yàn)槲覀儼严螺dab的實(shí)現(xiàn)交給了addressable,然后它的實(shí)現(xiàn)是當(dāng)你在加載資源時(shí)找到這個(gè)資源的ab包中燥,然后通過(guò)UnityWebRequestAssetBundle判斷該ab包是不是已經(jīng)下載如果下載那么直接從緩存目錄加載寇甸,不然就下載到緩存目錄再加載。所以我們要先加載資源才會(huì)去下載ab包疗涉。
- 目前沒(méi)有提供接口來(lái)知道那些ab包需要熱更拿霉。
雖然它本身不支持游戲初始化時(shí)下載所有資源,但是因?yàn)閍ddressable是開源的咱扣,我們可以直接修改源碼來(lái)達(dá)到我們自己想要的效果绽淘。比如我們想在游戲剛開始得到所有需要更新ab的大小,我們可以通過(guò)以下代碼來(lái)實(shí)現(xiàn)
AddressablesImpl.cs
//添加2個(gè)新方法
//初始化完之后調(diào)用GetRemoteBundleSizeAsync方法
AsyncOperationHandle<long> GetRemoteBundleSizeWithChain(IList<string> bundles)
{
return ResourceManager.CreateChainOperation(InitializationOperation, op => GetRemoteBundleSizeAsync(key));
}
//通過(guò)ab包名得到ab包大小
public AsyncOperationHandle<long> GetRemoteBundleSizeAsync(IList<string> bundles)
{
//如果還沒(méi)初始化完那么等待初始化完
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];
//尋找傳入的包名對(duì)應(yīng)的ab包闹伪,如果沒(méi)找到那么警告
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)
{
//計(jì)算包大小
size += sizeData.ComputeSize(locations[i]);
}
}
}
//返回總的包大小
return ResourceManager.CreateCompletedOperation<Long>(size, string.Empty)
}
//在對(duì)應(yīng)的Addressables外觀類里也添加GetRemoteBundleSizeAsync方法
//使用方法
//在addressable初始化完成后遍歷所有地址沪铭,如果地址的結(jié)尾是.bundle,那么他對(duì)應(yīng)了一個(gè)ab包偏瓤,然后把它緩存到列表杀怠,再使用添加的接口來(lái)獲得所有需要更新包的大小。
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);
};