Unity加載和內(nèi)存管理

Babybus-u3d技術(shù)交流-Unity加載和內(nèi)存管理

[unity
里有兩種動態(tài)加載機制:一是Resources.Load偷拔,一是通過AssetBundle,其實兩者本質(zhì)上我理解沒有什么區(qū)別。Resources.Load就是從一個缺省打進程序包里的AssetBundle里加載資源亏钩,而一般AssetBundle文件需要你自己創(chuàng)建莲绰,運行時動態(tài)加載,可以指定路徑和來源的姑丑。

其實場景里所有靜態(tài)的對象也有這么一個加載過程蛤签,只是Unity后臺替你自動完成了。

詳細說一下細節(jié)概念:

AssetBundle運行時加載:

來自文件就用CreateFromFile(注意這種方法只能用于standalone程序)這是最快的加載方法

也可以來自Memory,用CreateFromMemory(byte[]),這個byte[]可以來自文件讀取的緩沖栅哀,www的下載或者其他可能的方式震肮。

其實[WWW

的assetBundle就是內(nèi)部數(shù)據(jù)讀取完后自動創(chuàng)建了一個assetBundle而已

Create完以后称龙,等于把硬盤或者網(wǎng)絡(luò)的一個文件讀到內(nèi)存一個區(qū)域,這時候只是個AssetBundle內(nèi)存鏡像數(shù)據(jù)塊戳晌,還沒有Assets的概念鲫尊。

Assets加載:

用AssetBundle.Load(同Resources.Load) 這才會從AssetBundle的內(nèi)存鏡像里讀取并創(chuàng)建一個Asset對象,創(chuàng)建Asset對象同時也會分配相應(yīng)內(nèi)存用于存放(反序列化)

異步讀取用AssetBundle.LoadAsync

也可以一次讀取多個用AssetBundle.LoadAll

AssetBundle的釋放:

AssetBundle.Unload(flase)是釋放AssetBundle文件的內(nèi)存鏡像沦偎,不包含Load創(chuàng)建的Asset內(nèi)存對象疫向。

AssetBundle.Unload(true)是釋放那個AssetBundle文件內(nèi)存鏡像和并銷毀所有用Load創(chuàng)建的Asset內(nèi)存對象。

一個Prefab從assetBundle里Load出來 里面可能包括:Gameobject transform mesh texture material shader script和各種其他Assets豪嚎。

你Instantiate一個Prefab搔驼,是一個對Assets進行Clone(復(fù)制)+引用結(jié)合的過程,GameObject transform 是Clone是新生成的疙渣。其他mesh / texture / material / shader 等匙奴,這其中有些是純引用的關(guān)系的,包括:Texture
和TerrainData
妄荔,還有引用和復(fù)制同時存在的泼菌,包括:Mesh/material/PhysicMaterial
。引用的Asset對象不會被復(fù)制啦租,只是一個簡單的指針指向已經(jīng)Load的Asset對象哗伯。

這種含糊的引用加克隆的混合,大概是搞糊涂大多數(shù)人的主要原因篷角。

專門要提一下的是一個特殊的東西:Script Asset

焊刹,看起來很奇怪,Unity里每個Script都是一個封閉的Class定義而已,并沒有寫調(diào)用代碼恳蹲,光Class的定義腳本是不會工作的虐块。其實Unity引擎就是那個調(diào)用代碼,Clone一個script asset等于new一個class實例嘉蕾,

實例才會完成工作贺奠。通過AddComponent給物體添加一個Script Assets,就完成了

把腳本類實例掛到Unity主線程的調(diào)用鏈里去的工作错忱,Class實例里的OnUpdate OnStart等才會被執(zhí)行儡率。多個物體掛同一個腳本,其實就是在多個物體上掛了那個腳本類的多個實例而已以清,這樣就好理解了儿普。在new class這個過程中,數(shù)據(jù)區(qū)是復(fù)制的掷倔,代碼區(qū)是共享的眉孩,算是一種特殊的復(fù)制+引用關(guān)系。

你可以再Instantiate一個同樣的Prefab,還是這套mesh/texture/material/shader...,這時候會有新的GameObject等浪汪,但是不會創(chuàng)建新的引用對象比如Texture.

所以你Load出來的Assets其實就是個數(shù)據(jù)源胸囱,用于生成新對象或者被引用缕陕,生成的過程可能是復(fù)制(clone)也可能是引用(指針)

當你Destroy一個實例時俄讹,只是釋放那些Clone對象帽芽,并不會釋放引用對象和Clone的數(shù)據(jù)源對象,Destroy并不知道是否還有別的object在引用那些對象殃姓。

等到?jīng)]有任何游戲場景物體在用這些Assets以后袁波,這些assets就成了沒有引用的游離數(shù)據(jù)塊了,是UnusedAssets了蜗侈,這時候就可以通過Resources.UnloadUnusedAssets來釋放,Destroy不能完成這個任務(wù)篷牌,AssetBundle.Unload(false)也不行,AssetBundle.Unload(true)可以但不安全踏幻,除非你很清楚沒有任何對象在用這些Assets了枷颊。

配個圖加深理解:

