Unity 記憶體優化

語言: CN / TW / HK

導讀: 本文參考Unity官方教程和分享 。

先後介紹 1. 記憶體基礎知識 2. unity記憶體知識 3. 記憶體優化技巧,內容較為初級入門但相對全面,適合Unity專案初期優化。

01

什麼是記憶體?

作業系統有實體記憶體和虛擬記憶體兩個概念:

  • 實體記憶體

實體記憶體也就是我們真實的硬體裝置,例如記憶體條。

我們需要知道,CPU訪問記憶體是一個慢速過程。

訪問過程具體為:先訪問Cache,Cache包含L1,L2,L3,也就是一級快取,二級快取和三級快取,若在這些快取裡全沒找到我們要的資料,再去訪問記憶體,接著會把找到的資料存放到Cache中,完成一次操作。

在Cache中沒有找到資料,我們稱之為Cache Miss。因此過多的Cache Miss就會導致大量的記憶體和Cache的IO交換,浪費大量時間。

因此我們需要儘量減少Cache Miss,來提高訪問速度

臺式裝置和移動裝置記憶體架構的差異

1、首先移動裝置沒有獨立顯示卡。

2、移動裝置沒有獨立視訊記憶體(視訊記憶體的作用是用來儲存顯示卡晶片處理過或者即將提取的渲染資料),所有在移動端資料記憶體和視訊記憶體是同一塊記憶體。所以有可能我們遊戲佔用的記憶體並不大,但是依舊爆記憶體了,其實是因為視訊記憶體分配不出來了。這種情況,我們可以去檢視一下Log,例如Android會有一個 OpenGL Error:Out Of Memory。

3、移動裝置的CPU面積更小,因此會導致快取級數更少,大小也更小,例如一般的桌上型電腦三級快取可能有8-16M,而移動裝置則只有2M左右。

  • 虛擬記憶體

虛擬記憶體是利用磁碟空間虛擬出的一塊邏輯記憶體,用作虛擬記憶體的磁碟空間被稱為交換空間(Swap Space)。

記憶體交換

作業系統在使用記憶體不夠的情況下,會嘗試把一些不用的記憶體(Dead Memory)交換到硬碟上,從而節省出更多的實體記憶體。這個操作我們稱之為記憶體交換,它會佔用大量的硬碟空間。

然而移動裝置不做該操作,因為移動裝置的IO速度很慢,而且移動裝置的可儲存物(例如sd卡,記憶體晶片等)的可擦寫次數也比硬碟少很多,會影響使用壽命。

記憶體壓縮

在IOS中(Android沒有)會將不活躍的記憶體壓縮起來儲存到一個特定空間裡,來節省出實體記憶體空間,來給活躍的app使用,這個操作稱之為記憶體壓縮。

(可以檢視XCode的Virtual Memory)

  • 記憶體定址範圍

記憶體定址範圍也稱定址空間,指的是CPU對於記憶體定址的能力(最大能查詢多大範圍的地址)。資料在記憶體中存放是有規律的,CPU在運算的時候需要把資料提取出來就需要知道資料在那裡,這時候就需要挨家挨戶的找,這就叫做定址,但如果地址太多超出了CPU的能力範圍,CPU就無法找到資料了。

記憶體定址範圍和Memory Controller(記憶體控制器)有關,和運算位數(32位或64位)無直接關係。當然一般情況下,64位的CPU定址範圍更大。

02

Unity記憶體管理

  • Unity是一個C++引擎

Unity是一個C

引擎,並不是C#引擎,底層程式碼全部是由c

的,除了一些Editor裡面的Services可能會用到NodeJS這些網路的語言,Runtime裡面用到的每一行Unity底層程式碼全是C++的。

  • 記憶體管理簡介

Unity記憶體按照分配方式分為:Native Memory(原生記憶體)和Managed Memory(託管記憶體)。Native Memory並不會被系統自動管理,需要我們手動去釋放。而Managed Memory的記憶體管理是自動的,會通過GC來釋放。

此外Unity在Editor和Runtime下,記憶體的管理方式是不同的,除了記憶體大小不同,記憶體的分配時機以及分配方式也可能不同。

