標籤:

Moba手游優化總結

作為一款Moba遊戲,首先在性能方面,CPU的優化肯定是最重要的,一方面,Moba遊戲重在競技性,對畫面要求不是很高。另一方面,幀同步這種方式,導致全地圖所有的邏輯都必須在客戶端上運行。拿我們遊戲舉例,iphone5的CPU耗時30-40ms,而GPU耗時卻只有5-10ms左右。但是這裡要說明一點的是,GPU消耗不高,不代表渲染不用優化,實際上本遊戲渲染的CPU佔用也達到了21.5%,裁剪,渲染批次,頂點填充等這些也直接影響CPU效率。其實,考慮到GPU本身消耗不大,我們也可以做一些優化,將一些邏輯轉移到GPU中,從而規避在CPU中進行頂點更新,項目的全圖烏雲擴散就採用的GPU粒子,在這裡不做過多的敘述。

優化大方向主要是以下幾個點:

一,去除相機外的一些顯示相關邏輯。前面說了,由於我們遊戲本身是幀同步,導致全地圖的邏輯必須要全部執行,才能保證所有客戶端同步。這裡說的全圖邏輯指的是遊戲數據邏輯。但是跟顯示相關的邏輯我們完全可以不用執行,針對這個,我們可以做很多事情,我列出了一些優化比較明顯的。

1, 動畫。在優化前,動畫佔用是10%左右,這個數據還是我們本身NPC沒有使用任何動畫,只有角色使用,我們在今後NPC可能也要加入動畫,這個數據會變得相當大。我們的動畫系統是自己使用Animation改造的一個Animator系統,當時還吐槽為什麼不直接使用Animator,現在正好可以因為用的Animation,才得以優化(不過我覺得可能Animator本身自帶這個功能,哈哈)。這是為什麼呢,我們說相機外的動畫可以不用更新,但是,我們卻不能不更新所有邏輯,假設,有個角色從鏡頭外走到鏡頭內,那我們需要更新他的動畫了,但是如果啥邏輯都沒有,我們都不知道應該播哪個動畫,播到哪一幀了,甚至處於動畫融合階段,各個動畫所佔比重,這些我們就都不知道了。所以相機外的動畫,我們需要更新節點本身的賦值邏輯,只是不用去Sample。而賦值邏輯基本不怎麼消耗,Sample應該是動畫系統80%的CPU佔用。經過優化後,動畫部分只佔用到3.6%。

2, 血條。整個UI系統佔用在20%以上,而HUD的更新佔用在1/3以上,因為HUD實在是太多了,特別是如果我們以後要加入暴擊傷害或者其他屬性傷害冒字,這個佔用會更加明顯。同理,就是在屏幕外的血條我給隱藏掉了,這樣就會節省很多,像王者榮耀那種,我甚至都看到當畫面切到某處時,血條會出現異常的白條,感覺像是從新創建的。

3, DynamicBone,這個本身很費,當然,我們現在在Android上是直接幹掉,在IOS上是只顯示主角,這種情況下,做這個就沒多大意義,考慮到以後可能在高端機上開啟所有動態骨骼,那麼這個意義就很大了。

4, TweenPos,這個雖然邏輯很簡單,但是所有能看到的動的東西上面都掛了TweenPos,數量是相當多,整個佔用大概在3%左右,但是判斷是否在相機內這個演算法本身就是一次矩陣運算,消耗也挺大的,我沒有應用在所有物體上,只用在了一些本身就知道是否在相機內的一些物體。雖然一次邏輯運算頂好幾次渲染幀,但是感覺優化還是不是太大。不過,我覺得可以優化一下是否在相機內的演算法,因為我們是正交相機,只需要判斷xy的垂直距離就好了,這個以後再做吧。

二,控制更新頻率。其實思路跟上面的類似,就是盡量削減一些不必要的顯示邏輯。有些邏輯,不需要那麼實時,並且又非常佔用CPU,像這類代碼就可以每隔幾幀更新一次。比如地圖視野的更新(5%),小地圖的更新(10%)。這個能做的有很多,主要參考就是兩個點,一是延遲更新不會有啥太大的問題,二是佔用時間比較長。這類優化,代碼也比較好寫,也不會改變整個代碼結構,注意寫的時候不要直接用常數,把更新頻率用變數弄出來,寫到文件頭上,可以很容易改變更新頻率,今後如果要做根據設備配置來控制更新頻率也好弄。

