? ? ? ?提Unity3D項目優(yōu)化則必提DrawCall,這自然沒錯,但也有很不好影響。因為這會給人一個錯誤的認(rèn)識:所謂的優(yōu)化就是把DrawCall弄的比較低就對了。
對優(yōu)化有這種第一印象的人不在少數(shù),drawcall的確是一個很重要的指標(biāo),但絕非全部。為了讓各位和匹夫能達成盡可能多的共識,匹夫首先介紹一下本文可能會涉及到的幾個概念,之后會提出優(yōu)化所涉及的三大方面:
drawcall是啥?其實就是對底層圖形程序(比如:OpenGL ES)接口的調(diào)用,以在屏幕上畫出東西。所以,是誰去調(diào)用這些接口呢?CPU。
fragment是啥?經(jīng)常有人說vf啥的,vertex我們都知道是頂點,那fragment是啥呢?說它之前需要先說一下像素,像素各位應(yīng)該都知道吧?像素是構(gòu)成數(shù)碼影像的基本單元呀。那fragment呢?是有可能成為像素的東西。啥叫有可能?就是最終會不會被畫出來不一定,是潛在的像素。這會涉及到誰呢?GPU。
batching是啥?都知道批處理是干嘛的吧?沒錯,將批處理之前需要很多次調(diào)用(drawcall)的物體合并,之后只需要調(diào)用一次底層圖形程序的接口就行。聽上去這簡直就是優(yōu)化的終極方案啊!但是,理想是美好的,世界是殘酷的,一些不足之后我們再細(xì)聊。
內(nèi)存的分配:記住,除了Unity3D自己的內(nèi)存損耗。我們可是還帶著Mono呢啊,還有托管的那一套東西呢。更別說你一激動,又引入了自己的幾個dll。這些都是內(nèi)存開銷上需要考慮到的。
好啦,文中的幾個概念提前講清楚了,其實各位也能看的出來匹夫接下來要說的匹夫關(guān)注的優(yōu)化時需要注意的方面:
CPU方面
GPU方面
內(nèi)存方面
所以,這篇文章也會按照CPU—->GPU—->內(nèi)存的順序進行。
CPU的方面的優(yōu)化:
上文中說了,drawcall影響的是CPU的效率,而且也是最知名的一個優(yōu)化點。但是除了drawcall之外,還有哪些因素也會影響到CPU的效率呢?讓我們一一列出暫時能想得到的:
DrawCalls
物理組件(Physics)
GC(什么?GC不是處理內(nèi)存問題的嘛?匹夫你不要騙我??!不過,匹夫也要提醒一句,GC是用來處理內(nèi)存的,但是是誰使用GC去處理內(nèi)存的呢?)
當(dāng)然,還有代碼質(zhì)量
DrawCalls:
前面說過了,DrawCall是CPU調(diào)用底層圖形接口。比如有上千個物體,每一個的渲染都需要去調(diào)用一次底層接口,而每一次的調(diào)用CPU都需要做很多工作,那么CPU必然不堪重負(fù)。但是對于GPU來說,圖形處理的工作量是一樣的。所以對DrawCall的優(yōu)化,主要就是為了盡量解放CPU在調(diào)用圖形接口上的開銷。所以針對drawcall我們主要的思路就是每個物體盡量減少渲染次數(shù),多個物體最好一起渲染。所以,按照這個思路就有了以下幾個方案:
使用Draw Call Batching,也就是描繪調(diào)用批處理。Unity在運行時可以將一些物體進行合并,從而用一個描繪調(diào)用來渲染他們。具體下面會介紹。
通過把紋理打包成圖集來盡量減少材質(zhì)的使用。
盡量少的使用反光啦,陰影啦之類的,因為那會使物體多次渲染。
Draw Call Batching
首先我們要先理解為何2個沒有使用相同材質(zhì)的物體即使使用批處理,也無法實現(xiàn)Draw Call數(shù)量的下降和性能上的提升。
因為被“批處理”的2個物體的網(wǎng)格模型需要使用相同材質(zhì)的目的,在于其紋理是相同的,這樣才可以實現(xiàn)同時渲染的目的。因而保證材質(zhì)相同,是為了保證被渲染的紋理相同。
因此,為了將2個紋理不同的材質(zhì)合二為一,我們就需要進行上面列出的第二步,將紋理打包成圖集。具體到合二為一這種情況,就是將2個紋理合成一個紋理。這樣我們就可以只用一個材質(zhì)來代替之前的2個材質(zhì)了。
而Draw Call Batching本身,也還會細(xì)分為2種。
Static Batching 靜態(tài)批處理
看名字,猜使用的情景。
靜態(tài)?那就是不動的咯。還有呢?額,聽上去狀態(tài)也不會改變,沒有“生命”,比如山山石石,樓房校舍啥的。那和什么比較類似呢?嗯,聰明的各位一定覺得和場景的屬性很像吧!所以我們的場景似乎就可以采用這種方式來減少draw call了。
那么寫個定義:只要這些物體不移動,并且擁有相同的材質(zhì),靜態(tài)批處理就允許引擎對任意大小的幾何物體進行批處理操作來降低描繪調(diào)用。
那要如何使用靜態(tài)批來減少Draw Call呢?你只需要明確指出哪些物體是靜止的,并且在游戲中永遠不會移動、旋轉(zhuǎn)和縮放。想完成這一步,你只需要在檢測器(Inspector)中將Static復(fù)選框打勾即可,如下圖所示:

