1. 程式人生 > >Unity記憶體的優化

Unity記憶體的優化

這裡從三個緯度來分享下記憶體的優化經驗:程式碼層面、貼圖層面、框架設計層面。

一.程式碼層面。

1.foreach

Mono下的foreach使用需謹慎。頻繁呼叫容易觸及堆上限,導致GC過早觸發,出現卡頓現象。

特別注意的是在Update中如果非必要,不要使用foreach。儘可能用for來代替foreach。會產生GC Alloc,說明foreach呼叫GetEnumerator()時候有堆記憶體上的操作,new和dispose。

2.string修改。

如果熟悉C++的話,就會了解,每次使用string的時候,都要在記憶體中建立一個新的字串物件,就需要為該新物件分配新的空間。 特別是在迴圈中需要修改string物件,就會頻繁的分配新的空間,這時候推薦使用StringBuilder.Append等操作來處理。C++中通常也是通過分配一個固定的字元記憶體來處理字串的操作。

3.gameObject.tag

gameObject.tag 會在內部迴圈呼叫物件分配的標籤屬性以及拷貝額外的記憶體,推薦使用gameObject.CompareTag("XXX")來代替.tag。

4.使用ObjectPool物件池來管理物件,避免頻繁的Instance,Destroy。

二.貼圖層面。

程式碼上的記憶體優化,很大層面上都不及貼圖上的優化。有時候改一張圖就幫你省了大幾兆的記憶體。

1.巧妙通過調整紋理資源,來調整圖的大小。比如:通過9宮格、部分縮小後Unity裡在拉大等方式。

比如:(主要調整了兩個小元素)就省了一半的記憶體。

優化前


優化後

2.Ios平臺使用PVRT壓縮紋理。Adroid平臺使用ETC1格式壓縮。均可以減至1/4的記憶體大小。優化非常明顯。

目前主流的Android機型基本都支援ETC1格式壓縮。但ETC1只能支援非Alpha通道的圖片壓縮。所以一般把Alpha通道圖分離出來,繪製到GPU視訊記憶體時,a值從Alpha圖裡獲取,無Alpha通道的圖就可以使用ETC1壓縮。

而ETC2以上的格式壓縮雖然支援含Alpha通道的圖片,但是支援的機型還比較少。目前不推薦使用。

未使用ETC1壓縮前的記憶體佔用大小1024*1024的png圖佔用10.7M(包含了Editor中的記憶體佔用,以及mip map記憶體佔用

)。

mipMap是攝像機離得遠近用不同的圖片,3D遊戲中用記憶體換效能的一種有效方式。它會將大圖變成若干小圖,儲存記憶體中,當攝像機離的比較遠的時候,只需使用小圖。

UI、2D場景可以把Texure這個設定去掉。

這樣實際遊戲中未壓縮紋理1024×1024的圖在記憶體中佔用是 4M。(Unity Profiler下看應該是8M)

 

使用ETC1壓縮後,場景圖片一張大小隻有1.3MB,加上通道圖2.6M。幾乎是用來的1/4。

甚至檔案的大小也小了1/4。

 

3.通過減色的方式減少圖片大小。很多UI其實使用的色彩很少,用不到256色。這類圖片就可以進行減色壓縮。

 

三.框架設計層面

一個相對中大型的遊戲,系統非常的多。這時候合理的適時的釋放記憶體有助於遊戲的正常體驗,甚至可以防止記憶體快速到達峰值,導致裝置Crash。

 

目前主流平臺機型可用記憶體:

Android平臺:在客戶端最低配置以上,均需滿足以下記憶體消耗指標(PSS):

1)記憶體1G以下機型:最高PSS<=150MB

2)記憶體2G的機型:最高PSS<=200MB

iOS平臺:在iPhone4S下執行,消耗記憶體(real mem)不大於150MB

1.場景切換時避開峰值。

當前一個場景還未釋放的時候,切換到新的場景。這時候由於兩個記憶體疊加很容易達到記憶體峰值。解決方案是,在螢幕中間遮蓋一個Loading場景。在舊的釋放完,並且新的初始化結束後,隱藏Loading場景,使之有效的避開記憶體大量疊加超過峰值。

2.GUI模組加入生命週期管理。

 

主角、強化、技能、商城、進化、揹包、任務等等。通常一個遊戲都少不了這些系統。但要是全部都開啟,或者這個時候再點世界地圖,外加一些邏輯資料記憶體的佔用等等。你會發現,記憶體也很快就達到峰值。

這時候有效的管理系統模組生命週期就非常有必要。首先將模組進行劃分:

 

1)經常開啟 Cache_10;

2)偶爾開啟 Cache_5;

3)只打開一次 Cache_0。

 

建立一個ModuleMananger 類,內部Render方法每分鐘輪詢一次。如果是“Cache_0”這個型別,一關閉就直接Destroy釋放記憶體;“Cache_10”這個型別為10分鐘後自動釋放記憶體;" Cache_5"這種型別為5分鐘後自動釋放記憶體。每次開啟模組,該模組就會重新計時。這樣就可以有效合理的分配記憶體。