昨天去上海參加了UWA公司的張鑫和張強進行了一場關于MMO游戲開發(fā)和性能優(yōu)化的沙龍谭网,活動鏈接為:UWA優(yōu)化日上海站|傳統(tǒng)MMO手游性能該如何突圍汪厨?。雖然第二場場景分塊加載部分的內容沒有預期中對目前的項目那么有幫助愉择,再加上最后因為趕火車沒有聽完Q&A的環(huán)節(jié)就離開了(然而還是沒有趕上火車劫乱,此中悲苦就不提了。锥涕。衷戈。),有些疑問沒有提出來討論层坠,略有遺憾殖妇,但對于我這樣一個剛剛接觸Unity的新兵來說,收獲頗豐破花。因此這里做一個簡單的記錄和整理谦趣,只包含現(xiàn)場討論我印象比較深的一些點疲吸,與講座內容的順序沒有直接對應關系,更加詳細前鹅、條目性的內容可以參考官方放出的正式文檔摘悴。
0. 關于UWA
張鑫博士介紹了UWA成立這一年多時間內做過的事情,我還是比較贊賞有組織在做這件事情的舰绘,可以說是Unity引擎和用戶之間除了官網(wǎng)文檔和官網(wǎng)技術支持之外的一座橋梁蹂喻。雖然說他們目前的存在價值和盈利方式依賴于Unity引擎和這樣一個生態(tài)圈,但是對于程序員來說除盏,它們比單純的引擎部門接近游戲產(chǎn)品叉橱,和做產(chǎn)品的比又更純粹挫以,沒那么功利者蠕,介于兩者之間。如果有合適的機會我還是挺想做一做這樣的事情的掐松,哈哈~
目前來說踱侣,能有這樣的一群人幫忙提供一些技術上的分享,一些坑可以拿出專門的時間和精力去踩和分析大磺,對于沒有引擎組的小團隊來說是非常好的事情抡句。
張鑫博士提到他們提供熱更新方案,會后單獨問了一下還是只支持Android的版本杠愧,Lua方案的熱更新還沒有人力去做待榔。希望后面可以有人力來做一些這方面的踩坑和性能優(yōu)化工作……
1. UI系統(tǒng)中的Mesh重建
在UI系統(tǒng)的優(yōu)化中,著重提到了Mesh重建的過程流济,這通常是一個消耗最大的部分锐锣,因此也是優(yōu)化需要關注的重點。在NGUI中绳瘟,這一過程發(fā)生在Panel的LateUpdate函數(shù)中——由于NGUI是插件的形式雕憔,因此這一過程是在C#中的,每次需要改變渲染網(wǎng)格時都會銷毀內存中的mesh進行重建操作糖声。而對于UGUI來說斤彼,這一過程是在Cavas的BuildBatch中進行的,已經(jīng)被封裝成了Native的實現(xiàn)蘸泻,因此在UGUI中這部分沒有額外的堆內存分配琉苇,這也是UWA官網(wǎng)把UGUI在運行時分配的堆內存大小標準定義為小于2M,而NGUI的建議標準是小于20M的核心原因悦施。
那么翁潘,如何降低UI系統(tǒng)中Mesh重建的次數(shù)呢?主要從以下兩點出發(fā):
- 當ui發(fā)生改變歼争,比如有組件的添加拜马、刪除渗勘、遮擋關系改變等事情發(fā)生的時候會需要重建Mesh,而當ui的transform發(fā)生改變的時候俩莽,如果不影響原本的遮擋關系旺坠,是不會導致Mesh重建的過程的。
- UGUI中Mesh的重建的基本單位是Canvas扮超。
上述兩點分別對應兩個優(yōu)化思路:
- 盡量減少Mesh重建發(fā)生的次數(shù)取刃;
- 當一定需要Mesh重建的時候,盡量減少Mesh重建影響的范圍出刷;
針對第一點璧疗,除了不要進行不必要的ui修改操作之外,對于需要頻繁切換的復雜界面馁龟,張鑫博士給出的建議是:
"不要使用實例化/銷毀操作來實現(xiàn)界面的切換崩侠,會有很大的額外開銷,對于active/deactive操作來說也會有mesh重建的過程坷檩。推薦通過將其坐標移出視窗之外却音,或者通過Camera的Layer機制來隱藏界面這兩種方式具有最好的性能效果。"
一個使用案例是背包界面矢炼,有大量的item系瓢,創(chuàng)建過程消耗也會很大,如果內存允許的話句灌,使用上述方法會有最小的性能開銷夷陋。
而盡量減少Mesh重建影響的范圍則對應一個基本原則——動靜分離
,即把不需要動態(tài)改變和需要頻繁地動態(tài)改變的組件分離出來胰锌。按照上述的第二點骗绕,可以使用canvas進行動靜分離
。就是說比如某個組件下面掛的東西是一個會動態(tài)變化的部分匕荸,那就把它獨立成一個canvas爹谭。比如仍然以背包界面為例,滑動內容會導致界面的變化榛搔,在滑動過程中每幀都會有Mesh的重建操作诺凡。把滾動的部分做成單獨的Canvas,這樣背景圖片和標題等部分就不會被每幀重建了践惑。
這里需要著重注意的是一些會頻繁改變的動態(tài)部分腹泌,比較容易遺忘,比如聊天中的動態(tài)表情尔觉,角色坐標信息的顯示等部分凉袱,在開發(fā)過程中需要著重注意。
這里我的一個疑問是如果劃分過細,過多的Canvas是否會導致UGUI有額外開銷专甩?比如是否不同Canvas上的界面元素即使使用Batch后的圖钟鸵,也無法合并Draw Call?這是在開發(fā)中需要注意權衡的一個點涤躲。
之前有討論過動靜分離
的概念棺耍,但是進行分離的基本規(guī)則和原理并不是很清楚,張鑫博士的沙龍從性能分析的具體數(shù)據(jù)出發(fā)种樱,結合具體函數(shù)的作用蒙袍,講解得清晰透徹。
另外有幾點關于ui優(yōu)化的筆記:
- NGUI中UITexture不會被合并嫩挤,建議使用UISprite害幅;(我們項目目前使用UGUI,因此NGUI的不是很熟悉岂昭,不知道UGUI中有沒有無法合并的控件以现?)
- UI應該盡量避免重疊,尤其是看上去不重疊但是實際上有半透區(qū)域存在重疊佩抹,會導致Unity不進行合并操作叼风,增加Draww Call數(shù)量取董。
- 在Unity 5.2之后的版本中棍苹,ui控件的z值不為0的情況下不會進行合并。(不知道這是為了解決遮擋或者什么問題而修改的
特性
茵汰,還是Bug枢里,在制作的時候注意一下,尤其是做角色的3D血條有深度更改需求的情況下蹂午。)- 背包界面的優(yōu)化中提到了PixelPerfect的設置栏豺,這是一個對齊像素的效果可以讓字體等顯示的效果更好,比如在背包內容滾動的時候豆胸,每幀都會有比較大的消耗在這上面奥洼,可以考慮關閉掉。
- UI中Mesh需要的頂點數(shù)量如果可以控制在1000以內晚胡,Mesh合并的效率就會比較好灵奖。過多的頂點數(shù)量在全部是靜態(tài)的情況下問題不大,但是一旦涉及到動態(tài)界面就會造成卡頓估盘。
- UI部分的Draw Call建議控制在20~30之間瓷患,是一個比較理想的情況。
2. 血條優(yōu)化案例
UWA做了一個血條性能分析的Demo遣妥,也是和UI有關擅编。
通常MMO游戲中血條會比較多,比如一些PVP或者PVE玩法中,血條會有幾十個甚至上百個爱态,他們會隨著怪物的死亡等消失谭贪,又會隨著新的怪物產(chǎn)生而重新出現(xiàn)。這里需要注意的點有如下幾個:
- 通常比較直接的思路是創(chuàng)建一個緩沖池锦担,用完丟回去故河,需要的時候先沖池子里拿。這里依然會有一些性能問題吆豹,因為放回緩沖池的操作和重新放回場景中的操作會有SetParent的過程鱼的,在UGUI中這一過程會導致控件進行一系列的初始化操作,造成卡頓等問題痘煤。UWA的建議是使用移除視窗之外的操作來代替SetParent操作凑阶,可以提升性能。
- 在掉血跳字等字體的部分美術同學喜歡使用Outline等效果衷快,一次Outline對于一個字來說會多繪制上下左右4遍宙橱,因此對于內容可控的部分建議直接使用 靜態(tài)字體。
- 對于頻繁出現(xiàn)又消失的戰(zhàn)斗提示信息等部分蘸拔,可以使用
.text = ”“
的賦值操作师郑,即把文本的內容賦值為空的方式來代替Active和Deactive。- 如果血條非常多的情況下调窍,可以考慮拆分成多個Canvas宝冕,會有一些意想不到的優(yōu)化效果。UWA做測試觀察到現(xiàn)象是每個Canvas的性能消耗與其中的血條數(shù)量不是成正比的邓萨,而是一種超線性的關系地梨,這可以理解,在每一控件都可能發(fā)生變化的情況下缔恳,需要重建整個Canvas宝剖,那這是一個n*n的關系。(當然說是n^2的關系也不準確歉甚,因為每幀改變1個和改變n個都只會重建一次万细,但是從統(tǒng)計概率上來說,數(shù)量越多纸泄,每幀需要進行Mesh重建的概率就越大赖钞,重建消耗也是越大的,因此是超線性的刃滓。)這里還是一個需要進行Draw Call和重建消耗的折中考慮的點仁烹。
3. 多線程渲染
多線程渲染是我在網(wǎng)易的時候跟過的一個大坑。咧虎。卓缰。當然不是我做的開發(fā)卸勺,而是我們項目比較早在使用引擎組做的這一功能扔字,從集成過程到做兼容性測試遇到過很多問題摊欠,比如某些設備上莫名其妙的Crash吠式。
張鑫博士說他們從他們的經(jīng)驗來看,Unity 5.3版本之后多線程渲染的功能已經(jīng)是一個比較穩(wěn)定的版本了总寒,現(xiàn)在已經(jīng)有正在運營的項目在開啟多線程渲染扶歪。因此整體上還是可以比較放心地開啟的。多線程渲染對于PostEffect的提升效果很大摄闸,他們做了一個測試可以讓CPU消耗從平均20ms降低到平均2ms善镰,也有項目開始之后出現(xiàn)頓卡。
與之前了解的一樣年枕,開啟多線程渲染之后的性能提升效果根本上和是CPU瓶頸還是GPU瓶頸有關炫欺,不同設備效果不同,不同游戲的瓶頸也不同熏兄,因此要各個項目自己測試來看品洛,因此UWA官方的說法是——
”推薦各個項目嘗試開啟∧ν埃“
這部分提到如果在觀察Profile面板時發(fā)現(xiàn)WaitingForJob很高桥状,就說明CPU在等子線程,就已經(jīng)有性能問題了硝清,當出現(xiàn)PutGeometryJobFence的時候辅斟,性能問題就已經(jīng)很嚴重了。
4. 動態(tài)陰影的技術方案
MMO開發(fā)中角色陰影已經(jīng)成為了一個標配耍缴,UWA經(jīng)過測試砾肺,Unity官方的Build-in ShadowMap的性能還是最好的挽霉,推薦使用防嗡,只是效果相對差,而且在Mobile設備上無法支持原生的軟影(存疑侠坎,需要自己測試一下效果)蚁趁。
而Projector的實現(xiàn)方式比較適合只有一個角色需要陰影的情況,推薦了兩款插件:
- Fast Shadow Projector 只支持靜態(tài)物體实胸;
- Fast Shadow Receivor 可以將接受陰影的物體拆分出需要陰影的獨立的mesh他嫡,提升渲染速度,可以支持柱子這樣非平面的物體庐完。
陰影這塊是我們項目目前要研究的重點內容之一钢属,我們在考慮全部動態(tài)陰影的方案來部分替代烘焙的LightMap。這部分市面上的產(chǎn)品只觀察到韓國的兩款Unreal的手游這么來做门躯,之前在網(wǎng)易內部使用Forward Lighting一直都是烘焙的方案淆党。這塊如果有朋友了解什么信息還希望不吝賜教~
5. 資源的同步和異步加載
這是比較有意思的一塊內容,之前的思路一直都是大部分情況下統(tǒng)一使用異步加載,只有在明確知道資源非常小染乌,而且不重要的模塊使用同步加載的方案山孔,比如一個只有幾個面的dummy model之類的。
UWA給出的建議是:在Loading界面中荷憋,如果不需要表現(xiàn)平滑的加載進度和加載界面的話台颠,可以使用同步加載,其他的過程中使用異步加載勒庄。原因是——
”我們觀察到串前,在對于同一個資源,同步加載比異步加載所花費的時間要少很多实蔽。異步加載會把任務拆分成比較小的粒度到每幀執(zhí)行酪呻,但是在設備上每幀33ms的時間中,往往用不了這么久異步加載任務就執(zhí)行完畢了盐须,比如16ms甚至更少的時間玩荠,這就導致了無謂的等待≡舻耍”
如果覺得加載時間過長阶冈,而對于加載過程中的玩家體驗不需要過多關注的項目可以參考這一思路。這的確是我之前的經(jīng)驗所沒有包含的部分塑径。
另外女坑,在Unity中,對于異步加載來說统舀,也可能會造成頓卡匆骗,因為不是所有的過程都是異步的。IO部分可以做到完全異步誉简,但是內存中的初始化的部分過程可能仍然是同步的碉就,比如一張10241024大小的貼圖異步加載,通常設備上都會可以感受到卡頓闷串,因此建議大于這個尺寸的貼圖統(tǒng)一進行預加載*瓮钥。
與此相關的還有一個Unity的小知識。Unity默認每幀給2ms的時間讓CPU拷貝內存東西到GPU中烹吵,比如貼圖碉熄、網(wǎng)格頂點等,默認的Buffer是4M大小肋拔,因此這里也會影響資源加載到最終渲染到屏幕上的時間锈津。這兩個參數(shù)是可以調整的,具體接口參考官方文檔凉蜂。另外默認4M正好是一張32位的10241024大小的貼圖大小琼梆,如果使用了20482048或者更大的貼圖格式七咧,這個Buffer會增大為對應的大小,并且不會在縮小回來叮叹。因此建議資源中的最大尺寸可以給一個定義艾栋,盡量不要出現(xiàn)只有偶爾幾張貼圖使用非常大的尺寸的情況。
6. 動畫模塊的優(yōu)化
除了常規(guī)的降低骨骼數(shù)量和動畫曲線數(shù)量之外蛉顽,Unity 5.2之后提供了一個culling Group的功能蝗砾,用在模型位置一直綁定一個球的方式做碰撞體來優(yōu)化判定,不在視錐范圍的的物體不進行Animator的Update携冤。這一功能主要針對腳本邏輯的Update悼粮,Animator沒有提供單獨的接口根據(jù)距離來控制Update頻路,一個可行的思路是自己重寫其Update接口曾棕,然后傳入更高的Delta Time來模擬降頻的功能扣猫。不過以我之前用Havok的降頻功能來做性能對比的話,除非角色數(shù)量非常高翘地,否則這部分骨骼骨骼的更新的優(yōu)化空間不是非常大申尤,不過Unity這塊具體的數(shù)據(jù)要進行測試才知道。
Animator中有一個Optimize Game Objets的選項衙耕,可以降低Update的消耗昧穿,UWA建議使用。這是因為默認情況下每根骨骼都是一個GameObject橙喘,每幀骨骼更新之后會需要修改它們的Transform时鸵。
"當打開這個選項,導入的角色中的游戲對象transform hierarchy將會被移除厅瞎,而且以Avatar and Animator組件替代饰潜。
角色的SkinnedMeshRenderer將會直接使用Mecanim內部骨骼,因此我們能擺脫所有用于描述骨頭的Transform和簸。
這個選項將提升動畫角色的性能彭雾,推薦最終產(chǎn)品開啟這個選項。優(yōu)化模式下比搭,皮膚網(wǎng)格模型的抽取也是多線程的冠跷。
當開啟了這個選項,用戶能在ModelImporter inspector中指定“Extra Transforms to Expose”的列表身诺。例如,如果你想附加一把劍道右手抄囚,這是一個掛載點霉赡。暴露的transform在游戲對象的hierarchy中是平行的,不管它在骨架視圖中的深度"
對于Animator的Active和Deactive的操作有很大的性能消耗幔托,這在之前斗魚上的直播中已經(jīng)提到過了穴亏,這里還是像ui一樣蜂挪,建議將角色移出視窗之外的方式來進行緩存,或者只把組件Active和Deactive嗓化,來提升性能棠涮。
7. 其他的小tips
除了上述的一些問題之外,還有一些比較了零碎的筆記刺覆,不進行贅述严肪,只記錄如下:
- 渲染面數(shù)建議控制在10w面一下,這是目前經(jīng)過大量測試性價比比較高的一個點谦屑,5w-10w面測試看下來差別不是很大驳糯,Draw Call數(shù)量建議控制在200-300以內,比較好的情況是在100以下氢橙。
- 粒子系統(tǒng)通常需要加載的資源很多酝枢,但是初始化過程比較消耗CPU,因為通常一個粒子系統(tǒng)中的Component很多悍手,建議進行預加載帘睦。
- 粒子系統(tǒng)中的PreWarn選項會在后臺進行一次完整周期的模擬,因此使用可能會有卡頓坦康。
- Skined Mesh引擎是不會進行合并的官脓,MeshBaker插件可以減低Draw Call數(shù)量,對于動態(tài)的物體也可以涝焙,但是會影響裁剪卑笨,而且動態(tài)添加和刪除很慢,通常用于ARPG游戲中的優(yōu)化仑撞,MMO較為少用赤兴。
- 注意創(chuàng)建Mesh和Material的拷貝過程,比如修改一個Material的參數(shù)隧哮,會創(chuàng)建一個新的Material對象桶良,頻繁地執(zhí)行這樣的操作會有泄漏出現(xiàn),推薦使用DynamicMaterial沮翔,緩存然后只修改這一個動態(tài)材質的方法陨帆。
8. 大世界場景拆分和動態(tài)加載
這一部分是張強同學做的講座,基于地形的方式實現(xiàn)了的大世界動態(tài)加載功能采蚀。其實這部分本來是我期望去聽和討論的部分疲牵,以為我們項目正好在進行這塊的技術預研,但是我們不是使用地形榆鼠,而是基于靜態(tài)Mesh纲爸,另外視角我們更傾向于平視而非2.5D,因此這部分對于我們的幫助沒有想象中的大妆够。
我個人覺得這部分的一個問題是整個工程是基于一個Demo性質的實現(xiàn)识啦,而非正式的項目负蚊,因為時間關系沒有在后面進行深入的交流,因此也不清楚目前的實現(xiàn)是否在正式的項目中應用了颓哮。一些應用方面的疑問其實講座正文中沒有講到:
- 結合到美術制作家妆,地塊的拆分建議遵循的原則是什么?比如多少個屏幕范圍劃分為一塊比較合理冕茅?
- 如果使用lightmap的方案伤极,兩個地塊交接處是否會有問題,比如交界處左側有一座山嵌赠,它的投影可能會在另外一個拆分后的地塊上塑荒,如果拆分后再Bake,是否有解決方案可以處理這樣的問題姜挺?
- 如果使用平視視角齿税,目前有沒有什么比較好的解決方案?LOD的話有哪些注意事項炊豪?
這部分可以直接參考官方給出的PPT凌箕,我做的筆記不太多,這里只放了一些沒有來及提出的問題词渤,幸好加了兩位主持人的微信牵舱,回頭整理好問題再一并請教,有答復了再修改本文缺虐。
9. 總結
這次上海之行芜壁,一天時間往返上海杭州,只為了這兩場講座高氮。從收獲來說慧妄,雖然和預期稍有不同,但是還是很值得的剪芍。感謝張鑫博士和張強同學兩位主持人的分享塞淹,你們辛苦啦也感謝UWA公司組織這樣免費的技術沙龍,祝愿貴公司越來越好~
2016年11月26日于杭州家中