例如Asset,在Runtime時,只有我們Load的時候才會進記憶體。而Editor模式下,只要開啟Unity就會進記憶體(所以開啟很慢)。

Unity按照記憶體管理方式分為:引擎管理記憶體和使用者管理記憶體。引擎管理記憶體即引擎執行的時候自己要分配一些記憶體,例如很多的Manager和Singleton,這些記憶體開發者一般是碰觸不到的。使用者管理記憶體也就是我們開發者開發時使用到的記憶體,需要我們重點注意。

  • Untiy檢測不到的記憶體

即Unity Profilter無法檢查到的記憶體,例如使用者分配的Native記憶體。比如自己寫的Native外掛(C

外掛)匯入Unity,這部分Unity是檢測不到的,因為Unity沒法分析已編譯的C

是如何分配和使用記憶體的。還有就是Lua,它完全自己管理的,Unity也沒法統計到它內部的情況。

   03

Native Memory介紹

  • Allocator與Memory Lable

Unity在裡面過載了C++的所有分配記憶體的操作符,例如alloc,new等。每個操作符在被使用的時候要求有一個額外的引數就是Memory Lable,Profilter中檢視Memory Detailed裡的Name很多就是Memory Label。它指的就是當前的這一塊記憶體記憶體要分配到哪個型別池裡。

  • GetRuntimeMemory

Unity在底層會用Allocator,使用過載過的分配符分配記憶體的時候,會根據Memory Lable分配到不同的Allocator池裡面。每個Allocator池,單獨做自己的跟蹤。當我們要在Runtime去Get一個Memory Lable下面池的時候,可以從對應的Allocator中取,可以從中知道有什麼東西,有多少兆。

  • NewAsRoot

前面提到的Allocator的生成是使用NewAsRoot,生成一個所謂的Memory Island,它下面會有很多的子記憶體。例如一個Shader,當我們載入一個shader進記憶體的時候,首先會生成一個shader的Root,也就是Memory Island。然後Shader底下的資料,例如Subshader,Pass,Properties等,會作為該Root底下的成員,依次的分配。所以我們最後統計Runtime的記憶體時,統計這些Root即可。

  • 會及時返還給系統

因為是C++的,所以當我們去delete或free一個記憶體的時候,會立刻返回給系統。這和託管記憶體堆不一樣,需要GC後才返回。

  • 堆疊(Stack)和堆積(Heap)

我們看下Unity記憶體中重要的兩部分,堆疊和堆積,因為只有瞭解了它們,我們才能知道應該如何優化記憶體,提高效能。

堆疊:

堆疊是記憶體中儲存函式和值型別的地方。

例如我們呼叫一個函式A,會將這個函式體與函式收到的引數放入到堆疊中,若在函式A中呼叫函式B,同樣會把函式B存放到堆疊中。當函式B執行結束,會將其從堆疊中移除,然後當A執行結束,把A從堆疊中移除。

因此我們在看Debug資訊的時候,就會發現Log裡面能夠做到一層層的方法回溯,方便我們檢視整體的呼叫過程,這也就是堆疊回溯。

由於是堆疊的結構,因此不會遇到碎片化或是垃圾收集(GC)的問題。但是可能會碰見堆疊溢位的問題,比如呼叫了太多的函式導致一直push東西進堆疊,佔據越來越多的記憶體空間,導致堆疊溢位。

堆積:

堆積是記憶體中另一個區域,要比堆疊大,我們將所有的引用型別存放在這。通常我們每建立一個新的物件,會在堆積中找到下一個足夠存放的空位置,將其儲存。但是當我們銷燬物件後,記憶體空間不會馬上釋放出來,而是標記成未使用,之後垃圾收集器會釋放這部分空間。

物件例項化和摧毀的過程其實很慢,所以我們要儘可能地避免在堆積中配置記憶體的行為。如果我們需要的記憶體比之前已經配置好的還多,在放不下的情況下,堆積會膨脹,並且每次都增長兩倍,且不會再縮回去,過大的堆積就會影響到我們遊戲的效能。當我們在堆積中釋放了一些佔用空間小的物件,而後新增一些佔用空間大的物件時,由於前面釋放的空間不足以存放下,就會導致這些空間空出來,使得記憶體的使用情況就變得斷斷續續起來,這也就是記憶體的碎片化,同樣降低我們的遊戲效能。

