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

Unity3D_NGUI_性能优化实践_CPU卡顿的更多相关文章

  1. 直播推流端弱网优化策略 | 直播 SDK 性能优化实践

    弱网优化的场景 网络直播行业经过一年多的快速发展,衍生出了各种各样的玩法.最早的网络直播是主播坐在 PC 前,安装好专业的直播设备(如摄像头和麦克风),然后才能开始直播.后来随着手机性能的提升和直播技 ...

  2. 手游录屏直播技术详解 | 直播 SDK 性能优化实践

    在上期<直播推流端弱网优化策略 >中,我们介绍了直播推流端是如何优化的.本期,将介绍手游直播中录屏的实现方式. 直播经过一年左右的快速发展,衍生出越来越丰富的业务形式,也覆盖越来越广的应用 ...

  3. 转:携程App的网络性能优化实践

    http://kb.cnblogs.com/page/519824/ 携程App的网络性能优化实践 受益匪浅的一篇文章,让我知道网络交互并不是简单的传输和接受数据.真正的难点在于后面的性能优化 下面对 ...

  4. Lazy<T>在Entity Framework中的性能优化实践

    Lazy<T>在Entity Framework中的性能优化实践(附源码) 2013-10-27 18:12 by JustRun, 328 阅读, 4 评论, 收藏, 编辑 在使用EF的 ...

  5. Redis各种数据结构性能数据对比和性能优化实践

    很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. ...

  6. Tree-Shaking性能优化实践 - 原理篇

    Tree-Shaking性能优化实践 - 原理篇   一. 什么是Tree-shaking 先来看一下Tree-shaking原始的本意 上图形象的解释了Tree-shaking 的本意,本文所说的前 ...

  7. Hadoop YARN:调度性能优化实践(转)

    https://tech.meituan.com/2019/08/01/hadoop-yarn-scheduling-performance-optimization-practice.html 文章 ...

  8. 让Elasticsearch飞起来!——性能优化实践干货

    原文:让Elasticsearch飞起来!--性能优化实践干货 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog ...

  9. Go RPC 框架 KiteX 性能优化实践 原创 基础架构团队 字节跳动技术团队 2021-01-18

    Go RPC 框架 KiteX 性能优化实践 原创 基础架构团队 字节跳动技术团队 2021-01-18

随机推荐

  1. Mysql客户端下载地址

    官网:http://dev.mysql.com/downloads/mysql/   上述千万不要下载免安装版本. 千万记住一定要下载MSI安装版本.

  2. OE_ORDER_PUB.PROCESS_ORDER to Release a hold on sales order in R12

    PURPOSE: This post is to provide a sample script to Release a hold on sales order using an API OE_OR ...

  3. UITabBarController 详解

    // UITabBarController 标签视图控制 // 主要管理没有层级关系的视图控制器 // 1. ViewControllers 所有被管理的视图控制器, 都在这个数组中 // 2. se ...

  4. 网站怎么布局能解决不同浏览器对CSS解析的差异,使用css reset

    很多地方都提到过CSS Reset这个概念,而且细心的朋友会发现,许多大网站的CSS文件中也含有CSS Reset内容. CSS Reset是什么? 在HTML标签在浏览器里有默认的样式,例如 p 标 ...

  5. Wishbone接口Altera JTAG UART

    某些时候,我们在使用Altera FPGA的时候,尤其是涉及SoC系统的时候,通常需要一个串口与PC交互.使用Altera的USB-Blaster免去了外接一个串口.我们可以使用下面所述的IP核通过U ...

  6. Android获取wifi MAC,关闭的wifi不能获取

    最近一直在尝试着在Android上通过ndk用C++获取本机MAC地址,但是用ioctl(SIOCGIFHWADDR)时,一直返回值小于0,即获取不到接口信息.刚开始我以为是自己漏掉了哪些访问权限,所 ...

  7. socket网络编程基础小记

    "一切皆Socket!" 话虽些许夸张.可是事实也是,如今的网络编程差点儿都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间怎样通 ...

  8. java程序计算数独游戏

    兴趣来了,写了个简单的数独游戏计算程序,未做算法优化. 通过文件来输入一个二维数组,9行,每行9个数组,数独游戏中需要填空的地方用0来表示.结果也是打印二维数组. import java.io.Fil ...

  9. 阿里巴巴Android开发手册(规约)

    阿里巴巴Android开发手册(规约) 学习了:https://www.cnblogs.com/jb2011/p/8487889.html  这个猛 https://blog.csdn.net/ali ...

  10. [Functional Programming Monad] Apply Stateful Computations To Functions (.ap, .liftA2)

    When building our stateful computations, there will come a time when we’ll need to combine two or mo ...