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

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

  • Xcode & Instrunments
  • RenderDoc
  • Snapdragon Profiler

小结

Unity MMORPG游戏优化经验分享就为大家介绍到这里,更多Unity优化经验分享请访问Unity官方

中文论坛(Unitychina.cn)!

Unity MMORPG游戏优化经验分享相关推荐

  1. Unity3d-MMO游戏优化经验分享沙龙总结

    原文地址:MMO游戏优化经验分享沙龙总结 昨天去上海参加了UWA公司的张鑫和张强进行了一场关于MMO游戏开发和性能优化的沙龙,活动链接为:UWA优化日上海站|传统MMO手游性能该如何突围?.虽然第二场 ...

  2. Unity内存优化经验分享

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  3. 欢乐互娱庞池海:《龙之谷》项目性能优化经验分享

    欢乐互娱庞池海:<龙之谷>项目性能优化经验分享 在5月12日,UNITY 2017案例分享专场上,欢乐互娱技术引擎开发工程师娱庞池海分享了<龙之谷>项目性能优化经验.以下为分享 ...

  4. 全新的Unity移动游戏优化解决方案

    全新的Unity移动游戏优化解决方案 在今年的CJ CGDC 中国游戏开发者大会上,来自Unity大中华区的技术支持经理张鑫带来了关于<全新的Unity移动游戏优化解决方案>的精彩主题演讲 ...

  5. 城市快速路拥堵治理及一体化交通组织优化经验分享

    10月27日,在由中国道路交通安全协会主办.北京易华录信息技术股份有限公司承办的"交通缓堵新技术及应用实践论坛"上,济南市公安局交警支队交通科学研究所副所长云廷进就<城市快速 ...

  6. 【东营seo诊断公司】SEO优化经验分享 如何成为一个合格的SEOer?

    [东营seo诊断公司]SEO优化经验分享 如何成为一个合格的SEOer? 在优化网站的关键词排名,也就是进行SEO工作时,很多时候都要求SEO优化人员对很多细节进行把控并且对很多数据进行分析.只有这样 ...

  7. unity日常——游戏优化

    unity日常--游戏优化 今天就讲讲游戏中图片的优化 在开发游戏过程中你会发现我的图形界面很简但是为什么还是有延迟?为什吗游戏加载时间那么长?为什吗界面切换那么慢?FPS那么低? 玩游戏的时候电池消 ...

  8. 用Unity快速开发太空飞机大战游戏实战经验分享(上)

    用unity动手先来试试一个简单的太空飞机大战吧.看官请继续往下... 最终效果,可控制己方战机,朝目标敌机发射子弹,打飞机~~~!伴随想象,慢慢呈现这个太空飞机大战游戏. 1. 新建打飞机unity ...

  9. Unreal Open Day 2017 参会总结——ACT(动作)游戏制作经验分享

    国产游戏 失落之魂 (英文名 Lost Soul Aside ) 是由杨冰先生独自一人花费3年左右的时间开发的一款动作游戏,目前还处于并不完整的demo阶段.前不久杨冰先生在网上上传了一个宣传视频,网 ...

最新文章

  1. python扫雷游戏_python实现扫雷小游戏
  2. leetcode majority number
  3. python批量读取图片并复制入word_提取出 Word 文档里的图片 并利用 python 批量转换格式...
  4. python ^ 与 - +是有运算的先后顺序的
  5. POJ NOI MATH-7649 我家的门牌号
  6. 模板设计模式_设计模式实用又简单的模板模式
  7. Linux批量清空当前目录中的日志文件
  8. [20151014]关于result cache.txt
  9. Java程序员从笨鸟到菜鸟之(五十)细谈Hibernate(一)hibernate基本概念和体系结构...
  10. 三菱Q PLC解密 用户名功能块FB破解
  11. 关于电子书下载源、转换、阅读软件、个人图书馆的建立
  12. icon 的css,【iview】icon样式
  13. excel筛选和排序
  14. 拥抱变化 把握未来——“移动”电商营销新时代
  15. 滴滴跨端框架 Chameleon 正式支持快应用
  16. 华为借贴牌沃达丰闯欧洲 首款3G手机9月上市
  17. oracle 分区表 变大,Oracle11G新特性:分区表分区默认segment大小64k变为8M
  18. 斜杠,双斜杠,反斜杠与双反斜杠
  19. 2020年全球及中国术后镇痛药行业市场现状分析,非阿片类药物需求不断增长「图」
  20. Mamba Blog 博客小程序版

热门文章

  1. android 电视关闭动画,Activity 展开和关闭动画
  2. 粗虚线和细虚线_建筑图纸的细线,粗线,虚线表示什么
  3. java jvm 内存参数_深入详解JVM内存模型与JVM参数详细配置
  4. 云联会企业认证_今日新鲜事:沉浸式交互购车新体验 2020首届中国春季云车展启幕...
  5. apt-get update碰到错误
  6. 树形结构:递归转化为迭代,万能通用方法,分治策略基于栈的实现
  7. 动态规划,从例子到理解
  8. Win7 IIS7 ASP.NET MVC3.0 部署问题
  9. linux下C/C++ IDE比较 Code::Blocks还是不错的
  10. s6-8 TCP 拥塞控制