https://mp.weixin.qq.com/s/thGF2WVUkIQYQDrz5DISxA

今天由Unity技术支持工程师高岩,根据实际的技术支持工作经验积累,分享如何对Unity MMORPG游戏进行优化。

在优化Unity游戏时,我们一般从四个方面:CPU、GPU、内存、工程配置等入手,它们都可能是影响游戏性能瓶颈的关键。

CPU

我们平常游戏的很多性能瓶颈都在CPU。例如:MONO内存分配带来CPU开销,当Mono内存从50M、60M、70M,一直增大到100M,这些内存分配都相当于CPU的开销。当在Update函数中存在比较复杂的逻辑时,很容易出现每一帧都触发内存分配,如图01所示。

图 01

虽然截图中一帧里的GC Alloc只有0.6KB,但是当游戏运行很长时间后,累计数量是相当高的,这就让每一帧都存在GC Alloc带来的CPU开销。

处理客户端与服务器通信的数据包时,会存在序列化与反序列化,如果实现方式不合理时,会带来多余的内存分配。一般很多项目都现在使用Protobuff,如果是自行设计的数据包格式,就要考虑如何控制序列化与反序列化的内存分配。

静态数据表如果使用Json、xml等格式时,同时解析逻辑与数据结构设计不良,在初始化数据表时容易由于过大的内存分配而撑大MONO堆内存。所以要在项目设计时找到最优化的方式来实现功能需求与性能需求。

String是一个很常用的引用类型对象。当代码里存在字符串拼接、直接或间接调用ToString()函数时,会生成字符串的副本,也就产生了内存分配。例如:调用Object.name属性,即使每次返回值是固定的,依然是不同的String对象,因为这里每次返回都是一个对象拷贝。所以建议可以通过把这类字符串预先缓存,或者在打包时生成一个名字的列表作为静态数据,提供给运行时的逻辑直接读取。

部分Unity内置API在被调用时,都是返回对象拷贝。例如:Getcomponents、Sprite.Vertices、Input.Touches等。从设计角度是考虑代码安全性,防止外部直接去修改真正的对象数据。所以,这些属性返回值要做缓存。或者通过其他API来实现需求从而规避掉这个问题。请注意,Getcomponent只会在编辑器环境下存在内存开销,真机上不存在,大家在Profiling时不要被误导。

通常Debug.Log一类的日志函数应该只存在Debug阶段,但是很多时候这些函数没有屏蔽。如果它们出现在调用次数较多的逻辑中,就带来额外的CPU开销。同样Warning和Log存在相同的情况。虽然日常在console或真机Log里常见,但是经常没有被处理。建议对待Warning也要找到它的触发原因并解决,防止在Release中出现。Log函数不会因为打包为release版本就会自动屏蔽,需要使用宏定义来屏蔽。

闭包与匿名函数尽可能不要使用。闭包中调用外部变量,需要创建一个临时class对象来包含外部变量并且传给闭包函数,从而带来内存开销。匿名函数在作为一个函数的参数传入时,也存在内存分配。il2cpp中如果使用匿名函数当参数,不要用预声明的函数。

ParticleSystem API在Unity 2017.2之前的版本中,Stop和Simulate内部实现使用了闭包。粒子系统的一些API,例如:Start、Stop、Pause、Clear、Simulate在调用它们时会递归调用当前粒子节点下面的所有子级节点,并会触发GetComponent,这带来了一定的CPU开销。如果需要调这几个方法的时候,函数参数withChildren可以设为false,不触发遍历子节点。在粒子对象初始化时,预存子节点,在需要时直接根据缓存的子节点列表分别调用它们的Start。

Camera.main的调用是存在开销的,可以把Object.FindObjectWithTag(“MainCamera”)缓存下来来代替。调用射线检测函数时应该使用那些不存在开销的函数,例如Physics.RaycastNonAlloc。

当Canvas重建时,会引起材质的重新创建、排序、Mesh重建,这都会带来CPU的开销。当Canvas内容非常复杂的时候,每次重建很可能会带来比较明显的卡顿。UGUI里面的Mask会使用StencilBuffer,蒙版内的元素是没法和外面的元素做合批,即便在图集与材质都是相同的。这时可以用RectMask2D来实现蒙版,可以稍微降低一些开销。Canvas上的GraphicRaycaster选项,在不需要有交互时可以不勾选。而Layout组件会涉及到节点的遍历操作,都有内存与CPU的开销,如果能不用就不用它,或者自行硬编码实现简单的自动布局。