而我們前面所提到的GC就是在堆積上進行的,每一次GC,都會遍歷堆積上所有的物件,找到需要釋放的東西,也就是沒有被引用的物件,然後將其釋放。但是有時候我們的一些錯誤引用,導致一些我們希望釋放掉的物件沒有被GC掉,那麼就會造成記憶體洩漏。

假如遊戲玩到一半,GC必須要釋放數十或數百個遊戲物件的記憶體,那麼這會對你的遊戲過程造成一個負載峰值,我們要避免這樣的負載峰值。

   04

優化 Native Memory

以下東西都是和我們Native Memory相關的,使用不當可能導致Native Memory的增長,這塊內容也就和我們的效能優化相關了。

  • 分清主次

優化效能首先要找出效能瓶頸,對效能影響最大的地方先優化,接著對次影響的進行優化,以此類推。

如果能遵守這條規則,優化效果和花費時間的曲線關係大致如圖:

即前期的優化效果會非常明顯,但隨著時間的推移,花的時間越來越多,優化的效果反而逐漸放緩。這告訴我們:

1、首先找出效能瓶頸,優化效果最明顯;

2、優化無止境,後期優化效果和時間比降低,要適可而止。

  • Scene

導致Native Memory增長的原因,最常見的就是Scene。因為是c 引擎,所有的實體最終都會反映在c 上,而不會反映在託管堆上。所以當我們構建一個GameObject的時候,實際上在Unity的底層會構建一個或多個object來儲存這一個GameObject的資訊(Component資訊等)。所以當一個Scene裡面有過多的GameObject存在的時候,Native Memory就會顯著的上升,甚至可能導致記憶體溢位。

注:當我們發現Native Memory大量上升時,可以先著重檢查我們的Scene。

  • Audio

DSP Buffer:DSP Buffer,是指一個聲音的緩衝,當一個聲音要播放的時候,需要向CPU去傳送指令。如果聲音的資料量非常的小,會造成頻繁的向CPU發指令,造成IO壓力。在Unity的FMOD聲音引擎裡面,一般會有一個Buffer,當Buffer填充滿了才會去向CPU傳送一次播放聲音的指令。

DSP Buffer大小的設定一般會導致兩種問題:

1、設定的值過大會導致聲音的延遲,因為填充滿需要很多的聲音資料,當我們聲音資料不大的時候,就會產生延時。

2、設定的值太小會導致CPU負擔上升,因為會頻繁的傳送。

Force To Mono: 這個選項作用是強制單聲道,很多聲音為了追求質量會設定成雙聲道,導致聲音在包體和記憶體中,佔用的空間加倍,但是95%以上的聲音,兩個聲道是完全一樣的資料。因此對聲音不是很敏感的專案建議勾選此項,來降低記憶體的佔用。

Compression Format:不同的平臺有不同的聲音格式的支援,IOS對MP3有硬體支援,Android暫時沒有硬體支援。建議IOS適合使用ADPCM和MP3格式,Android適合使用Vorbis格式。

Load Type:決定聲音在記憶體中的存在形態:

Decompress On Load. 當audio clip被載入時,解壓聲音資料 . 適用於小型音訊檔案(< 200kb)

Compressed In Memory. 聲音資料將以壓縮的形式儲存在記憶體當中. 適用於中型音訊檔案(>= 200kb)

Streaming . 從磁碟讀取聲音資料 . 適用於大型音訊檔案,例如背景音

注:例如Decompress On Load,要求檔案必須小於200kb,因為內部記憶體管理的問題,如果是大於200kb的檔案,那麼也還是隻會被分配到不足200kb的記憶體。

Unity 建議的音效設定 如下

Bitrate:我們可以對音訊檔案本身進行壓縮,降低檔案的位元率(bitrate),前提音訊品質不會被破壞太嚴重。