至于效果如何呢?
舉個例子:新建4個物體,分別是Cube,Sphere, Capsule, Cylinder,它們有不同的網(wǎng)格模型,但是也有相同的材質(zhì)(Default-Diffuse)。
首先,我們不指定它們是static的。Draw Call的次數(shù)是4次,如圖:

我們現(xiàn)在將它們4個物體都設(shè)為static,在來運行一下:

如圖,Draw Call的次數(shù)變成了1,而Saved by batching的次數(shù)變成了3。
靜態(tài)批處理的好處很多,其中之一就是與下面要說的動態(tài)批處理相比,約束要少很多。所以一般推薦的是draw call的靜態(tài)批處理來減少draw call的次數(shù)。那么接下來,我們就繼續(xù)聊聊draw call的動態(tài)批處理。
Dynamic Batching 動態(tài)批處理
有陰就有陽,有靜就有動,所以聊完了靜態(tài)批處理,肯定跟著就要說說動態(tài)批處理了。首先要明確一點,Unity3D的draw call動態(tài)批處理機制是引擎自動進行的,無需像靜態(tài)批處理那樣手動設(shè)置static。我們舉一個動態(tài)實例化prefab的例子,如果動態(tài)物體共享相同的材質(zhì),則引擎會自動對draw call優(yōu)化,也就是使用批處理。首先,我們將一個cube做成prefab,然后再實例化500次,看看draw call的數(shù)量。
for(inti =0; i <500; i++)
{
GameObject cube;
cube = GameObject.Instantiate(prefab)asGameObject;
}
draw call的數(shù)量:

可以看到draw call的數(shù)量為1,而 saved by batching的數(shù)量是499。而這個過程中,我們除了實例化創(chuàng)建物體之外什么都沒做。不錯,unity3d引擎為我們自動處理了這種情況。
但是有很多童靴也遇到這種情況,就是我也是從prefab實例化創(chuàng)建的物體,為何我的draw call依然很高呢?這就是匹夫上文說的,draw call的動態(tài)批處理存在著很多約束。下面匹夫就演示一下,針對cube這樣一個簡單的物體的創(chuàng)建,如果稍有不慎就會造成draw call飛漲的情況吧。
我們同樣是創(chuàng)建500個物體,不同的是其中的100個物體,每個物體的大小都不同,也就是Scale不同。
for(inti =0; i <500; i++)
{
GameObject cube;
cube = GameObject.Instantiate(prefab)asGameObject;
if(i /100==0)
{
cube.transform.localScale =newVector3(2+ i,2+ i,2+ i);
}
}
draw call的數(shù)量:

我們看到draw call的數(shù)量上升到了101次,而saved by batching的數(shù)量也下降到了399。各位看官可以看到,僅僅是一個簡單的cube的創(chuàng)建,如果scale不同,竟然也不會去做批處理優(yōu)化。這僅僅是動態(tài)批處理機制的一種約束,那我們總結(jié)一下動態(tài)批處理的約束,各位也許也能從中找到為何動態(tài)批處理在自己的項目中不起作用的原因:
批處理動態(tài)物體需要在每個頂點上進行一定的開銷,所以動態(tài)批處理僅支持小于900頂點的網(wǎng)格物體。
如果你的著色器使用頂點位置,法線和UV值三種屬性,那么你只能批處理300頂點以下的物體;如果你的著色器需要使用頂點位置,法線,UV0,UV1和切向量,那你只能批處理180頂點以下的物體。
不要使用縮放。分別擁有縮放大小(1,1,1) 和(2,2,2)的兩個物體將不會進行批處理。
統(tǒng)一縮放的物體不會與非統(tǒng)一縮放的物體進行批處理。
使用縮放尺度(1,1,1) 和 (1,2,1)的兩個物體將不會進行批處理,但是使用縮放尺度(1,2,1) 和(1,3,1)的兩個物體將可以進行批處理。
使用不同材質(zhì)的實例化物體(instance)將會導(dǎo)致批處理失敗。
擁有l(wèi)ightmap的物體含有額外(隱藏)的材質(zhì)屬性,比如:lightmap的偏移和縮放系數(shù)等。所以,擁有l(wèi)ightmap的物體將不會進行批處理(除非他們指向lightmap的同一部分)。
多通道的shader會妨礙批處理操作。比如,幾乎unity中所有的著色器在前向渲染中都支持多個光源,并為它們有效地開辟多個通道。
預(yù)設(shè)體的實例會自動地使用相同的網(wǎng)格模型和材質(zhì)。
所以,盡量使用靜態(tài)的批處理。
物理組件
曾幾何時,匹夫在做一個策略類游戲的時候需要在單元格上排兵布陣,而要偵測到哪個兵站在哪個格子匹夫選擇使用了射線,由于士兵單位很多,而且為了精確每一幀都會執(zhí)行檢測,那時候CPU的負(fù)擔(dān)叫一個慘不忍睹。后來匹夫果斷放棄了這種做法,并且對物理組件產(chǎn)生了心理的陰影。
這里匹夫只提2點匹夫感覺比較重要的優(yōu)化措施:
1.設(shè)置一個合適的Fixed Timestep。設(shè)置的位置如圖:

那何謂“合適”呢?首先我們要搞明白Fixed Timestep和物理組件的關(guān)系。物理組件,或者說游戲中模擬各種物理效果的組件,最重要的是什么呢?計算啊。對,需要通過計算才能將真實的物理效果展現(xiàn)在虛擬的游戲中。那么Fixed Timestep這貨就是和物理計算有關(guān)的啦。所以,若計算的頻率太高,自然會影響到CPU的開銷。同時,若計算頻率達不到游戲設(shè)計時的要求,有會影響到功能的實現(xiàn),所以如何抉擇需要各位具體分析,選擇一個合適的值。
2.就是不要使用網(wǎng)格碰撞器(mesh collider):為啥?因為實在是太復(fù)雜了。網(wǎng)格碰撞器利用一個網(wǎng)格資源并在其上構(gòu)建碰撞器。對于復(fù)雜網(wǎng)狀模型上的碰撞檢測,它要比應(yīng)用原型碰撞器精確的多。標(biāo)記為凸起的(Convex )的網(wǎng)格碰撞器才能夠和其他網(wǎng)格碰撞器發(fā)生碰撞。各位上網(wǎng)搜一下mesh collider的圖片,自然就會明白了。我們的手機游戲自然無需這種性價比不高的東西。
當(dāng)然,從性能優(yōu)化的角度考慮,物理組件能少用還是少用為好。
處理內(nèi)存,卻讓CPU受傷的GC
在CPU的部分聊GC,感覺是不是怪怪的?其實小匹夫不這么覺得,雖然GC是用來處理內(nèi)存的,但的確增加的是CPU的開銷。因此它的確能達到釋放內(nèi)存的效果,但代價更加沉重,會加重CPU的負(fù)擔(dān),因此對于GC的優(yōu)化目標(biāo)就是盡量少的觸發(fā)GC。
首先我們要明確所謂的GC是Mono運行時的機制,而非Unity3D游戲引擎的機制,所以GC也主要是針對Mono的對象來說的,而它管理的也是Mono的托管堆。 搞清楚這一點,你也就明白了GC不是用來處理引擎的assets(紋理啦,音效啦等等)的內(nèi)存釋放的,因為U3D引擎也有自己的內(nèi)存堆而不是和Mono一起使用所謂的托管堆。
其次我們要搞清楚什么東西會被分配到托管堆上?不錯咯,就是引用類型咯。比如類的實例,字符串,數(shù)組等等。而作為int,float,包括結(jié)構(gòu)體struct其實都是值類型,它們會被分配在堆棧上而非堆上。所以我們關(guān)注的對象無外乎就是類實例,字符串,數(shù)組這些了。
那么GC什么時候會觸發(fā)呢?兩種情況:
首先當(dāng)然是我們的堆的內(nèi)存不足時,會自動調(diào)用GC。
其次呢,作為編程人員,我們自己也可以手動的調(diào)用GC。
所以為了達到優(yōu)化CPU的目的,我們就不能頻繁的觸發(fā)GC。而上文也說了GC處理的是托管堆,而不是Unity3D引擎的那些資源,所以GC的優(yōu)化說白了也就是代碼的優(yōu)化。那么匹夫覺得有以下幾點是需要注意的:
字符串連接的處理。因為將兩個字符串連接的過程,其實是生成一個新的字符串的過程。而之前的舊的字符串自然而然就成為了垃圾。而作為引用類型的字符串,其空間是在堆上分配的,被棄置的舊的字符串的空間會被GC當(dāng)做垃圾回收。
盡量不要使用foreach,而是使用for。foreach其實會涉及到迭代器的使用,而據(jù)傳說每一次循環(huán)所產(chǎn)生的迭代器會帶來24 Bytes的垃圾。那么循環(huán)10次就是240Bytes。
不要直接訪問gameobject的tag屬性。比如if (go.tag == “human”)最好換成if (go.CompareTag (“human”))。因為訪問物體的tag屬性會在堆上額外的分配空間。如果在循環(huán)中這么處理,留下的垃圾就可想而知了。
使用“池”,以實現(xiàn)空間的重復(fù)利用。
最好不用LINQ的命令,因為它們會分配臨時的空間,同樣也是GC收集的目標(biāo)。而且我很討厭LINQ的一點就是它有可能在某些情況下無法很好的進行AOT編譯。比如“OrderBy”會生成內(nèi)部的泛型類“OrderedEnumerable”。這在AOT編譯時是無法進行的,因為它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平臺上也許會報錯。
代碼?腳本?
聊到代碼這個話題,也許有人會覺得匹夫多此一舉。因為代碼質(zhì)量因人而異,很難像上面提到的幾點,有一個明確的評判標(biāo)準(zhǔn)。也是,公寫公有理,婆寫婆有理。但是匹夫這里要提到的所謂代碼質(zhì)量是基于一個前提的:Unity3D是用C++寫的,而我們的代碼是用C#作為腳本來寫的,那么問題就來了~腳本和底層的交互開銷是否需要考慮呢?也就是說,我們用Unity3D寫游戲的“游戲腳本語言”,也就是C#是由mono運行時托管的。而功能是底層引擎的C++實現(xiàn)的,“游戲腳本”中的功能實現(xiàn)都離不開對底層代碼的調(diào)用。那么這部分的開銷,我們應(yīng)該如何優(yōu)化呢?
1.以物體的Transform組件為例,我們應(yīng)該只訪問一次,之后就將它的引用保留,而非每次使用都去訪問。這里有人做過一個小實驗,就是對比通過方法GetComponent()獲取Transform組件, 通過MonoBehavor的transform屬性去取,以及保留引用之后再去訪問所需要的時間:
GetComponent = 619ms
Monobehaviour = 60ms
CachedMB = 8ms
Manual Cache = 3ms
2.如上所述,最好不要頻繁使用GetComponent,尤其是在循環(huán)中。
3.善于使用OnBecameVisible()和OnBecameVisible(),來控制物體的update()函數(shù)的執(zhí)行以減少開銷。
4.使用內(nèi)建的數(shù)組,比如用Vector3.zero而不是new Vector(0, 0, 0);
5.對于方法的參數(shù)的優(yōu)化:善于使用ref關(guān)鍵字。值類型的參數(shù),是通過將實參的值復(fù)制到形參,來實現(xiàn)按值傳遞到方法,也就是我們通常說的按值傳遞。復(fù)制嘛,總會讓人感覺很笨重。比如Matrix4x4這樣比較復(fù)雜的值類型,如果直接復(fù)制一份新的,反而不如將值類型的引用傳遞給方法作為參數(shù)。
好啦,CPU的部分匹夫覺得到此就介紹的差不多了。下面就簡單聊聊其實匹夫并不是十分熟悉的部分,GPU的優(yōu)化。
GPU的優(yōu)化
GPU與CPU不同,所以側(cè)重點自然也不一樣。GPU的瓶頸主要存在在如下的方面:
填充率,可以簡單的理解為圖形處理單元每秒渲染的像素數(shù)量。
像素的復(fù)雜度,比如動態(tài)陰影,光照,復(fù)雜的shader等等
幾何體的復(fù)雜度(頂點數(shù)量)
當(dāng)然還有GPU的顯存帶寬
那么針對以上4點,其實仔細(xì)分析我們就可以發(fā)現(xiàn),影響的GPU性能的無非就是2大方面,一方面是頂點數(shù)量過多,像素計算過于復(fù)雜。另一方面就是GPU的顯存帶寬。那么針鋒相對的兩方面舉措也就十分明顯了。
減少頂點數(shù)量,簡化計算復(fù)雜度。
壓縮圖片,以適應(yīng)顯存帶寬。
減少繪制的數(shù)目
那么第一個方面的優(yōu)化也就是減少頂點數(shù)量,簡化復(fù)雜度,具體的舉措就總結(jié)如下了:
保持材質(zhì)的數(shù)目盡可能少。這使得Unity更容易進行批處理。
使用紋理圖集(一張大貼圖里包含了很多子貼圖)來代替一系列單獨的小貼圖。它們可以更快地被加載,具有很少的狀態(tài)轉(zhuǎn)換,而且批處理更友好。
如果使用了紋理圖集和共享材質(zhì),使用Renderer.sharedMaterial?來代替Renderer.material?。
使用光照紋理(lightmap)而非實時燈光。
使用LOD,好處就是對那些離得遠,看不清的物體的細(xì)節(jié)可以忽略。
遮擋剔除(Occlusion culling)
使用mobile版的shader。因為簡單。
優(yōu)化顯存帶寬
第二個方向呢?壓縮圖片,減小顯存帶寬的壓力。
OpenGL ES 2.0使用ETC1格式壓縮等等,在打包設(shè)置那里都有。
使用mipmap。
MipMap
這里匹夫要著重介紹一下MipMap到底是啥。因為有人說過MipMap會占用內(nèi)存呀,但為何又會優(yōu)化顯存帶寬呢?那就不得不從MipMap是什么開始聊起。一張圖其實就能解決這個疑問。

上面是一個mipmap 如何儲存的例子,左邊的主圖伴有一系列逐層縮小的備份小圖
是不是很一目了然呢?Mipmap中每一個層級的小圖都是主圖的一個特定比例的縮小細(xì)節(jié)的復(fù)制品。因為存了主圖和它的那些縮小的復(fù)制品,所以內(nèi)存占用會比之前大。但是為何又優(yōu)化了顯存帶寬呢?因為可以根據(jù)實際情況,選擇適合的小圖來渲染。所以,雖然會消耗一些內(nèi)存,但是為了圖片渲染的質(zhì)量(比壓縮要好),這種方式也是推薦的。
內(nèi)存的優(yōu)化
既然要聊Unity3D運行時候的內(nèi)存優(yōu)化,那我們自然首先要知道Unity3D游戲引擎是如何分配內(nèi)存的。大概可以分成三大部分:
Unity3D內(nèi)部的內(nèi)存
Mono的托管內(nèi)存
若干我們自己引入的DLL或者第三方DLL所需要的內(nèi)存。
第3類不是我們關(guān)注的重點,所以接下來我們會分別來看一下Unity3D內(nèi)部內(nèi)存和Mono托管內(nèi)存,最后還將分析一個官網(wǎng)上Assetbundle的案例來說明內(nèi)存的管理。
Unity3D內(nèi)部內(nèi)存
Unity3D的內(nèi)部內(nèi)存都會存放一些什么呢?各位想一想,除了用代碼來驅(qū)動邏輯,一個游戲還需要什么呢?對,各種資源。所以簡單總結(jié)一下Unity3D內(nèi)部內(nèi)存存放的東西吧:
資源:紋理、網(wǎng)格、音頻等等
GameObject和各種組件。
引擎內(nèi)部邏輯需要的內(nèi)存:渲染器,物理系統(tǒng),粒子系統(tǒng)等等
Mono托管內(nèi)存
因為我們的游戲腳本是用C#寫的,同時還要跨平臺,所以帶著一個Mono的托管環(huán)境顯然必須的。那么Mono的托管內(nèi)存自然就不得不放到內(nèi)存的優(yōu)化范疇中進行考慮。那么我們所說的Mono托管內(nèi)存中存放的東西和Unity3D內(nèi)部內(nèi)存中存放的東西究竟有何不同呢?其實Mono的內(nèi)存分配就是很傳統(tǒng)的運行時內(nèi)存的分配了:
值類型:int型啦,float型啦,結(jié)構(gòu)體struct啦,bool啦之類的。它們都存放在堆棧上(注意額,不是堆所以不涉及GC)。
引用類型:其實可以狹義的理解為各種類的實例。比如游戲腳本中對游戲引擎各種控件的封裝。其實很好理解,C#中肯定要有對應(yīng)的類去對應(yīng)游戲引擎中的控件。那么這部分就是C#中的封裝。由于是在堆上分配,所以會涉及到GC。
而Mono托管堆中的那些封裝的對象,除了在在Mono托管堆上分配封裝類實例化之后所需要的內(nèi)存之外,還會牽扯到其背后對應(yīng)的游戲引擎內(nèi)部控件在Unity3D內(nèi)部內(nèi)存上的分配。
舉一個例子:
一個在.cs腳本中聲明的WWW類型的對象www,Mono會在Mono托管堆上為www分配它所需要的內(nèi)存。同時,這個實例對象背后的所代表的引擎資源所需要的內(nèi)存也需要被分配。
一個WWW實例背后的資源:
壓縮的文件
解壓縮所需的緩存
解壓縮之后的文件
如圖:

那么下面就舉一個AssetBundle的例子:
Assetbundle的內(nèi)存處理
以下載Assetbundle為例子,聊一下內(nèi)存的分配。匹夫從官網(wǎng)的手冊上找到了一個使用Assetbundle的情景如下:
IEnumerator DownloadAndCache (){
// Wait for the Caching system to be ready
while(!Caching.ready)
yieldreturnnull;
// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
yieldreturnwww;//WWW是第1部分
if(www.error !=null)
thrownewException("WWW download had an error:"+ www.error);
AssetBundle bundle = www.assetBundle;//AssetBundle是第2部分
if(AssetName =="")
Instantiate(bundle.mainAsset);//實例化是第3部分
else
Instantiate(bundle.Load(AssetName));
// Unload the AssetBundles compressed contents to conserve memory
bundle.Unload(false);
}// memory is freed from the web stream (www.Dispose() gets called implicitly)
}
}
內(nèi)存分配的三個部分匹夫已經(jīng)在代碼中標(biāo)識了出來:
Web Stream:包括了壓縮的文件,解壓所需的緩存,以及解壓后的文件。
AssetBundle:Web Stream中的文件的映射,或者說引用。
實例化之后的對象:就是引擎的各種資源文件了,會在內(nèi)存中創(chuàng)建出來。
那就分別解析一下:
WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)
將壓縮的文件讀入內(nèi)存中
創(chuàng)建解壓所需的緩存
將文件解壓,解壓后的文件進入內(nèi)存
關(guān)閉掉為解壓創(chuàng)建的緩存
AssetBundle bundle = www.assetBundle;
AssetBundle此時相當(dāng)于一個橋梁,從Web Stream解壓后的文件到最后實例化創(chuàng)建的對象之間的橋梁。
所以AssetBundle實質(zhì)上是Web Stream解壓后的文件中各個對象的映射。而非真實的對象。
實際的資源還存在Web Stream中,所以此時要保留Web Stream。
Instantiate(bundle.mainAsset);
通過AssetBundle獲取資源,實例化對象
最后各位可能看到了官網(wǎng)中的這個例子使用了:
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
}
這種using的用法。這種用法其實就是為了在使用完Web Stream之后,將內(nèi)存釋放掉的。因為WWW也繼承了idispose的接口,所以可以使用using的這種用法。其實相當(dāng)于最后執(zhí)行了:
//刪除Web Stream
www.Dispose();
OK,Web Stream被刪除掉了。那還有誰呢?對Assetbundle。那么使用
//刪除AssetBundle
bundle.Unload(false);
使用Unity Profiler工具檢測內(nèi)存
這篇文章當(dāng)時寫的時候略顯倉促,因此并沒有特別介紹Unity Profiler工具,也更談不上用Unity Profiler工具來監(jiān)測內(nèi)存的使用狀態(tài)了。但是使用Unity Profiler工具來監(jiān)測還是十分必要的,下面就簡單補充一下這方面的知識。