Canvas都建议做动静分离,频繁改动的元素和固定不变的元素分开到不同的Canvas。需要注意Canvas数量,数量多少根据UI的复杂程度、动静分离的Canvas个数进行测试,评估多少个Canvas是合理的。目前发现Unity2017.3中,出现过当Canvas数量达到十几个或更多时,带来的开销反而比不分拆时还大。

UI元素存在半透并很多元素进行叠加,就导致OverDraw消耗比较大。可以通过减少叠加层数、缩小Sprite的空白区域等方式来控制。

当Canvas 处于Worldspace或者Screen Space时,Canvas存在Event Camera或者Render Camera属性,需要挂接Camera。此处若为None,运行时每帧都会有十几次访问它,底层默认返回Camera.main。所以预先关联Camera对象。

图集的分类方式直接影响到UI的合批效率。除了几个通用图集外,其它图集按UI模块类型区分,一个或多个UI公用一套图集。图集的面积利用率要做到最高,避免图集存在太多空白区域。而图标是分散还是合并到图集上,要看项目实际情况,并没有固定的规则。

UI背景图不要出现NPOT尺寸,如果要用NPOT,尝试多个NPOT图合并为POT尺寸,或者美术对NPOT图拉伸为POT,在Unity中还原为原始尺寸。

通常静态合批通过给场景上的物体勾上Static实现,但是有时会因为导致包体太大,改为运行时调用staticBatchingUtility.Combine进行物件合并。但是运行时手动静态合批会有不小的CPU开销,同时Mesh可读写选项也开启,在内存中边存在双份的Mesh数据,同时合并后模型也是一份新Mesh数据。建议可以用第三方插件Mesh Baker来进行静态合批。同时,各个模型的材质也要针对静态合批来制作,毕竟相同材质的模型才可以合并。

图 02

动态合批对于大部分有Lightmap的模型是无效的,还存在900左右顶点的合批限制。在Unity 2017.3支持32bit Mesh index buffers,可以合并Mesh时支持更多的顶点,可以在FBX选项内Index Format打开或者运行时设置Mesh.indexFormat。

骨骼蒙皮计算一般使用CPU Skinning,虽然引擎也是支持GPU skinning的,但需要注意性能瓶颈在CPU端还是GPU端。如果GPU端是性能瓶颈时,盲目打开GPU skinning,会变成一种负优化。当角色模型的骨骼数超过100根、150根时,某些身体部位的骨骼动画,可以用BlendShapes代替。当某一部位骨骼动画不播放时,可以把这个部位的Animator组件关掉。Animation Instancing也是一个可以优化大量角色动画性能的手段。

物理系统中,MeshCollider的使用在场景比较复杂庞大时,Bake的性能比较差。可以通过配合射线检测和自定义高度图数据控制角色高度。

GPU

顶点数量的控制,首先要从美术方面,控制模型的合理面数。有的建筑物被遮挡了一部分,被遮挡部分可以减面甚至把这一块抠掉留空。避免场景中出现大量小物体组合出一个更大的物件,设计之初就对零散物体合并材质、贴图、Mesh。场景地图也可以分区块制作、加载管理,同时配合LODGroup使用。还可以通过第三方插件Mesh Baker LOD辅助进行。

图 03

纹理的尺寸会影响上传纹理时带宽的使用,也就是上传耗时比较高。通常3D模型的纹理,都会把打开Mipmap,可以提高纹理采样的质量,降低命中耗时,提升IO速度。同时纹理过滤模式的选择,对于UI纹理使用Bilinear足矣,Trilinear配合打开Mipmap后的插值计算,效果更好。

当一个角色带有一对翅膀,设置Mesh.alpha进行隐藏或显示,翅膀在Alpha=0时,依然被渲染。而显示全屏UI时,它挡住了后面的主场景,但由于场景Camera未关闭使得场景依然被渲染,如果此时UI里还显示角色模型,积累的渲染压力就比较大,这些都会体现在Overdraw消耗上。

根据对Shader的功能需求,对复杂度要进行控制。运算符要合理使用,变量的浮点精度要同时考虑计算需求和真机的实际支持的精度范围。对Tex2D、纹理采样的使用方式要合理,毕竟这类指令过多时会增加开销。

Unity引擎自带的Terrian系统,可以通过分区块或者转为Mesh解决此部分性能瓶颈。我们可以通过插件Terrain Slicing & Dynamic Loading Kit来分割地形,并调整地形的尺寸和精度等配置参数。

图 04

 

一个特效包含粒子发射器的数量不能随意创建,对渲染和内存都有不小的负载。当粒子存在发射Mesh的需要时,要控制Max Particles的数量。同时有些特效不一定要通过粒子系统实现,可以通过各种变通方式或低负载的方式制作。