靜音處理相關: 一般遊戲中都會有靜音的設定,但是開發人員往往只是把AudioSource或Mixer的音量設定為0,這樣還是會造成不必要的記憶體和CPU佔用,因為關音量並不會釋放音訊的記憶體。

因此建議在

1、記憶體中解除安裝音訊相關的來源或是記憶體中的音訊檔案,將AudioSource元件Disable,同時有個上層管理系統負責過濾和音訊相關的API呼叫。比如設了一個標記 如果遊戲為靜音 就不要播放某音訊之類,

2、當然解除安裝和重新載入音訊的成本也很高,要是玩家頻繁的開啟和關閉靜音的話,就不適用了,當然了一般情況下玩家不會這麼操作。

壓縮方式(Lz4和Lzma): 現在Unity主推Lz4(也就是ChunkBased,BuildAssetBundleOptions.ChunkBasedCompression),Lz4非常快,大概是Lzma的十倍左右,但是平均壓縮比例會比Lzma差30%左右,即包體可能會更大些。Lz4的演算法開源。

Lzma基本可以不用了,因為Lzma解壓和讀取速度都會非常慢,並且佔大量的記憶體,因為不是ChunkBased,而是Stream,也就是一次全解壓出來。而ChunkBased可以一塊一塊解壓,每次解壓可以重用之前的記憶體,減少記憶體的峰值。

  • Texture

例如下圖中左右兩邊使用的都是相同的模型與貼圖,但是最終所佔的磁碟大小卻差了很多,就是因為一些設定導致的。

Upload Buffer: 和聲音的Buffer類似,填滿後向GPU push 一次。

一個環形緩衝區將貼圖傳送給GPU。 你可以通過

QualitySettings.asyncUploadBufferSize調整這個非同步緩衝區的大小。

注意:這個環形緩衝區的記憶體同樣直到遊戲程序退出才會把記憶體釋放掉)。

Read/Write: 沒必要的話就關閉,正常情況,Texture讀進記憶體解析完了擱到Upload Buffer裡之後,記憶體裡那部分就會delete掉。除非開了Read/Write,那就不會delete了,會在視訊記憶體和記憶體裡各一份。前面說過手機記憶體視訊記憶體通用的,所以記憶體裡會有兩份。

Mip Maps: 例如UI元素這類相對於相機Z軸的值不會有任何變化的紋理,關閉該選項。不然記憶體將會多用1/3

Format: 選擇合適的Format,可減少佔用的空間。

alpha: 對於不透明紋理,關閉其alpha通道。

Max Size:根據平臺不同,紋理的Max Size設成該平臺最小值。這個設定是初期記憶體的大頭,也是首要選擇 因為設計提供的資源很多時候都不需要達到特別大的解析度 特別是遠景。

POT:紋理的大小盡量為2的冪次方(POT),因為有些壓縮格式可能不支援非2的冪次方的。

合併:

儘量將多張紋理合併成為大圖。非常適合地形和小圖片

壓縮:

Android裝置執行平臺要求支援OpenGL ES 3.0的使用ETC2,RGB壓縮為RGB Compressed ETC2 4bits,RGBA壓縮為RGBA Compressed ETC2 8bits。需要相容OpenGL ES 2.0的使用ETC,RGB壓縮為RGB Compressed ETC 4bits,RGBA壓縮為RGBA 16bits。(壓縮大小不能接受的情況下,壓縮為2張RGB Compressed ETC 4bits)

IOS裝置執行平臺要求支援OpenGL ES 3.0的使用ASTC,RGB壓縮為RGB CompressedASTC 6x6 block,RGBA壓縮為RGBA Compressed ASTC 4x4 block。對於法線貼圖的壓縮精度較高可以選擇RGB CompressedASTC 5x5 block。需要相容OpenGLES 2.0的使用PVRTC,RGB壓縮為PVRTC 4bits,RGBA壓縮為RGBA 16bits。(壓縮大小不能接受的情況下,壓縮為2張RGB Compressed PVRTC 4bits)

參考: Unity貼圖壓縮格式設定

  • Mesh

