一.問題
首先,這裡說明一下,我這邊的GameObject有點籠統,就是表達的是遊戲中的具體執行個體。

二.概念
1)Asset是什麼?
遊戲中具體的資源,像texture,mesh,material,shader,script等,實實在在的遊戲項目檔案夾中所需要堆放的資源。比如,var obj = Resource.Load<GameObject>("Prefabs/testItem"),這個obj就是Asset。
2)GameObject是什麼?
var gameItem= Instantiate(obj),這個gameItem就是可以存在于遊戲的實際場景中(這個比較簡單,不多說了)。GameObject是遊戲中實際使用的對象(就是你會在螢幕中實際看到的),是由Asset執行個體化後的對象。本質上其實還是Asset的衍變,是對部分Asset的引用和複制出來的新東西,其本質還是Asset。
3)AssetBundle是什麼?
由上述可知,我們在遊戲中生成實際的物體,需要Asset。而Asset,比如一張圖檔,也是一個Asset,實際大小1M,遊戲中這種圖檔很多,那就輕輕松松幾百M,幾個G的Asset,都是很有可能的。作為程式員,對于這種“原汁原味”拿過來肯定不行。比如我們工作中把檔案什麼的發給同僚的時候都知道壓縮一下,可以傳輸的過程中小一些。當然了,我們在遊戲開發中使用Asset,也是需要類似的。于是,就推出了AssetBundle這一概念。當然我們推出AssetBundle,遠不止壓縮這一需求。但是你需要知道,主要是為了更好的傳輸,還有比如減少資源大小,利于網絡那邊的傳輸,友善加載。
簡而言之,AssetBundle就是為了讓遊戲項目中大量Asset适應實際遊戲運作時而被壓縮後的一種二進制檔案。
三.分析
1)Asset和GameObject的關系?
①複制+引用關系
Instaniate一個Prefab(Asset),是對Asset進行clone(複制)+引用結合的過程。GameObject,transform是clone的。其他mesh/texture/material/shader等,這些都是純引用的。引用的Asset對象不會被複制,隻是一個簡單的指針指向已經load的Asset對象。專門要提一下Script Asset,Unity裡每個Script都是一個封閉的class定義,并沒有寫調用代碼。光class是不會工作的。其實Unity引擎就是那個調用代碼。clone一個script asset等于new一個class執行個體,執行個體才會工作 ,把它挂到Unity主線程的調用鍊去,class執行個體裡的update和start才會被執行。多個物體挂同一個腳本,其實就是多個物體挂了挂了那個腳本的類的執行個體。在new class過程中,資料區是複制的,代碼區是共享的,算是一種複制+引用關系。引用關系的話,會有一對一,一對多,多對一的關系。如下圖:
②釋放
如果你Destroy(GameObject,這個是遊戲中具有執行個體),隻是釋放了clone的asset,引用的asset并不會被幹掉。還有因為destroy并不知道有沒有被别的asset引用。但是如果你想把asset也釋放,有兩種方案。
I. Resources.UnloadUnusedAssets()
釋放目前所有的沒有被引用的(無用)asset,但是不能保證釋放掉目前的被引用的資源(因為可能還被其他資源引用,就不會去釋放了)。缺點:異步,會卡。由于Unity資源的互相引用關系比較複雜,想要明确判斷某一資源不存在引用關系是有一定難度的,并且,如果我 們想要釋放的資源存在隐形的引用關系,UnloadUnUsedAssets将會無視這個資源而無任何回報。根據實戰來看,最佳使用的時機是在場景切換進入新的場景後,Unity場景關閉會有效的銷毀所有的對象和所有代碼的引用,即在新場景開頭最為穩妥。必要時加上GC.Collect().
II.Resouce.UnLoad(obj)
釋放目前執行個體的所有被引用的asset(不管這個asset是否還有被引用,是以,風險很大,除非保證确定被他引用的資源沒有再被其他資源引用,一般用于單獨的一張紋理釋放)缺點:風險太大,容易被報:UnloadAsset may only be used on individual assets and can not be used on GameObject's / Components or AssetBundles,不能用作解除安裝GameObject,隻能用于紋理釋放。不然會報這個錯,我也不知道為啥,有知道的告我一下,額,還有就是這個方法很少用,應用的話,也隻是對紋理釋放。
對于這種使用,對于資源大的且無引用的可以使用,如果資源消耗不大,可以等到場景切換,使用I中 Resources.unloadUnusedAssets方案。
III.GameObject.DestroyImmediate(asset,true)
代替上述方案,可以針對解除安裝asset(好像還可以直接使用GameObject.Destroy(asset) ,也可以直接使用。(待驗證))
IV.AssetBundle.unload(true) !!!慎用
這個也可以解除安裝asset,但是解除安裝的是這個assetBundle裡面的所有的asset。(後面詳說)
2)AssetBundle和Asset的關系
①包含,依賴關系
一個AssetBundle中可以包含一個或多個Asset。一個Asset依賴于AssetBundle。
②釋放
AssetBundle的釋放隻能通過以下兩種方式釋放。即使系統在加載新場景的時候所有的記憶體對象都會被自動銷毀,包括你用AssetBundle.load加載的對象和Instaniate克隆的,但是不包括AssetBundle檔案本身的記憶體鏡像,那個必須要用Unload來釋放,用.Net術語說該資源是非托管的。
I.AssetBundle.Unload(false) 使用頻率較多
用AssetBundle.Load加載需要的asset之後應該立即使用unlaod(false),釋放assetbundle檔案本身的記憶體鏡像,但不銷毀該assetBundle加載過的asset對象。(盡量釋放一部分記憶體,大多數遊戲這麼做)
II.AssetBundle.Unload(true)
釋放該assetBundle檔案鏡像并釋放該assetBundle所有loaded的asset記憶體對象。(風險很大,因為一般不太能确定是否該loaded的asset是否還被其他資源引用)。
四.執行個體測試
AssetBundle和Asset 項目工程中大小分析
①首先,準備10個一樣大小的texture(為了測試結果更加準備,每個圖1.33M)
texture 的原大小,即Asset本身的大小:1.33M *10 = 13.3M
②LZ4和LZMA打包方式案例對比
I.BuildAssetBundleOptions.None 打包方式,該為預設壓縮,即LZMA,在使用AssetBundle之前需要解壓縮。使用LAMA格式壓縮的AssetBundle的包體最小
(高壓縮比),但是會增加相應的解壓縮時間和記憶體。
BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None,BuildTarget.StandaloneWindows64);
以上述方式打包完之後 每張 texture :613K *10 = 6M
執行下面代碼。(為友善資料對比,是在找不到大的texture了)
使用AssetBundle.LoadFromFile 加載那10個AssetBundle之後的記憶體顯示
對比上2張圖的消耗,大概消耗了13.6M的Unity記憶體,0.5M其他記憶體。因為是十張紋理,原紋理沒有打包之前的大小大概是1.33m,也就是說,大概技術釋放的是原Asset的大小。
再Load其中的Asset
由上圖可知,申請的是Unity記憶體爆增100M左右,Mono的記憶體也相應的增加了。
II.ChunkBasedCompression,即LZ4壓縮方式,壓縮比一般,壓縮後的包體較大,但是解壓速度快,消耗記憶體小。
BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.ChunkBasedCompression,BuildTarget.StandaloneWindows64);
以上述方式打包完之後 每張texture :1110K*10=11100K,11M左右
再執行同上一緻的Load腳本.
AssetBundle,Asset,GameObject 加載中的記憶體對比
同上述步驟一緻,給出LoadAssetBundle,即AssetBundle.LoadFromFile後的記憶體大小
大概消耗2M記憶體。(上述LAMA方式打出的包是23.5M,相差10倍啊,果斷使用這種方式,我們項目也是這種方式)
再給出LoadAsset之後的記憶體圖
LoadAsset之後,Unity記憶體,Mono記憶體都增加很多
III.總結對比,I加載的時候LoadAssetBundle消耗明顯大于II方式10倍(甚至不止,因為總量越大,差距就越大)。移動遊戲中的記憶體多麼珍貴大家懂得。使用ChunkBasedCompression,即LZ4壓縮,更為劃算。因為甯願犧牲一點包體大小,也要消耗記憶體小一些。(我們遊戲項目是這個需求,具體還是看項目吧)
當然還有其他打包選項,各有利弊,具體看遊戲實際情況需求。我這裡隻是對比出了告訴你打包選項會決定你的打出的資源包體大小和遊戲中加載消耗記憶體。很重要。
具體打包選項BuildAssetBundleOptions參考:https://blog.csdn.net/AnYuanLzh/article/details/81485762
補充:由上,我們知道打包時壓縮方式會導緻打出的包體和加載時的消耗都不同。加載時候的加載方式我們這邊使用AssetBundle.CreateFromFile直接加載AssetBundle,Unity其實還提供了WWW加載AssetBundle的方式。但是這裡不作詳述了(講不完...)。
五.總結
由上可知,文章雖說AB,Asset,GameObject三者聯系,但是GameObject主要是由Asset執行個體化而來,GameObject是Asset的引用和複制的關系(主要引用),這個也可以說是Asset的一種。問題也就可以簡化為AB和Asset之間的關系。
由上圖,再總結一下,打包方式不同,加載方式不同,造成的消耗不同。即,如果想優化遊戲中的資源,需要注意打包AB的方式,以及加載AB的方式。當然,還要注意AB的解除安裝,和Asset的解除安裝。
六.參考
關于加載AssetBundle和加載Asset的差別,詳情就不多說了,見UWA:https://blog.uwa4d.com/archives/ABTheory.html
打包加密壓縮算法差別參考:https://www.cnblogs.com/murongxiaopifu/p/5629415.html#autoid-3-3-0
附:測試Demo源碼:連結:https://pan.baidu.com/s/1ZHPoQbuxgdUVh9PGbz6ArA 提取碼:7lkv