内存

每一个Mesh的压缩选项、Read/Write选项都要根据Mesh使用方式进行单独设置,同时要做好当Mesh存在双份数据时,CPU端数据的及时释放。合理的减面也是必不可少的。

压缩纹理的使用是毋庸置疑,而压缩格式要根据项目的机型适配灵活选择,保证质量和体积都能满足需要。当编辑器中刷地形纹理时,需要纹理开启Read/Write,而在打包时要关闭这个选项。

每个纹理的尺寸要根据它的用途、实际测试时内存占用的情况,进行合理的限制,不能随意设定它。对于图集需要最大限度利用面积,避免浪费宝贵的内存。另外当纹理使用ETC2、ASTC格式时,在不支持这些格式的设备上,压缩纹理会被fallback为无压缩的RGBA格式,不但增大了内存占用,同时增加了fallback的CPU开销。

AnimationClip可以通过压缩浮点数精度,剔除无用的scale曲线降低内存占用。同时AnimationClip加载策略也对内存占用有很大影响,全部预加载还是按需异步加载,需要根据项目实际情况决定。

Mono进行内存分配时,在不同类型的数据对象在内存中是相邻的存在内存块里,如果说释放了一个数组,它所占的内存被释放了。但是这个区域是不会还给系统内存,依然保留着。接着又创建了新的对象,新对象的内存大小比刚才被释放的空间大,就无法直接放入这个空间,只能由Mono申请一份新的内存来存放。当Mono申请新内存时,Mono堆内存一般会扩大很大一部分,如见下图05所示。

图 05

在使用数组类型的对象时,如果初始化时时非定长数组,数组实际容量会根据Add操作以0、4、8、16、32倍逐步扩大,其中大量空间为Null,浪费了内存。这种情况常出现在客户端初始化数据表保存到List、Dictionary时。

当我们需要手动释放一些对象的内存时,会有很多种方式,Unity提供了很多卸载各种资源的函数。主动调GC.collect是不必要的,如果一个对象的引用不是Null时,是不可能释放它的。GC只需要做好对象引用的清理就可以,剩下的还是由GC机制自动管理更好。我们可以通过自定义内存池和资源管理器,来很精细的控制每一种资源的生命周期。

AssetBundle压缩格式一般使用LZ4,但要注意AssetBundle的合理Unload时机。而LZMA格式,由于存在加载时解压后重压缩为LZ4的开销,一般情况下不建议使用。主Bundle卸载时,与它关联的依赖Bundle一定要根据引用计数来控制是否可以卸载,否则依赖Bundle的Asset容易引发内存泄露。

IL2CPP在安卓系统使用时,要注意libil2coo.so的文件大小。在安卓系统中,so会在游戏启动后直接加载在内存中,它的内存占用大小基本上和文件大小差不多。所以so的尺寸要有所控制,否则会影响整个游戏的内存数值。所以,使用il2cpp时要注意值类型的泛型、重复代码等容易增大il2cpp的cpp代码体积的情况。

其它

在PhysicsManagerSetting的LayerCollisionMatrix去掉不参加碰撞检测的layer。Time Manager中的fixed time step要根据物理系统的使用情况设置间隔时长。游戏分辨率要通过高中低配置来动态调整。

Graphics Stettings和内置Shader有关的开关根据项目使用情况来有选择的打开或关闭。同时建议所有Shader都要打包为Bundle来加载初始化。

项目的性能优化工作应该每隔一阶段就进行一次性能分析评估,及时解决掉性能瓶颈。同时应该有专人负责这一项工作,提高执行力。

虽然Unity Asset Store资源商店提供的各种插件功能强大,但是插件内部的一些逻辑没有考虑到移动平台的应用环境,存在很多不良代码,需要开发者仔细检查插件源代码,根据情况进行改进。并在性能测试时观察是否存在插件带来的性能瓶颈。

通常在对项目进行性能分析时,会有很多工具辅助我们进行分析工作。下面是我们推荐的工具:

小结

Unity MMORPG游戏优化经验分享就为大家介绍到这里,更多Unity优化经验分享请访问Unity官方中文论坛(Unitychina.cn)!