Read/Write:同Texture,若開啟,Unity會儲存兩份Mesh,導致執行時的記憶體用量變成兩倍。

Compression:Mesh Compression是使用壓縮演算法,將Mesh資料進行壓縮,結果是會減少佔用硬碟的空間,但是在Runtime的時候會被解壓為原始精度的資料,因此記憶體佔用並不會減少。

需要注意的是有些版本開了,實際解壓之後記憶體佔用大小會更嚴重。

Rig:如果沒有使用動畫,請關閉Rig,例如房子,石頭這些。

**Blendshapes:**如果沒有用到Blendshapes,也關閉。

05

優化 Managed Memory

  • Destroy與null

用Destroy,別用null,顯示的呼叫Destroy才能真正的銷燬掉。

  • 物件池

雖然VM自己有記憶體池,但是我們還是需要自己使用記憶體池來管理。

在遊戲程式中,建立和銷燬物件事很常見的操作,通常會通過 Instantiate 和 Destroy 方法來實現,如果頻繁的進行這些操作,GC的時候會導致負載很重,因為會有大量的已摧毀物件的存在,不僅會造成CPU的負載峰值,還可能導致堆積碎片化。因此我們可以使用物件池來處理這類問題。

使用物件池時需要注意,要決定物件池的大小,以及一開始要產生多少數量的物件在池中。因為如果你需要的物件數量多過池中現有的,就必須將物件池變大,擴的太大可能造成浪費,擴的小可能又造成頻繁的新增。

  • 配置表

一個遊戲,策劃往往會通過excel配置很多的配置表,然後我會在遊戲中載入這些excel來讀取其中的資料。但是如果excel數量非常的龐大,我們最好不要一下子全丟到記憶體裡,建議分關載入等。

  • 單例

慎用單例,且不要什麼都往裡放,因為裡面的變數會一直佔用記憶體。

  • 快取一些Hash值

在我們想要在執行時修改動畫或者材質的時候,可以使用下面方法來實現

animator.SetTrigger("Idle");

material.SetColor("Color", Color.white);

這類方法往往也可以通過索引來作為引數,使用字串只是能顯示的更加直觀,但是當我們傳遞字串時,程式內部會進行一些處理,頻繁呼叫的話可能就會造成效能的消耗。因此我們可以先找到對應的索引,並將其快取起來,供後續使用,如下:

int idleHash = Animator.StringToHash("Idle");
animator.SetTrigger(idleHash);
int colorId = Shader.PropertyToID("Color");
material.SetColor(colorId, Color.white);
  • 快取引用物件

例如我們常常會在遊戲執行的時候去查詢一些物件,GameObject.Find與其他所有關聯的方法,需要遍歷所有記憶體中的遊戲物件以及元件,因此在複雜場景中,效率會很低。GameObject.GetComponent,會查詢所有附加到GameObject上的元件,元件越多,GetComponent的成本就越高。若使用的是GetComponentInChildren,隨著查詢變複雜,成本會更高。

因此不要多次查詢相同的物件或元件,而且查詢一次後將其快取起來,方便後續的使用。

- - -

前面我們基本上介紹了記憶體的概念以及和記憶體直接相關的一些優化方法,當然了,優化除了優化記憶體意外還有很多其他的優化,例如DrawCall,演算法的時間複雜度等,接下來我們看看其他方面相關的一些優化。

06

影象(Graphics)的一些優化建議

基本上當Unity渲染遊戲影象時,會呼叫 draw call 來對GPU下指令,讓場景能成功渲染。物件,材質和紋理越多,處理起來需要的時間也越多。所以過多的drawcall就會影響遊戲的優化,這對於瓶頸在GPU上的遊戲影響特別大,也就是我們的遊戲已經給GPU太大的壓力了。

  • 使用批處理:

我們可以使用批處理來儘量減少drawcall,使用批處理需要滿足一些情況,例如,要批處理的物件必須引用一樣的材質,並使用相同的紋理(紋理合並在這就很重要),但是使用的模型可以不一樣。

動態批處理:可以減少對於移動物件的drawcall。只能用於少於900個頂點資訊的情況,包含座標、法線、uv0、uv1、切線。動態批處理每幀評估一次,由CPU負責。

靜態批處理:即對開啟 static 標記的物件做批處理,在構建期完成。適用於絕大部分的靜態Mesh,因此任何不會動的物件都應標記為靜態的。如果我們在執行時要新增靜態物件,可以看一下 StaticBatchUtility.Combine() 的API

  • Cast Shadows

預設情況下,MeshRenderder元件的Cast Shadows是開啟的。

陰影的渲染可以讓遊戲的光線增加真實度和深度感,但是某些情況下可能並不需要。在複雜場景中,可能會造成多餘的陰影計算,陰影效果最後也看不見。

因此若場景有的物件是否有陰影對整體效果沒有影響的話,就關閉這個選項。

不計算陰影,可能為你省下CPU時間 某些情況下 這項建議可能看不出效果,但在特定情況下 你可能會想擠出哪一點CPU優化。(具體渲染步驟可以在 Frame Debugger的Shadows.Draw中檢視)

  • Light Culling Mask

在複雜場景中,許多光線緊靠彼此,你可能覺得光線不能影響特定物件。根據渲染流程的設定,場景中越多的光照,效能可能就會越差。因此我們要確保光照隻影響特定的物件層(例如專門給角色打光的光源,設定成隻影響角色),尤其是多光源和多物件彼此緊靠的時候。

  • 避免使用手機原生解析度

現在的手機解析度非常的高,在手機呈現高解析度可能會影響效能和手機過熱的問題。因為會有大量的計算需求,如後期處理。如果遊戲本身很耗GPU,高解析度會惡化這些問題。建議使用 Screen.SetResolution 來降低遊戲預設的解析設定(根據不同的裝置來找到一些合適的值),來提高效能。

07

UI的一些優化建議

  • 顯示與隱藏

UI的隱藏我們可以使用將其移到Canvas外的方法,而不是利用SetActive(false)的方法來隱藏。

  • 批處理你的UI

如果UI元素會改變數值或是位置,會影響批處理,導致向GPU傳送更多的drawcall。

因此建議:

1、將更新頻率不同的UI放在不同的Canvas上。

2、相同Canvas中的UI元素的Z值要相同,這樣才不會打斷批處理draw call。

3、相同Canvas中的UI元素要使用相同的材質和紋理,材質或著色器可以有動態變換(例如一些特效),這不會影響批處理。

4、相同Canvas中的UI元素要使用相同裁剪矩陣。

  • 全屏UI的處理

遊戲中可能會有些全屏UI(例如一些設定介面),會遮擋住場景物體或其他UI元素。然而它們即使被遮擋看不見,CPU和GPU還是會有消耗,因此建議:

1、3D場景完全被遮擋的話,關閉渲染3D場景的攝像機。

2、被遮蔽的UI,Disable這些Canvas,注意不是SetActive(false)。

3、儘可能的降低幀率,因為這些UI一般不需要重新整理那麼頻繁,如果有個靜態的UI畫面 或者畫面幀率只有15 那遊戲的幀率就沒必要到60

08

其他一些優化

  • GameObject的層次結構

某些情況下,場景中的物體可能有很深的巢狀結構,當我們對父節點的GameObject進行座標轉換時,就會產生OnTransformChanged事件,這訊息會傳遞給該GameObject下所有子物件,即使這些物件沒有任何渲染元件(也就是我們看不見任何變化),造成一些不必要的轉換運算,包括平移,旋轉和縮放。

此外,較深的結構也會導致在GC時,花費更多的時間在層級結構間遍歷。

  • Accelerometer Frequency

這個設定在Project Settings->Player->IOS->Other Settings中,這個功能定義Unity從裝置讀取加速度儀資訊的頻率,在不需要加速儀的遊戲中,將它啟動或設定了高於需求的頻率,會影響效能表現。因為讀取硬體裝置資訊,會增加CPU的處理時間。

  • 移動物體

