Unity3D_NGUI_性能优化实践_CPU卡顿

发表于2015-12-05
评论2 6k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

博尔特以9.58秒创造了百米世界纪录,假设他是跑酷游戏的角色,卡顿一帧就足以冠军拱手让人。

 

Unity3D程序各项性能问题,从Profiler观察到许多蛛丝马迹。下面看几个典型例子ProfilerCPU指标截图

 

有时候蛛丝马迹非常显眼,闪瞎钛白金像素眼。然而有些过于显眼,以至于Profiler都展开不了看详情。囧。

[UICamera.Update]

 

有时候展开很多很多很多...层才能抓住元凶。

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

C:UserspanchenAppDataLocalTemp{8E9ACA7F-C80E-49E2-AA6D-67F2D04999FB}.png

 

CPU曲线有性卡顿,比如:

[UIPanel.LateUpdate...FillAllDrawCall]

CPU曲线也有持续性卡顿,比如:

[UIPanel.LateUpdate...FillDrawCall]

 

游戏单局内每一帧都很关键,卡顿轻则引起操作失误,重则直接撞死。优化边记录,发现UI原因导致卡顿如下表所列个别类型频繁亮相

序号

操作

引发问题

处理方案

实时加载资源

Resource.Load

预加载

实时分配内存

Instantiate

预先分配

显示与隐藏

GameObject.SetActive

调整其他等效方式

UIWIdget子类设置coloralphadepthpositionrotationscalewidthheightrawPivotpivot

UIPanel.LateUpdate.FillDrawCallFillAllDrawCall

重点处理触发FillAllDrawCall的情况

UIBasicSprite子类设置flipfillDirectionfillAmountinvert

触发UIWIdget.MaskAsChange问题

方案

UISprite设置atlasspriteNamefillCenter

触发UIWIdget.MaskAsChange问题

方案

UITexture设置mainTexturematerialshaderborderuvRect

触发UIWIdget.MaskAsChange问题

方案

UILabel设置textfontmaterialfontSizefontStyle等一系列属性

触发UIWIdget.MaskAsChange问题text变更还触发Font.CacheFontForText需特别关注,真机耗时明显

方案,且避免耗时Font.CacheFontForText

重复赋值多个关联UIWIdget组件

多次刷新不同UIWIdget组件的各项值

合并赋值

图标位置重排

触发UIGrid. Reposition

调整为其他等价方式

同一帧逻辑繁重

CPU耗时不平滑

分帧处理,或延迟处理

ParticleSystem粒子特效Animation动画

占用CPU资源

调整策略,或调整为其他方式

UILabelUITextureUISprite都是UIWidget子类,而UIWidgetUIRect子类

 

下文举例阐述表格中各种问题与处理方案详情游戏项目实际逻辑代码较为复杂,例子中Sample代码示意。

 

①实时加载资源

问题描述Resource.Load示意代码

Profiler调用节点类似下图所示。

(上图借用其他界面某个瞬间截图)

处理方案:预加载资源加载不可避免,但触发时机可以提前,也提早加载资源,比如脚本Start()OnEnable(),而不是游戏单局中玩家输入实时触发。

 

创建实例分配内存

问题描述Instantiate示意代码

Profiler调用节点类似下图所示。

(上图借用其他界面某个瞬间截图)

处理方案:预先分配内存提早分配所需内存避免实时触发。

 

③显示与隐藏

问题描述显示或隐藏物件,常规思路是采用GameObject.SetActive(true/false),然而此方法会触发一系列UIRect初始化方法。

Profiler调用节点类似下图所示。

处理方案:应避免直接使用GameObject.SetActive(true/false)显示或隐藏物件,改为其他方式代替比如设置Position把物件移进移出屏幕,或者设置Scale缩放比例、设置Alpha透明度等方式代替

 

设置UIWIdget属性

问题描述变更UIWidgetUIRect及其子类属性包括alphacolordepthlocalPositionlocalRotationlocalScalewidthheightrawPivotpivot会触发UIPanel.LateUpdate.FillDrawCallFillAllDrawCall

属性

描述

alpha

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

color

RGB颜色值

depth

组件层级

localPosition

xyz坐标

localRotation

xyz旋转

localScale

xyz缩放

width

宽度

height

高度

rawPivot

中心点,变更时变更transform

pivot

中心点,变更同时变更transform

UIPanel.LateUpdate代码中看到,若UIWidgetUIRectdepthmaterialmainTexture变更,一定条件下重排UIWdiget组件渲染顺序,调用FillAllDrawCallFillDrawCall

Profiler调用节点类似下图所示。

可看到FillAllDrawCall明显比FillDrawCall更耗资源。

处理方案:单局游戏中应避免变更UIWdigetdepth次序,或变更UIWdiget所用materialmainTexture ,为达到同样的界面显示效果,可以采用变更UIWdiget其他属性来达到相同目的。除了depth调整可能触发FillAllDrawCall,其他属性变更并不会触发FillAllDrawCall

例(1):调整position代替调整depth,达到完全显示遮挡特殊切换效果

 

设置UIBasicSprite属性

问题描述UIBasicSprite继承于UIWidget,除UIWidget属性外,UIBasicSprite自身属性字段包括flipfillDirectionfillAmountinvert变更属性值会调用UIWIdget.MaskAsChange可能触发UIPanel.LateUpdate.FillDrawCallFillAllDrawCall问题

属性

描述

flip

翻转方式

fillDirection

填充方式

fillAmount

填充百分比

invert

是否反方向

一般来说UIBasicSprite这几个属性值变更并不会引发FillAllDrawCall

处理方案:,根据实际情况处理

 

设置UISprite属性

问题描述UISprite继承于UIBasicSprite,除UIBasicSprite属性外,UISprite自身属性字段包括atlasspriteNamefillCenter变更属性值会调用UIWIdget.MaskAsChange可能触发UIPanel.LateUpdate.FillDrawCallFillAllDrawCall问题

属性

描述

atlas

所在图集

spriteName

图集中图片名

fillCenter

是否填充

注意,变更atlas变更mainTexture,进而触发FillAllDrawCall变更atlas常见应用场景是游戏逻辑而实时变更图标

处理方案为了避免因atlas变更而触发FillAllDrawCall有几个思路:

1prefab预先创建多个UISprite并设置对应的atlasspriteName,在实际显示时再通过变更position来达到相同目的。

2整合多张Atlas为一张大的Atlas,在实际显示时只切换spriteName

 

设置UITexture 属性

问题描述UITexture继承于UIBasicSprite,除UIBasicSprite属性外,UITexture自身属性字段包括mainTexturematerialshaderborderuvRect变更属性值会调用UIWIdget.MaskAsChange,进而可能触发UIPanel.LateUpdate.FillDrawCallFillAllDrawCall问题

属性

描述

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

字体,包括bitmapFonttrueTypeFont

material

字体所用Material

fontSize

字号

fontStyle

字体样式斜体和加粗

其他基础属性

同上

问题描述UILabel继承于UIWidget,除UIWidget属性外,UILabel自身属性字段很多,其中textfont重点关注

1text赋值带来一系列字符串操作,相对较耗时。此外需特别留意的是,text赋值还会触发Font.CacheFontForText此方法在真机的实际占用资源比编辑器多很,实践中应以真机Profiler为准以下分别是编辑器0.06ms/0.21ms和真机2.12ms/2.39ms的消耗对比。不管数字、英文是汉字,现象都一样。

处理方案:为避免实时触发Font.CacheFontForText,可在游戏开始时把可能出现的字符全部赋值一次UILabel.text,也避免提前触发Font.CacheFontForText这里提前赋值的字符Cache最小单位是单个字符比如表示奔跑距离的UILabel

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

2font变更会直接把UILabelUIPanelUIWidget列表中移除,再添加一个新的此操作会带来FillAllDrawCall实践鲜见动态修改UILabel.font确实需要动态切换UILabel.font,可采用切换两个UILabel来代替,而这两个UILabel分别应用了两个不同font

 

重复赋值多个关联UIWIdget组件

问题描述:举一个例子。

游戏单局内的任务进度条先前实现是使用两个UIProgressBar,分别更新图形进度条(红色框内容)和数字进度(蓝色框内容)。当任务进度刷新时,两个UIProgressBar.value会同步刷新

处理方案:经分析,此处相关联的多个UIWidget可合并赋值数字进度(蓝色框内容,更新数字和位置)加上亮光头部(更新位置)算到整个Thumb,这样UIProgressBar.value就只需要更新一次(红色框内容更新同步更新蓝色框内容

 

图标位置重排

问题描述:举一个例子。

游戏单局内的Buff列表先前实现使用一个UIGrid,当数量变更时重排UIGridItem数量减少,需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)关键节点代码采用returncontinue等中断正常流程,排除法逐渐逼近目标。尤其适用于Profiler展开失败的情况。

3构建Development Build调试版本,连接真机Profiler

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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台