Unity3D_NGUI_性能優化實踐_CPU卡頓


http://gad.qq.com/college/articledetail/7083468

 

 

 

博爾特以9.58秒創造了百米世界紀錄,假設他是跑酷游戲的角色,卡頓一幀就足以把冠軍拱手讓人。

 

Unity3D程序各項性能問題,從Profiler可觀察到許多蛛絲馬跡。下面看幾個典型例子Profiler的CPU指標截圖。

 

有時候蛛絲馬跡非常顯眼,閃瞎鈦白金像素眼。然而有些過於顯眼,以至於Profiler都展開不了看詳情。囧。

[UICamera.Update]

 

有時候要展開很多很多很多...層才能抓住元凶。

[UICamera.Update.xxx.xxx.xxx..........GameObject.SetActive]

 

CPU曲線有偶發性卡頓,比如:

[UIPanel.LateUpdate...FillAllDrawCall]

CPU曲線也有持續性卡頓,比如:

[UIPanel.LateUpdate...FillDrawCall]

 

游戲單局內每一幀都很關鍵,卡頓輕則引起操作失誤,重則直接撞死。邊優化邊記錄,發現UI原因導致卡頓如下表所列。個別類型頻繁亮相。

序號

操作

引發問題

處理方案

實時加載資源

Resource.Load

預加載

實時分配內存

Instantiate

預先分配

顯示與隱藏

GameObject.SetActive

調整為其他等效方式

UIWIdget子類設置color、alpha、depth、position、rotation、scale、width、height、rawPivot、pivot

UIPanel.LateUpdate.FillDrawCall或FillAllDrawCall

重點處理觸發FillAllDrawCall的情況

UIBasicSprite子類設置flip、fillDirection、fillAmount、invert

觸發UIWIdget.MaskAsChange,問題同

方案同

UISprite設置atlas、spriteName、fillCenter

觸發UIWIdget.MaskAsChange,問題同

方案同

UITexture設置mainTexture、material、shader、border、uvRect

觸發UIWIdget.MaskAsChange,問題同

方案同

UILabel設置text、font、material、fontSize、fontStyle等一系列屬性

觸發UIWIdget.MaskAsChange,問題同。text值變更還會觸發Font.CacheFontForText,需特別關注,真機耗時明顯。

方案同,且避免耗時Font.CacheFontForText

重復賦值多個關聯UIWIdget組件

多次刷新不同UIWIdget組件的各項值

合並賦值

圖標位置重排

觸發UIGrid. Reposition

調整為其他等價方式

同一幀邏輯繁重

CPU耗時不平滑

分幀處理,或延遲處理

ParticleSystem粒子特效與Animation動畫

占用CPU資源

調整策略,或調整為其他方式

注:UILabel、UITexture、UISprite都是UIWidget子類,而UIWidget是UIRect子類。

 

下文舉例闡述表格中各種問題與處理方案詳情。游戲項目中實際邏輯代碼較為復雜,例子中或以Sample代碼示意。

 

①實時加載資源

問題描述:Resource.Load示意代碼

Profiler調用節點類似下圖所示。

(上圖借用其他界面某個瞬間截圖)

處理方案:預加載。資源加載不可避免,但觸發時機可以提前,也即提早加載資源,比如腳本Start()或OnEnable(),而不是游戲單局中由玩家輸入實時觸發。

 

創建實例分配內存

問題描述:Instantiate示意代碼

Profiler調用節點類似下圖所示。

(上圖借用其他界面某個瞬間截圖)

處理方案:預先分配內存。同提早分配所需內存,避免實時觸發。

 

③顯示與隱藏

問題描述:顯示或隱藏物件,常規思路是采用GameObject.SetActive(true/false),然而此方法會觸發一系列UIRect子類的初始化方法。

Profiler調用節點類似下圖所示。

處理方案:應避免直接使用GameObject.SetActive(true/false)顯示或隱藏物件,改為其他方式代替。比如設置Position把物件移進移出屏幕,或者設置Scale縮放比例、設置Alpha透明度等方式代替。

 

設置UIWIdget屬性

問題描述:變更UIWidget(UIRect)及其子類屬性,包括alpha、color、depth、localPosition、localRotation、localScale、width、height、rawPivot、pivot,會觸發UIPanel.LateUpdate.FillDrawCall或FillAllDrawCall。

屬性

描述

alpha

透明度。1不透明,0全透明。

color