Unity MMORPG游戏优化经验分享的更多相关文章

  1. Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理

    Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理 2017年01月04日 08:52:12 阅读数:18366 基于Lucene检索引擎我们开发了自己的全文检索系统,承担起后台PB ...

  2. Unity多媒体展示项目经验分享-ImageEffect+动态绑定

    Unity多媒体展示项目经验分享-ImageEffect+动态绑定+网络通信 <ignore_js_op> “海尔科技展墙”是去年年初我们为上海家电博览会制作的一个多媒体展项,有限的工期以 ...

  3. Unity技术支持团队性能优化经验分享

    https://mp.weixin.qq.com/s?__biz=MzU5MjQ1NTEwOA==&mid=2247490321&idx=1&sn=f9f34407ee5c5d ...

  4. 项目优化经验分享(八)TeamLeader经验总结

    引言 通过前面的七篇博客.我把自己在项目优化过程的经验进行了分享,今天这篇博客,作为一个总结,就来讲讲作为一个TeamLeader,在项目管理中遇到的问题和解决经验! 正文 问题一:团队之间怎么沟通? ...

  5. Unite Europe案例项目《影子战术》层级优化经验分享

    http://forum.china.unity3d.com/thread-25087-1-9.html 在Unite Europe 2017的Keynote主题演讲中,我们为大家分享了将主机游戏&l ...

  6. Go程序GC优化经验分享

    http://1234n.com/?post/yzsrwa 最近一段时间对<仙侠道>的服务端进行了一系列针对GC的调优,这里跟各位分享一下调优的经验. 游戏第一次上线的时候,大部分精力都投 ...

  7. Unity MMORPG游戏开发教程(一)——初识Unity

    五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以C#为介绍语言,基于Unity2017.4.x. 一.环境搭建 我使用的是Unity+VSCode,用的是mac系统,windows的自己 ...

  8. unity 2d 游戏优化之路 遇坑记录

    情景说明:  unity 出的Android包,在目前一些主流机型跑都没有问题,但是在 小米3 这种比较老的机器上跑,报如下错误 GLSL compilation failed, no infolog ...

  9. C#.NET 大型企业信息化系统 - 防黑客攻击 - SSO系统加固优化经验分享

    好久没写文章了,突然间也不知道写什么好了一样,好多人可能以为我死了,写个文章分享一下.证明一下自己还在,很好的活着吧,刷个存在感. 放弃了很多娱乐.休闲.旅游.写文章.看书.陪伴家人,静心默默的用了接 ...

随机推荐

  1. linux系统配置之单一网卡配置多个不同网段IP(centos)

    1.用root权限的用户登录CENTOS,进入network-scripts文件夹下(本步骤可以省略,与二步骤一起完成): shell命令:cd /ect/sysconfig/network-scri ...

  2. Android多点触控技术

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

  3. Spring MVC 学习第一篇

    很好的MVC 参考blog:http://jinnianshilongnian.iteye.com/blog/1752171 MVC: 概念:是一种设计模式,并没有引入新的技术,只是把我们开发的结构组 ...

  4. Hihocoder1662 : 查找三阶幻方([Offer收割]编程练习赛40)(暴力)

    时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一个N x M的矩阵,请你数一数其中有多少个3 x 3的子矩阵可以构成三阶幻方? 如果3 x 3的矩阵中每一行.每一列 ...

  5. ACM学习历程—HDU5422 Rikka with Graph(贪心)

    Problem Description As we know, Rikka is poor at math. Yuta is worrying about this situation, so he ...

  6. oracle rac搭建

    (一)环境准备 主机操作系统 windows10 虚拟机平台 vmware workstation 12 虚拟机操作系统 redhat 5.5 x86(32位) :Linux.5.5.for.x86. ...

  7. 机器学习:k-NN算法(也叫k近邻算法)

    一.kNN算法基础 # kNN:k-Nearest Neighboors # 多用于解决分裂问题 1)特点: 是机器学习中唯一一个不需要训练过程的算法,可以别认为是没有模型的算法,也可以认为训练数据集 ...

  8. Go中使用动态库C/C++库

    转自:http://studygolang.com/articles/1441 最近需要做一些在go中使用动态C++库的工作,经常碰到找不到动态库路径这种情况,所以就花点时间,专门做一下实验来了解Go ...

  9. java core

    1:  Java7 以后的 NIO. 2:  泛型要掌握,这里重点强调一点,泛型类之间不存在继承关系,所有的泛型对象在编译后都会去泛型化,都是同一个 class 对象,例如 ArrayList< ...

  10. 转载:IntelliJ IDEA 2016.2 配置Tomcat 运行Web项目

    以前都用MyEclipse写程序的 突然用了IDEA各种不习惯的说 借鉴了很多网上好的配置办法,感谢各位大神~ 前期准备 IDEA.JDK.Tomcat请先在自己电脑上装好 好么~ 博客图片为主 请多 ...