編程代碼規(guī)范:
1:腳本行數(shù)最多500行。
2:如果要發(fā)行其他國家的版本需要重新Copy一份客戶端代碼,在另行修改
3:聲音和國際化文字由策劃寫在Excel里面
4:c#擴展做鏈式語法非常容易,比如項目里面的WWWFormEx類的數(shù)據(jù)裝載,非常好寫.
代碼控制
1: 盡量不用foreach,全使用for,因為foreach產(chǎn)生GC
-
2: 字典替換成下面的寫法
var enumerator = m_Dictionary.GetEnumerator(); while (enumerator.MoveNext()) { var element = enumerator.Current; element.Value.UpdateComponent(deltaTime); } 3: 字符串 + 號拼接不超過約10次,不會產(chǎn)生GC,如果超過10次,要使用StringBuilder進行拼接,不會產(chǎn)生GC
4: Struct 與 Class 如何裝箱或拆箱少,堆內(nèi)存少,Struct 在棧中不產(chǎn)生 GC,class 在堆中,會產(chǎn)生 GC。對 Struct 的結(jié)點修改時,修改完以后記得重新賦值。因為 Struct 賦值是 copy
而不是引用,修改完以后,以前的不生效。
- 5: 堆棧的空間有限,對于大量的邏輯的對象,創(chuàng)建類要比創(chuàng)建結(jié)構(gòu)好一些。
結(jié)構(gòu)表示輕量對象,并且結(jié)構(gòu)的成本較低,適合處理大量短暫的對象。
在表現(xiàn)抽象和多級別的對象層次時,類是最好的選擇。
大多數(shù)情況下該類型只是一些數(shù)據(jù)時,結(jié)構(gòu)是最佳的選擇。
- 6: 數(shù)組,ArrayList,List 的區(qū)別;
數(shù)組:內(nèi)存中是連續(xù)存儲的,索引速度非常快,賦值與修改元素也很簡單。但不
利于動態(tài)擴展以及移動。
ArrayList: 因為數(shù)組的缺點,就產(chǎn)生了 ArrayList。ArrayList:使用該類時必須進行引用,同時繼承了 IList 接口,提供了數(shù)據(jù)存儲和檢索,ArrayList 對象的大小動態(tài)伸縮,支持不同類型的結(jié)點。
ArrayList 雖然很完美,但結(jié)點類型是 Object,故不是類型安全的,也可能發(fā)生
裝箱和拆箱操作,帶來很大的性能耗損。對象是值類型的話會帶來裝箱拆箱操作
List 是泛型接口,規(guī)避了 ArrayList 的兩個問題。利于動態(tài)擴展以及移動,但是搜索速度慢
- 7: 不要把枚舉當(dāng) Tkey (字典的key)使用,不要把枚舉轉(zhuǎn)成 string 使用。
閉包
- 1:變量的作用域,成員變量作用于類、局部變量作用于函數(shù)、次局部變量作用于函數(shù)局部
片段。生命周期:變量隨著其寄存對象生而生和消亡(不包括非實例化的 static 和 const
對象)。
- 2:委托概念:是一個類型安全的對象,它指向程序中另一個以后會被調(diào)用的方法(或
多個方法)。通俗的說,委托是一個可以引用方法的對象,當(dāng)創(chuàng)建一個委托,也
就創(chuàng)建一個引用方法的對象,進而就可以調(diào)用那個方法,即委托可以調(diào)用它所指
的方法。如何沒有涉及到閉包的話,委托代碼只生產(chǎn)一個函數(shù)而不是一個類。lamda表達式(閉包)
- 3: 閉包概念:函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。本質(zhì) 1:代碼塊依然維護著它第一個被創(chuàng)建時環(huán)境(執(zhí)行上下文)- 即它仍可以使用創(chuàng)建它的方法中局部變量,即使那個方法已經(jīng)執(zhí)行完了。(循環(huán)引用不釋放);本質(zhì) 2 Closures close over variables, not over values。閉包關(guān)閉的是變量,而不是值.閉包引用了外部變量就會生成一個新得類.函數(shù)調(diào)用頻繁不使用閉包
MonoBehaviour 優(yōu)化
1:如果沒有相應(yīng)的事件處理,刪除對應(yīng)的空函數(shù)
2:Update 優(yōu)化 在 update 中盡量不要調(diào)用查找對象或組件方法如 FindByTag 或 Find 等等???/p>
以在 start 中先緩存下來,然后使用。 如果沒必要每幀的邏輯,可以降低頻率,方法如下:
Void Update(){if(Time.frameCount%6==0){DoSomething();}}
3:如果沒必要每幀的邏輯,可以使用周期性的協(xié)程,如果沒必要每幀的邏輯,可以使用InvokeRepeating("DoSomeThing",0.5f,1.0f);
4:Gameobject 不可見時,設(shè)置 enabled = false 時,update 就會停止調(diào)用。
-
5:協(xié)程有優(yōu)化:yield return null;每幀產(chǎn)生9個字節(jié)的GC垃圾,其余函數(shù)也會產(chǎn)生GC垃圾,需要使提前預(yù)生成方式
WaitForSeconds wfs = new WaitForSeconds(0.1f); IEnumerator AtlasTextureSetting() { yield return wfs; }
Component 優(yōu)化
1:使用內(nèi)建的數(shù)組,比如用 Vector3.zero 而不是 new Vector(0, 0, 0);
2:transform.localRotation = Quaternion.Euler(Vector3.zero);transform.localScale =Vector3.one;transform.localPosition =Vector3.one;等
GameObject 相關(guān)優(yōu)化
- 1:(腳本和本地引擎 C++代碼之間的通信開銷)Gameobject 緩存:類似組件的緩存策略。查找對象標簽:if (go.CompareTag (“xxx”)來代替 if (go.tag == “xxx”),因為內(nèi)部循環(huán)調(diào)用對象分配的標簽屬性以及拷貝額外內(nèi)存。SendMessageUpwards、SendMessage:少用這兩個函數(shù),使用委托替代。緩存組件:調(diào)用 GetComponent 函數(shù)耗性能,用變量先緩存到內(nèi)存在使用, 有必要時記得更新緩存組件。
NGUI 相關(guān)優(yōu)化
- 1: Canvas.BuildBatch(),
合批 Canvas 下所有網(wǎng)格,這個性能熱點在 5.2 版本后挪到了子線程去做減輕了
主線程的壓力,而 NGUI 作為一個插件沒法做到這一點,網(wǎng)格合批的性能熱點還
是耗在主線程的 UIPanel.LateUpdate();
- 2 : UGUI 的 UIMesh 生成是通過底層 C++代碼實
現(xiàn)的,而 NGUI 只能通過上層的不斷創(chuàng)建 Vertex List,這樣在堆內(nèi)存的管理上,
UGUI 確實要好很多,帶來的隱形收益就是 GC 觸發(fā)次數(shù)會少很多。
UI 資源規(guī)范(內(nèi)存優(yōu)化)
1:任何的 UI 圖集最大 size 1024*1024(內(nèi)存優(yōu)化);
2:同一個界面出現(xiàn)的 UI 資源盡量放到一個圖集,重復(fù)利用的公用資源放
common(DrawCall 優(yōu)化);
3:能用九宮格的盡量用九宮格來減小原圖大小(內(nèi)存優(yōu)化);
4.美術(shù)給過來的 UI 原圖 size 盡量小,對于一些全屏的 loading 原畫圖,原畫大
小是 1280 * 720,讓美術(shù)按照比例高度縮小到 500,
這樣一張 1024*1024 的圖集就可以放兩張原圖了,提升圖集利用率。對于一些
600*400 類似大小的原圖,就盡量按比例把最長邊壓小到 500,這樣出來的圖
集就是 512 * 512 而不是 1024 * 1024(內(nèi)存優(yōu)化);
- 5.對于特別長條的 UI 原圖,例如 1000*100,如果由于加入這個長條的原圖導(dǎo)
致圖集大小變大而且利用率很低的話,要把 1000*100 的原圖拆分成兩張圖
500*100,在制作界面的時候用兩個 Image 拼接即可,這樣可以把 1024 的圖
集縮小到 512(內(nèi)存優(yōu)化);
- 6.圖集利用率低于 1/3 的時候,要考慮和其他同一個 size 的圖集合并以提升利
用率。合并的原則是不改變?nèi)魏我粋€圖集的大小,這樣即可完全省掉一張圖集(內(nèi)
存優(yōu)化、安裝包量優(yōu)化);
- 7.盡量復(fù)用 UI 資源,減少不必要的原圖,例如一個卡牌分了五種品質(zhì)原畫底圖,
白藍黃綠紫,就不要使用五張大底圖了,讓美術(shù)同事畫一個灰色原圖,Image 在
使用的時候直接按需求修改頂點色即可(內(nèi)存優(yōu)化);
- 8.關(guān)閉 mipmaps(內(nèi)存優(yōu)化)。
GPU 優(yōu)化
Shader優(yōu)化
1:Fog { Mode Off },最早有一個版本我們沒有關(guān)閉 Fog
2:Fragment 剔除掉 Alpha 為 0 的像素點,減少 OverDraw;
3:OverDraw 優(yōu)化,在每幀繪制中,如果一個像素被反復(fù)繪制的次數(shù)越多,那么它占用的資源也必然
更多。目前在移動設(shè)備上,OverDraw 的壓力主要來自半透明物體。因為多數(shù)情
況下,半透明物體需要開啟 Alpha Blend 且關(guān)閉 ZWrite,同時如果我們繪制
像 alpha=0 這種實際上不會產(chǎn)生效果的顏色上去,也同樣有 Blend 操作,這是
一種極大的浪費。我們的 UI 繪制是 Alpha Blend 且關(guān)閉 ZWrite,因此 UI OverDraw 的優(yōu)化主要
是在制作界面的時候減少 UI 重疊層級(和策劃、美術(shù) pk)。除此之外還是有一
些我們程序可以控制的優(yōu)化點:1.對于九宮格的 Image,如果去掉 fillcenter 不影響最后出來的效果就要把
fillcenter 去掉,可以減少中間一片的像素繪制;2.看不見的元素且沒有邏輯功能要 disable 或者挪出裁剪區(qū)域,而不要通過設(shè)置Alpha=0 來隱藏;3.不要使用一張 Alpha=0 的 Image 來實現(xiàn)放大響應(yīng)區(qū)域的功能;4.UI 底層系統(tǒng)來控制隱藏看不見的元素,例如打開全屏 UI 的時候把下面看不見的 UI 挪出裁減區(qū)域、關(guān)閉主相機渲染。
CPU優(yōu)化
1:優(yōu)化DrawCall、Canvas.SendWillRenderCanvases()、Canvas.BuildBatch()
2:DrawCall,DrawCall 是 CPU 調(diào)用底層圖形接口,頻繁的調(diào)用對 CPU 性能的影響是很明顯
的。優(yōu)化思路很簡單,合批繪制。UGUI 本身的動態(tài)合批機制會幫我們盡量的去
優(yōu)化合批,我們要做的就是弄清楚它的合批機制然后讓 UI 元素盡量合批繪制。合理分配圖集,同一個界面上的圖盡量打到一個圖集,多個界面復(fù)用的圖,放到 common;
3:制作界面的時候,相鄰節(jié)點盡量使用同一個圖集的圖片;
4:Text 本身也是用的 Font Texture,不同字體的 Text 也是來自不同的圖集,所
以在布局界面的時候也要盡量避免穿插打斷繪制流程;
- 5:DrawCall 的數(shù)量不是完全由 Hierarchy 的布局決定,和 UI 的位置也有關(guān)系,
這個位置不是指的 Rectranform 上面的 size 位置重疊就一定打斷繪制,而是真
實的三角面的位置是否重疊。這個可以在 Scene 視圖下用線框模式(Texture
Wire)去觀察;
- 6:.少用 Mask 組件,Mask 實現(xiàn)的原理是 Stencil Buffer,往模版緩存里繪制,
模版緩存里的東西才是可見的。模板緩存會打斷所有的合批,Mask 的子節(jié)點和
外面的節(jié)點無法合批,模板緩存自己占一個 DrawCall。Unity5.2 之后的版本建
議使用 2D Rect Mask 替代。
Profiler介紹及優(yōu)化
1: WaitForTargetFPS: Vsync(垂直同步)功能,即顯示當(dāng)前幀的CPU等待時間
2: Camera.Render: 相機渲染準備工作的CPU占用量
3: Shader.Parse: 資源加入后引擎對Shader的解析過程
4: Reserved Total:系統(tǒng)在當(dāng)前幀的申請內(nèi)存
5: GameObjects in Scene:當(dāng)前幀場景中的GameObject數(shù)量
6: Total Objects in Scene:當(dāng)前幀場景中的Object數(shù)量(除GameObject外,還有Component等).
7: Total Object Count: Object數(shù)據(jù) + Asset數(shù)量.
8: Assets: Texture2d:記錄當(dāng)前幀內(nèi)存中所使用的紋理資源情況,包括各種GameObject的紋理、天空盒紋理以及場景中所用的Lightmap資源.
9: Scene Memory:記錄當(dāng)前場景中各個方面的內(nèi)存占用情況,包括GameObject、所用資源、各種組件以及GameManager等(天般情況通過AssetBundle加載的不會顯示在這里).
10: Other:ManagedHeap.UseSize:代碼在運行時造成的堆內(nèi)存分配,表示上次GC到目前為止所分配的堆內(nèi)存量. SerializedFile(3): WebStream:這個是由WWW來進行加載的內(nèi)存占用. System.ExecutableAndDlls:不同平臺和不同硬件得到的值會不一樣。
優(yōu)化重點
A:CPU-GC Allow:1.檢測任何一次性內(nèi)存分配大于2KB的選項 2.檢測每幀都具有20B以上內(nèi)存分配的選項.
B:Time ms:記錄游戲運行時每幀CPU占用(特別注意占用5ms以上的).
C:Memory Profiler-Other:1.ManagedHeap.UsedSize: 移動游戲建議不要超過20MB. 2.SerializedFile: 通過異步加載(LoadFromCache、WWW等)的時候留下的序列化文件,可監(jiān)視是否被卸載. 3.WebStream: 通過異步WWW下載的資源文件在內(nèi)存中的解壓版本,比SerializedFile大幾倍或幾十倍,重點監(jiān)視.
D: Memory Profiler-Assets: 1.Texture2D: 重點檢查是否有重復(fù)資源和超大Memory是否需要壓縮等. 2.AnimationClip: 重點檢查是否有重復(fù)資源. 3.Mesh: 重點檢查是否有重復(fù)資源.
E:Device.Present: 1.GPU的presentdevice確實非常耗時,一般出現(xiàn)在使用了非常復(fù)雜的shader.2.GPU運行的非???,而由于Vsync的原因,使得它需要等待較長的時間.3.同樣是Vsync的原因,但其他線程非常耗時,所以導(dǎo)致該等待時間很長,比如:過量AssetBundle加載時容易出現(xiàn)該問題.4.Shader.CreateGPUProgram:Shader在runtime階段(非預(yù)加載)會出現(xiàn)卡頓(華為K3V2芯片).
F:StackTraceUtility.PostprocessStacktrace()和StackTraceUtility.ExtractStackTrace(): 1.一般是由Debug.Log或類似API造成. 2.游戲發(fā)布后需將Debug API進行屏蔽.
G:GC.Collect: 原因: 1.代碼分配內(nèi)存過量(惡性的) 2.一定時間間隔由系統(tǒng)調(diào)用(良性的). 占用時間:1.與現(xiàn)有Garbage size相關(guān) 2.與剩余內(nèi)存使用顆粒相關(guān)(比如場景物件過多,利用率低的情況下,GC釋放后需要做內(nèi)存重排)
H:GarbageCollectAssetsProfile:1.引擎在執(zhí)行UnloadUnusedAssets操作(該操作是比較耗時的,建議在切場景的時候進行). 2.盡可能地避免使用Unity內(nèi)建GUI,避免GUI.Repaint過渡GC Allow. 3.if(other.tag == GearParent.MogoPlayerTag)改為other.CompareTag(GearParent.MogoPlayerTag).因為other.tag為產(chǎn)生180B的GC Allow.
I:少用foreach,因為每次foreach為產(chǎn)生一個enumerator(約16B的內(nèi)存分配),盡量改為for. Lambda表達式,使用不當(dāng)會產(chǎn)生內(nèi)存泄漏. 盡量少用LINQ:1.部分功能無法在某些平臺使用. 2.會分配大量GC Allow.
J:控制StartCoroutine的次數(shù): 1.開啟一個Coroutine(協(xié)程),至少分配37B的內(nèi)存. 2.Coroutine類的實例 -- 21B. 3.Enumerator -- 16B.緩存組件: 1.每次GetComponent均會分配一定的GC Allow. 2.每次Object.name都會分配39B的堆內(nèi)存.
K:1:許多貼圖采用的Format格式是ARGB 32 bit所以保真度很高但占用的內(nèi)存也很大。在不失真的前提下,適當(dāng)壓縮貼圖,使用ARGB 16 bit就會減少一倍,如果繼續(xù)Android采用RGBA Compressed ETC2 8 bits(iOS采用RGBA Compressed PVRTC 4 bits),又可以再減少一倍。把不需要透貼但有alpha通道的貼圖,全都轉(zhuǎn)換格式Android:RGB Compressed ETC 4 bits,iOS:RGB Compressed PVRTC 4 bits。2:當(dāng)加載一個新的Prefab或貼圖,不及時回收,它就會永駐在內(nèi)存中,就算切換場景也不會銷毀。應(yīng)該確定物體不再使用或長時間不使用就先把物體制空(null),然后調(diào)用Resources.UnloadUnusedAssets(),才能真正釋放內(nèi)存。3:有大量空白的圖集貼圖,可以用TexturePacker等工具進行優(yōu)化或考慮合并到其他圖集中。4:要保證每張圖得像素寬高都是4得倍數(shù),即除4余0.
L:AudioClip:播放時長較長的音樂文件需要進行壓縮成.mp3或.ogg格式,時長較短的音效文件可以使用.wav 或.aiff格式。
M:Setpass call 的數(shù)值低,Draw call 不一定低,但是SetPass call 的數(shù)值高的話,Draw call 一定高;Batches 的數(shù)值一定情況下與Draw call的數(shù)值比較接近,這個數(shù)值要盡量去優(yōu)化,盡量多利用Unity 的 Dynamic Batch 功能。動態(tài)合批的原理是將所有的頂點轉(zhuǎn)換為CPU的世界空間,如果應(yīng)用沒有調(diào)用過圖形API,則這個動態(tài)批處理不是優(yōu)勢,例如:控制臺程序 1)使用相同的Material,使用的陰影也盡量相同,或者不使用陰影; 2)一幀的頂點數(shù)不超過900個; 3)Shader使用 Vertex Position, Normal and single UV 可以批量處理300個頂點,Shader使用 Vertex Position, Normal, UV0, UV1 and Tangent,可以批量處理180個頂點; 4)如果相同的GameObject被copy了一份,新得這一份修改了 Scale (放大縮小),則不可以進行動態(tài)合批; 5)光照貼圖需要在批處理中嚴絲合縫; 6)Legacy Deferred(光預(yù)傳遞)渲染路徑禁用動態(tài)批處理,因為它必須繪制兩次GameObjects,繪制時調(diào)用額外的像素光不會被合批