RGB顏色值

depth

組件層級

localPosition

xyz坐標

localRotation

xyz旋轉

localScale

xyz縮放

width

寬度

height

高度

rawPivot

中心點,變更時不變更transform

pivot

中心點,變更同時變更transform

在UIPanel.LateUpdate代碼中可看到,若UIWidget(UIRect)的depth、material、mainTexture變更,一定條件下會重排UIWdiget組件渲染順序,調用FillAllDrawCall或FillDrawCall。

Profiler調用節點類似下圖所示。

可看到FillAllDrawCall明顯比FillDrawCall更耗資源。

處理方案:單局游戲中應避免變更UIWdiget的depth次序,或變更UIWdiget所用的material、mainTexture,為達到同樣的界面顯示效果,可以采用變更UIWdiget其他屬性來達到相同目的。除了depth調整可能觸發FillAllDrawCall,其他屬性變更並不會觸發FillAllDrawCall。

例(1):調整position代替調整depth,達到完全顯示與遮擋的特殊切換效果。

 

設置UIBasicSprite屬性

問題描述:UIBasicSprite繼承於UIWidget,除UIWidget屬性外,UIBasicSprite自身屬性字段包括flip、fillDirection、fillAmount、invert。變更屬性值會調用UIWIdget.MaskAsChange,可能觸發UIPanel.LateUpdate.FillDrawCall或FillAllDrawCall。問題同④。

屬性

描述

flip

翻轉方式

fillDirection

填充方式

fillAmount

填充百分比

invert

是否反方向

一般來說UIBasicSprite這幾個屬性值變更並不會引發FillAllDrawCall。

處理方案:同④,根據實際情況處理

 

設置UISprite屬性

問題描述:UISprite繼承於UIBasicSprite,除UIBasicSprite屬性外,UISprite自身屬性字段包括atlas、spriteName、fillCenter。變更屬性值會調用UIWIdget.MaskAsChange,可能觸發UIPanel.LateUpdate.FillDrawCall或FillAllDrawCall。問題同⑤。

屬性

描述

atlas

所在圖集

spriteName

圖集中圖片名

fillCenter

是否填充

注意,變更atlas會變更mainTexture,進而觸發FillAllDrawCall。變更atlas的常見應用場景是因游戲邏輯而實時變更圖標。

處理方案:為了避免因atlas變更而觸發FillAllDrawCall,有幾個思路:

(1)prefab預先創建多個UISprite並設置對應的atlas和spriteName,在實際顯示時再通過變更position來達到相同目的。

(2)整合多張Atlas為一張大的Atlas,在實際顯示時只切換spriteName。

 

設置UITexture 屬性

問題描述:UITexture繼承於UIBasicSprite,除UIBasicSprite屬性外,UITexture自身屬性字段包括mainTexture、material、shader、border、uvRect。變更屬性值會調用UIWIdget.MaskAsChange,進而可能觸發UIPanel.LateUpdate.FillDrawCall或FillAllDrawCall。問題同⑤。

屬性

描述

mainTexture

渲染的Texture。

material

渲染的Material。

shader

渲染的Shader。

border

四個角的坐標

uvRect

uv值

處理方案:為了避免觸發FillAllDrawCall,應盡量避免變更屬性值。一般來講,固定不變的貼圖才適合使用UITexture。

注意,UITexture有一個特殊應用場景是玩家頭像。這是因為頭像圖片是網絡下載的資源,默認僅適合UITexture顯示。於是變更mainTexture即帶來FillAllDrawCall。基於此問題,項目中采用了“動態生成Atlas,游戲中僅切換spriteName”的方案來解決。

此Atlas圖集包含了真實玩家頭像和NPC頭像,於游戲開始前動態生成。

參考:http://blog.csdn.NET/u012091672/article/details/35297949

 

設置UILabel屬性

屬性

描述

text

內容。

font

字體,包括bitmapFont和trueTypeFont。

material

字體所用Material。

fontSize

字號

fontStyle

字體樣式:斜體和加粗。

其他基礎屬性

同上

問題描述:UILabel繼承於UIWidget,除UIWidget屬性外,UILabel自身屬性字段很多,其中text和font需重點關注。

(1)text:賦值會帶來一系列字符串操作,相對較耗時。此外需特別留意的是,text賦值還會觸發Font.CacheFontForText,此方法在真機的實際占用資源比編輯器多很多,實踐中應以真機Profiler為准。以下分別是編輯器(0.06ms/0.21ms)和真機(2.12ms/2.39ms)的消耗對比。不管數字、英文或是漢字,現象都一樣。