Unity中有許多移動遊戲物件的方法,例如 transform.Translate,如果物件需要碰撞判定,我們則會新增剛體和碰撞體,如果還是使用 transform.Translate 方法,會造成PhysX物理引擎整體重新計算,對於複雜的場景,成本可能很高。因此若要移動帶有剛體的物件,使用rigidBody.MovePosition,並且要在FixedUpdate方法中執行。

建議使用transform.Translate就在Update中執行,使用rigidBody.MovePosition或AddForce方法在FixedUpdate中執行。

  • 新增元件

在執行時呼叫AddComponent其實很沒效率,尤其在一幀中多次啟用這類呼叫。 當我們新增一個元件的時候,Unity會做下列操作:

1、先看元件有沒有DisallowMultipleComponent的設定,如果有,就要去檢查是否有同類型的元件已加入

2、然後檢查RequireComponent設定是否存在,如果設定了,就代表這個元件需要別的元件同步加入(重複做新增元件的操作)

3、最後呼叫所有被加入的MonoBehaviour的Awake方法

上述這些步驟都 發生在堆積 上,所以可能會影響效能和增加GC的處理時間。

  • 資料結構

也就是Array,List和Dictionary等,例如在Array或List中使用索引的成本很低,那麼就適合要經常通過索引讀取的情況。而要頻繁增加和移除物件時,使用Dictionary是最合適的。

  • 減少層級複雜度

當我們對某個物件做座標轉換的時候

就會產生 OnTransformChanged 事件

這訊息會傳遞到這個物件旗下的所有子物件,及時他們沒有任何視覺效果

我們還是做了很多不必要的座標轉換,包括平移旋轉和縮放,

較深的層級結構,會讓垃圾處理器 花更多事件在層級結構間遍歷。

所以我建議, 如果可以,避免太深的層級結構

將哪些真正需要做座標轉換的物件移出來。這也能加快垃圾收集器的處理時間。

  • 雜湊字串

在動畫控制器或者shader之類都可以使用一些字串來獲得物件 這很簡單也很易懂 但是底層並不是通過字串找到對應的物件,而是會經過一層雜湊定址,如果這在迴圈中使用那就會有額外的效能消耗。

所以建議通過自己提前取好雜湊值。

這看起來像是過度優化,但是如果在多次廣泛使用之後,可以省下許多處理器的時間。

  • 指令碼化工具

如果對於預製體都通用一些屬性,那可以考慮將這些屬性用作設定,因為這些預製體可能會生成多個物件。我們等於讓這些資料重複的出現在有此指令碼的每個Enemy物件上。

所以這裡建議使用ScriptableObject

如果使用這個物件,你就只會耗費一組這樣資料的記憶體。

   09

工具

Unity提供了很多工具,關鍵的就是 永遠在目的裝置上做分析

可以在unity上分析,但是結果會和手機上有出入。

  • Unity Editor Profiler

可以通過接入區域網或者資料線來和手機進行連線

也可以在編輯時,邊開發邊瞭解資源分配的情況。

  • Memory Profiler

可以檢視執行時候的記憶體快照 檢查碎片記憶體

  • Frame Debugger

能讓進行中的遊戲暫時凍結,停留在某一幀

並檢視該幀單獨的draw call 和渲染步驟

並且可以一個一個的單獨檢視draw call

這樣就可以看到各種細節和和組成每個場景的影象元素

  • unity build report tool

通過此工具可以看到unity在打包過程中各資源的大小和佔用比例

  • xcode的instruments

對於iOS裝置是個非常好的檢測工具

參考

https://www.bilibili.com/video/BV1aJ411t7N6 淺談Unity記憶體管理

https://www.bilibili.com/video/BV1Tt4y1X7f6 效能優化技巧(上)

https://www.bilibili.com/video/BV1Bp4y1i7wK 效能優化技巧(下)

掃描下方二維碼新增 「好未來技術」 微信官方賬號

進入好未來技術官方交流群與作者實時互動~

(若掃碼無效,可通過微訊號 TAL-111111 直接新增)

- 也許你還想看 -

Orchestrator 在好未來資料庫高可用系統中的應用

基於雙模檢測的通話錄音質檢解決方案

混合雲網絡治理一期總結,二期展望

容器化後資源與成本優化實踐

我知道你“在看”喲~