在Profiler工具中提供了兩種模式供我們監(jiān)測內(nèi)存的使用情況,即簡易模式和詳細(xì)模式。在簡易模式中,我們可以看到總的內(nèi)存(total)列出了兩列,即Used Total(使用總內(nèi)存)和Reserved Total(預(yù)定總內(nèi)存)。Used Total和Reserved 均是物理內(nèi)存,其中Reserved是unity向系統(tǒng)申請的總內(nèi)存,Unity底層為了不經(jīng)常向系統(tǒng)申請開辟內(nèi)存,開啟了較大一塊內(nèi)存作為緩存,即所謂的Reserved內(nèi)存,而運行時,unity所使用的內(nèi)存首先是向Reserved中來申請內(nèi)存,當(dāng)不使用時也是先向Reserved中釋放內(nèi)存,從而來保證游戲運行的流暢性。一般來說,Used Total越大,則Reserved Total越大,而當(dāng)Used Total降下去后,Reserved Total也是會隨之下降的(但并不一定與Used Total同步)。
Unity3D的內(nèi)存從大體上可以分為以下幾個部分:
Unity:位Unity3D的底層代碼所分配的內(nèi)存。
Mono:即托管堆。Mono運行時在運行游戲腳本時所需要的內(nèi)存,換句話說托管堆的大小與我們的GameObject數(shù)量、資源量無關(guān),僅是腳本代碼造成的。這部分內(nèi)存是有垃圾回收機制的。
GfxDriver:可以理解為GPU顯存開銷,主要由Texture,Vertex buffer以及index buffer組成。所以盡可能地減少或釋放Texture和mesh等資源,即可降低GfxDriver內(nèi)存。
FMOD:音頻的內(nèi)存開銷。
Profiler
而在簡易模式下的監(jiān)視器最下方,則列出了常見的一些資源以及它們所消耗的內(nèi)存。
紋理
網(wǎng)格
材質(zhì)
動作
音頻
游戲?qū)ο蟮臄?shù)量
而詳細(xì)模式則需要點擊“Take Sample”按鈕來捕獲詳細(xì)的內(nèi)存使用情況。需要注意的是,由于獲得數(shù)據(jù)需要花費一定的時間,因此我們無法獲得實時的詳細(xì)內(nèi)存的使用情況。在詳細(xì)模式中,我們可以觀察每個具體資源和游戲?qū)ο蟮膬?nèi)存使用情況。
若需轉(zhuǎn)載請保留原文鏈接(http://www.cnblogs.com/murongxiaopifu/p/4284988.html)及作者信息慕容小匹夫