Unity加載和內(nèi)存管理.jpg

雖然都叫Asset,但復(fù)制的和引用的是不一樣的该面,這點被Unity的暗黑技術(shù)細節(jié)掩蓋了夭苗,需要自己去理解。

關(guān)于內(nèi)存管理

按照傳統(tǒng)的編程思維隔缀,最好的方法是:自己維護所有對象题造,用一個Queue來保存所有object,不用時該Destory的,該Unload的自己處理猾瘸。

但這樣在C# .net框架底下有點沒必要界赔,而且很麻煩。

穩(wěn)妥起見你可以這樣管理

創(chuàng)建時:

先建立一個AssetBundle,無論是從www還是文件還是memory

用AssetBundle.load加載需要的asset

加載完后立即AssetBundle.Unload(false),釋放AssetBundle文件本身的內(nèi)存鏡像牵触,但不銷毀加載的Asset對象淮悼。(這樣你不用保存AssetBundle的引用并且可以立即釋放一部分內(nèi)存)

釋放時:

如果有Instantiate的對象,用Destroy進行銷毀

在合適的地方調(diào)用Resources.UnloadUnusedAssets,釋放已經(jīng)沒有引用的Asset.

如果需要立即釋放內(nèi)存加上GC.Collect()揽思,否則內(nèi)存未必會立即被釋放袜腥,有時候可能導(dǎo)致內(nèi)存占用過多而引發(fā)異常。

這樣可以保證內(nèi)存始終被及時釋放绰更,占用量最少瞧挤。也不需要對每個加載的對象進行引用锡宋。

當然這并不是唯一的方法儡湾,只要遵循加載和釋放的原理,任何做法都是可以的执俩。

系統(tǒng)在加載新場景時徐钠,所有的內(nèi)存對象都會被自動銷毀,包括你用AssetBundle.Load加載的對象和Instaniate克隆的役首。但是不包括AssetBundle文件自身的內(nèi)存鏡像尝丐,那個必須要用Unload來釋放显拜,用.net的術(shù)語,這種數(shù)據(jù)緩存是非托管的爹袁。

總結(jié)一下各種加載和初始化的用法:

AssetBundle.CreateFrom.....:創(chuàng)建一個AssetBundle內(nèi)存鏡像远荠,注意同一個assetBundle文件在沒有Unload之前不能再次被使用

WWW.AssetBundle:同上,當然要先new一個再 yield return 然后才能使用

AssetBundle.Load(name):從AssetBundle讀取一個指定名稱的Asset并生成Asset內(nèi)存對象失息,如果多次Load同名對象譬淳,除第一次外都只會返回已經(jīng)生成的Asset對象,也就是說多次Load一個Asset并不會生成多個副本(
singleton)

盹兢。

Resources.Load(path;name):同上,只是從默認的位置加載邻梆。

Instantiate(object):Clone一個object的完整結(jié)構(gòu),包括其所有Component和子物體(詳見官方文檔),淺Copy绎秒,并不復(fù)制所有引用類型浦妄。有個特別用法,雖然很少這樣用见芹,其實可以用Instantiate來完整的拷貝一個引用類型的Asset,比如Texture等剂娄,要拷貝的Texture必須類型設(shè)置為Read/Write able。

總結(jié)一下各種釋放

Destroy:主要用于銷毀克隆對象玄呛,也可以用于場景內(nèi)的靜態(tài)物體宜咒,不會自動釋放該對象的所有引用。雖然也可以用于Asset,但是概念不一樣要小心把鉴,如果用于銷毀從文件加載的Asset對象會銷毀相應(yīng)的資源文件故黑!但是如果銷毀的Asset是Copy的或者用腳本動態(tài)生成的,只會銷毀內(nèi)存對象庭砍。

AssetBundle.Unload(false):釋放AssetBundle文件內(nèi)存鏡像

AssetBundle.Unload(true):釋放AssetBundle文件內(nèi)存鏡像同時銷毀所有已經(jīng)Load的Assets內(nèi)存對象

Reources.UnloadAsset(Object):顯式的釋放已加載的Asset對象场晶,只能卸載磁盤文件加載的Asset對象

Resources.UnloadUnusedAssets:用于釋放所有沒有引用的Asset對象

GC.Collect()強制垃圾收集器立即釋放內(nèi)存 Unity的GC功能不算好,沒把握的時候就強制調(diào)用一下

在3.5.2之前好像Unity不能顯式的釋放Asset

舉兩個例子幫助理解

例子1:

一個常見的錯誤:

你從某個AssetBundle里Load了一個prefab并克隆之:obj = Instantiate(AssetBundle1.Load('MyPrefab”);

這個prefab比如是個npc

然后你不需要他的時候你用了:Destroy(obj);你以為就釋放干凈了

其實這時候只是釋放了Clone對象怠缸,通過Load加載的所有引用诗轻、非引用Assets對象全都靜靜靜的躺在內(nèi)存里。

這種情況應(yīng)該在Destroy以后用:AssetBundle1.Unload(true)揭北,徹底釋放干凈扳炬。

如果這個AssetBundle1是要反復(fù)讀取的 不方便Unload,那可以在Destroy以后用:Resources.UnloadUnusedAssets()把所有和這個npc有關(guān)的Asset都銷毀搔体。

當然如果這個NPC也是要頻繁創(chuàng)建 銷毀的 那就應(yīng)該讓那些Assets呆在內(nèi)存里以加速游戲體驗恨樟。

由此可以解釋另一個之前有人提過的[話題
:為什么
第一次
Instantiate一個Prefab的時候都會卡一下,因為在你第一次Instantiate之前疚俱,相應(yīng)的Asset對象還沒有被創(chuàng)建劝术,要加載系統(tǒng)內(nèi)置的AssetBundle并創(chuàng)建Assets,第一次以后你雖然Destroy了,但Prefab的Assets對象都還在內(nèi)存里,所以就很快了养晋。

例子2:

從磁盤讀取一個1.[unity3d
文件到內(nèi)存并建立一個AssetBundle1對象

AssetBundle AssetBundle1 = AssetBundle.CreateFromFile("1.unity3d");

從AssetBundle1里讀取并創(chuàng)建一個Texture Asset,把obj1的主貼圖指向它

obj1.renderer.material.mainTexture = AssetBundle1.Load("wall") as Texture;

把obj2的主貼圖也指向同一個Texture Asset

obj2.renderer.material.mainTexture =

obj1.renderer.material.mainTexture;

Texture是引用對象衬吆,永遠不會有自動復(fù)制的情況出現(xiàn)(除非你真需要,用代碼自己實現(xiàn)copy)绳泉,只會是創(chuàng)建和添加引用

如果繼續(xù):

AssetBundle1.Unload(true) 那obj1和obj2都變成黑的了逊抡,因為指向的Texture Asset沒了

如果:

AssetBundle1.Unload(false) 那obj1和obj2不變,只是AssetBundle1的內(nèi)存鏡像釋放了

繼續(xù):

Destroy(obj1),//obj1被釋放零酪,但并不會釋放剛才Load的Texture

如果這時候:

Resources.UnloadUnusedAssets();

不會有任何內(nèi)存釋放 因為Texture asset還被obj2用著

如果

Destroy(obj2)

obj2被釋放秦忿,但也不會釋放剛才Load的Texture

繼續(xù)

Resources.UnloadUnusedAssets();

這時候剛才load的Texture Asset釋放了,因為沒有任何引用了

最后CG.Collect();

強制立即釋放內(nèi)存

由此可以引申出論壇里另一個被提了幾次的[問題
蛾娶,如何加載一堆大圖片輪流顯示又不爆掉

不考慮AssetBundle灯谣,直接用www讀圖片文件的話等于是直接創(chuàng)建了一個Texture Asset

假設(shè)文件保存在一個List里

TLlist<string> fileList;
int n=0;IEnumerator OnClick()
{

WWW image = new www(fileList[n++]);
yield return image;
obj.mainTexture = image.texture;
n = (n>=fileList.Length-1)?0:n;
Resources.UnloadUnusedAssets();
}

這樣可以保證內(nèi)存里始終只有一個巨型Texture Asset資源蛔琅,也不用代碼追蹤上一個加載的Texture Asset,但是速度比較慢

或者:

IEnumerator OnClick()

{
WWW image = new www(fileList[n++])胎许;
yield return image;
Texture tex = obj.mainTexture;
obj.mainTexture = image.texture;
n = (n>=fileList.Length-1)?0:n;
Resources.UnloadAsset(tex);

}

這樣卸載比較快

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市罗售,隨后出現(xiàn)的幾起案子辜窑,更是在濱河造成了極大的恐慌,老刑警劉巖寨躁,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穆碎,死亡現(xiàn)場離奇詭異,居然都是意外死亡职恳,警方通過查閱死者的電腦和手機所禀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來放钦,“玉大人色徘,你說我怎么就攤上這事〔儋鳎” “怎么了褂策?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颓屑。 經(jīng)常有香客問我斤寂,道長,這世上最難降的妖魔是什么揪惦? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任遍搞,我火速辦了婚禮,結(jié)果婚禮上丹擎,老公的妹妹穿的比我還像新娘尾抑。我一直安慰自己,他們只是感情好蒂培,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布再愈。 她就那樣靜靜地躺著,像睡著了一般护戳。 火紅的嫁衣襯著肌膚如雪翎冲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天媳荒,我揣著相機與錄音抗悍,去河邊找鬼。 笑死钳枕,一個胖子當著我的面吹牛缴渊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鱼炒,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼衔沼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昔瞧?” 一聲冷哼從身側(cè)響起指蚁,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎自晰,沒想到半個月后凝化,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡酬荞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年搓劫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混巧。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡糟把,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牲剃,到底是詐尸還是另有隱情遣疯,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布凿傅,位于F島的核電站缠犀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏聪舒。R本人自食惡果不足惜辨液,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箱残。 院中可真熱鬧滔迈,春花似錦止吁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谈山,卻和暖如春俄删,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奏路。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工畴椰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸽粉。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓斜脂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親触机。 傳聞我的和親對象是個殘疾皇子秽褒,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容