三,事件集中處理。事件系統這個東西很方便,幾乎每個地方都有用到。這裡說一下屬性改變事件,我們的屬性改變會自動拋出事件。有些屬性的改變可能在某一幀出現多次,而每次觸發所乾的事情卻又比較費。比如:桃,血量這類的屬性,需要刷新UI面板等。像這類事件,我們可以先將改變記錄下來,在一幀所有邏輯執行完畢,再去集中調用這個事件。

四,Lua的遍歷。我們有一個Avatar_Control的更新邏輯,放在了Lua中,這是個渲染幀更,,直接佔用整個邏輯的13%的性能,當時看了下,好像就只調用了很多Avatar的更新,感覺不應該這麼費,看性能佔用,那個函數本身消耗6%(也挺多的),剩下的全是遍歷,也就是說遍曆本身消耗10%。這裡其實有兩個問題,一是遍歷,還有一個是我們也經常強調,盡量少進行Lua跟C#之間的調用。只更新Avatar_Control,6%的消耗本身就是個問題。解決辦法就是,把所有用的Avatar存到C#的列表中統一更新。這一下就省下13%的性能。

五,某些策劃功能通過代碼簡化完成。我們在設計整體架構的時候,考慮的是通用性,就是一個東西,策劃可以通過編輯能實現各種各樣的功能,這當然是應該的。但是,有些使用比較頻繁的功能,如:視野,通過策劃的那一套Effect,Buffer,Skill會導致性能不足。我們可以把產生視野這個功能直接寫在Hero腳本本身上,直接調用產生視野本身,規避掉那套通用流程。

六,資源預載入。遊戲過程中,大部分出現突然掉幀,或者突然卡頓,基本都是資源載入導致的。提前載入所有需要載入的資源是必不可少的,但是這個工作其實很麻煩,一方面,在處於開發階段的時候,資源一直在變化,另一方面,要搜集到根據選擇角色載入所有用到的資源實在是一個很繁瑣很困難的過程。要麼,需要有一個統一的管理,團隊中任何一個使用資源的時候添加到表中,但是這又需要所有人都會這個操作。要麼,就是有個人專門維護這個表。而程序能做的就是檢測出某個資源是否預載入過,沒有的話,就給列印Log出來,讓人處理這個東西。

七,代碼本身的一些優化。這個其實優化空間是很大的,但是又是最困難的,這要求對系統本身比較熟悉,清楚每個函數的調用消耗大概是多少,然後調整代碼結構,而且這個又沒有一個通用方法去檢查。這裡就舉一些例子說明一下。

1, 最常見的,大家也都知道的Unity的GetComponent這個方法本身比較費,在能用緩存的時候,特別是需要每幀更新的時候盡量先緩存下來。

2, 判斷某個變數改變了,才去調用某個函數,我們有個設置位置的方法,他的調用從Lua調到C++,再回調到Lua,然後調用到C#,這個過程完全可以通過判斷位置是否改變了才去執行。

3, 有一種這樣的情況,A語句算出一個值,通過這個值執行B語句,B語句成功則執行C語句,其中B語句有好幾個條件,A語句又執行效率特別低。那麼我們可以在執行A語句之前,把B語句的不需要依賴A語句的判斷方法提前判斷,就能規避掉大部分A語句的執行次數。

4, 項目中提供視野是採用的是不斷地在人身旁產生視野,一段時間後消失視野,這是由於人是移動的,所以必須要執行這樣的tick,而防禦塔這樣的不能移動單位的視野,我們就沒必要執行Tick操作了,只需要產生的時候添加視野,死亡的時候去掉視野。

說下成果吧,優化前前後後大概進行了兩周,性能大概優化了40%左右,現在在我們定義的低端機Iphone5上能已經可以跑滿幀了。然而Android那邊,一些低端機還是會出現打團掉幀的情況,哎,優化是一個持續並且痛苦的過程。


推薦閱讀:

使用頂點投射的方法製作實時陰影
Unity接入多個SDK的通用介面開發與資源管理(一)
Unity中的單例模式、回調函數、消息分發的使用區別?
教你做Unity第一個遊戲Roll A Ball(上)
Unity 2D 動態陰影怎麼實現 | Nexus遊戲說

TAG:unity |