處理方案:為避免實時觸發Font.CacheFontForText,可在游戲開始時把可能出現的字符全部賦值一次給UILabel.text,也即避免提前觸發Font.CacheFontForText。這里提前賦值的字符Cache最小單位是單個字符。比如表示奔跑距離的UILabel:

即可Cache全部阿拉伯數字加“米”字的展示需求。

(2)font:變更會直接把UILabel從UIPanel的UIWidget列表中移除,再添加一個新的。此操作會帶來FillAllDrawCall。實踐中鮮見動態修改UILabel.font。若確實需要動態切換UILabel.font,可采用切換兩個UILabel來代替,而這兩個UILabel分別應用了兩個不同font。

 

重復賦值多個關聯UIWIdget組件

問題描述:舉一個例子。

游戲單局內的任務進度條先前實現是使用兩個UIProgressBar,分別更新圖形進度條(紅色框內容)和數字進度欄(藍色框內容)。當任務進度刷新時,兩個UIProgressBar.value會同步刷新。

處理方案:經分析,此處相關聯的多個UIWidget可合並賦值。數字進度欄(藍色框內容,更新數字和位置)加上亮光頭部(更新位置)算到整個Thumb,這樣UIProgressBar.value就只需要更新一次(紅色框內容更新時會同步更新藍色框內容)。

 

圖標位置重排

問題描述:舉一個例子。

游戲單局內的Buff列表先前實現是使用一個UIGrid,當數量變更時重排UIGrid。當Item數量減少,需gameObject.SetActive(false)或transform.parent=null,才可以讓UIGrid正確重排。這使得UIGrid.Reposition相對耗時。

處理方案:以其他等價方式代替。由於是固定的0~3個圖標Item,所以按情況設置這4種情況的每組Item坐標即可。不顯示的設置Position移到屏幕外。此方法可避免UIGrid.Reposition。

 

同一幀邏輯繁重

問題描述:看具體例子。

現象(1),游戲單局內UI界面某些數據變化不頻繁,無需每幀實時刷新。為了節省CPU資源,按經驗值可以每6幀處理一次。代碼片段如下:

游戲單局內Profiler曲線大致如下,可以看到明顯有節奏鋸齒:

現象(1)處理方案,將耗時邏輯分散到這6幀處理,每幀處理不同邏輯。也即分散了CPU壓力。代碼片段如下:

調整后Profiler曲線大致如下,看到鋸齒有所緩解。由於拆分后每一幀的邏輯復雜程度不一樣,所以曲線平滑程度有限,仍有改善空間。

 

現象(2),當游戲單局內觸發某類事件,會立即在UI界面上表現。比如撿到一個道具,UI界面立即顯示一個圖標。代碼片段如下:

從Profiler看到是同一幀處理了多個耗時邏輯。

現象(2)處理方案:延遲處理。

將UI表現邏輯延后一幀再處理,分散CPU壓力。代碼片段如下:

調整后Profiler曲線大致如下(下圖分別是兩幀數據),相對修改前能稍微緩和曲線峰值。

 

ParticleSystem粒子特效Animation動畫

問題描述:游戲中為達到酷炫效果,往往會應用ParticleSystem粒子特效與Animation動畫,然而此舉較耗CPU資源。當游戲單局內場景幀率要求較高時,較多或較復雜的粒子特效和動畫會降低幀率,反而影響游戲體驗。

處理方案:有以下思路,需取折衷方案。

(1)調整顯示策略。例如:根據高端機、中端機、低端機(根據CPU、運行內存、屏幕分辨率等參數計算的綜合性能評分)來決定是否啟動特效和動畫。

(2)以其他等效方式代替。例如:一個圖標漸顯漸隱同時放大縮小的效果,可采用組合TweenAlpha+TweenScale動畫來代替Animation動畫。

 

其他

除前文提到Unity自帶強大Profiler(官方手冊),實踐中還有很多調試工具和方法。比如:

(1)強制暫停Unity編輯器。

(2)關鍵節點代碼采用return、continue等中斷正常流程,以排除法逐漸逼近目標。尤其適用於Profiler展開失敗的情況。

(3)構建Development Build調試版本,連接真機Profiler:

http://blog.csdn.Net/u014076894/article/details/38050957


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM