Unity优化及问题集合

游戏优化

Posted by Bob on May 7, 2021
NGUI
  • NGUI使用C#开发,会导致堆栈的内存分配,运行时会导致内存的操作
  • NGUI的优化方面,UIPanel.LateUpdate开销最大,
  • 尽可能将动态UI元素和静态UI元素分离到不同的UIPanel中(UI的重建以UIPanel为单位),从而尽可能将因为变动的UI元素引起的重构控制在较小的范围内;
  • 尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中;
  • 控制同一个UIPanel中动态UI元素的数量,数量越多,所创建的Mesh越大,从而使得重构的开销显著增加。
  • 循环虚拟列表
  • 高效地处理大量HUD元素等
UGUI
  • Unity 5.2之后Rebatch的过程进行了优化,会根据设备CPU核心数量多线程执行,但是如果每隔几帧就要Rebatch/Rebuild一次,耗时还是比较可观的。例如UI元素含有Animator动画,频繁缩放、修改Sprite等等。这时候就建议将Canvas进行拆分,进行动静分离等策略
  • 降低overdraw有以下几种方法: ⑴尽量减少alpha = 0的资源的使用,因为这种资源也会参与绘制(2) 避免无用对象及组件的过度使用(用了很多“不可见”的Image作为交互响应的控件)
  • 避免使用Layout
  • 减少不必要的Graphic Raycaster,Image或Text,如果不需要点击,则不要勾选Raycasts
  • 对Component进行 enabled = false 的操作比对GO进行SetActive(false)的操作耗时低
  • 通过将UI元素的坐标移动到Canvas的范围之外的方法来显示与隐藏,避免SetActive的耗时以及SendWillRenderCanvases的耗时
  • 在C#层设置变量来标识相应的GO处于Active还是非Active状态,避免对Active的对象进行SetActive(true),避免对非Active的对象进行SetActive(false)
  • UI元素的变化导致所在的Canvas变化,触发函数Canvas.SendWillRenderCanvases()与Canvas.BuildBatch()造成高耗时
  • 通过添加CanvasGroup组件设置透明度的方式来进行显示与隐藏
  • 如果该UI界面开启的频率很低,可考虑直接通过Instantiate/Destroy来进行切换;
  • 如果该UI界面使用较为频繁,可尝试通过Active/Deactive来代替Instantiate/Destroy操作,从而降低UI切换时的性能开销;
  • 如果该UI界面使用非常频繁,则可尝试直接改变UI界面位置的方式来移进/移出相机视域体,从而来极大提升UI界面的切换效率。
  • 给Text添加outline之后,顶点数大约是未添加之前的7.5倍,使用TextMeshPro描边或者美术字
  • 同一图集的Image元素应尽量保证在Hierarchy中连续,避免中间插入其他图集,或插入文本。
  • 同一父节点下所有子节点,保持相同的层次结构(如List控件下的item),便于底层相同depth下UI元素Batch。
  • 固定的Text考虑与背景图层合在一张图上(可能不便本地化,但可以减少drawcall)
  • 尽可能使用少的UI Material和贴图(使用图集),使得可以Batching。
  • 避免或减少Mask的使用,1个Mask至少增加两个DC
  • 镂空九宫格不勾选FillCenter,在Scene的Overdraw下可以查看到,不勾选FillCenter,overdraw会减少
场景
  • 场景总面数均值5-6w左右(大部分),最高10w(少部分),单个模型300-1500面,摄像机可见部分2w以下,导出去除废点,多余的法线(如果没用的话),尽量单面;
  • 场景总面数还要看同屏战斗人数多少做调整,drawcall最好不超过50,一边给角色和特效留下空间,总 drawcall 不要超过250这个尽量保证;
  • 场景物体每个模型一个材质球,贴图256为主,正方形,pot(长宽2的倍数);
  • 严格限制场景中透明片的大小和重叠的个数,尽量少用,减少面积;
  • 使用occlusion culling,遮挡剔除可实现减少DrawCall(但是如果项目使用了多线程渲染且开启了Occlusion Culling,通常会导致子线程的压力过大导致整体Culling过高)
  • 场景切换时,主动调用System.GC.Collect(),及时清理内存
  • 使用静态批处理,静态批处理允许游戏引擎尽可能多的去降低绘制任意大小的物体所产生的DrawCall,网格合并的顶点数量有上限(Unity中是65535)
  • 静态物体尽量将法线渲染到贴图。
  • Culling的耗时在10%~20%的范围是比较合理的:Culling耗时与场景中的GameObject小物件数量的相关性比较大。这种情况建议研发团队优化场景制作方式 ,关注场景中是否存在过多小物件,导致Culling耗时增高。可以考虑采用动态加载、分块显示,或者Culling Group、Culling Distance等方法优化Culling的耗时。
  • 设计不同的Layer来决定物件是否产生阴影和接受阴影
  • 摄像机一定要调整远近裁剪面,尽量降低远近裁剪面的距离,默认数值过大,也千万不要图方便设置比如 0.1-2000这种,该数值极大影响低端机的深度缓存精度,引起过近的物体穿插闪烁;
模型
  • 设定好 3dmax 导出模型的比例标尺,最好与 unity 大小 1:1
  • 角色面数部分,主角1500以下;特殊boss2000以下;精英怪物1000面以下;普通怪物800面以下;
  • 每个角色一个材质,除了特殊部件(翅膀等)一律不使用透明材质;贴图长宽相等,符合pot,128-256为主,最大不超过512;
  • 去除 IK 节点和所有不必要的网格,对齐模型中心点与坐标原点;
  • 预先规定好角色需要换装的所有节点名称,并且将需要换装的部分规定好并制作时拆开;(建议将脸部单独拆卡,脸部可增加面数);
  • 角色骨骼30-35根为主,特殊大boss适当增加,超过40慎重考虑;
  • 可以考虑动画帧率10帧每秒,视具体情况或者不限制;
  • 简化资源
  • 删掉背面
  • 合并使用同贴图的材质球,合并使用相同材质球的Mesh
  • 骨骼系统不要使用太多
  • 控制面数
  • LOD层级细节,根据距离的远近使用不同模型级别,这样就可以减少模型上面的顶点和面片数量从而提高性能;
  • 养成良好的标签(Tags)、层次(Hieratchy)和图层(Layer)的条理化习惯,将不同的对象置于不同的标签或图层,三者有效的结合将很方便的按名称、类别和属性来查找
  • Mesh Compression:压缩比越高模型文件越小,需要根据游戏内的实际效果决定,一般可以设置为Medium。
  • Read/Write Enable:如果你不需要运行时修改模型的话,禁用,否则启用后模型的内存消耗会增加一倍。
  • Optimize Mesh:推荐启用,可以提升GPU性能。
  • Normals:如果你的模型没有法线信息,将其设为None,可以减小模型大小。
粒子
  • 不适用稠密的粒子,尽量使用UV动画
  • 粒子单个发射器不超过50,每个特效最好10个粒子以下,同屏不超过500(200太低难以达到),尽量减少材质和粒子种类,大小,面积,层叠数等,粒子真的很费;
动画
  • 动画压缩:无用的曲线删除,调整float精度
  • Anim. Compression:推荐使用Optimal,经过测试Optimal比Keyframe Reduction节省约50%的大小,从而可以提升加载速度。如果觉得动画质量太差,则可以退回到Keyframe Reduction甚至到Off。
  • AnimationClipPlayable替代状态机
渲染
  • 降低Draw Call
  • 使用尽量少的material
  • 不要使用alpha–test(如那些cutout shader),使用alpha-blend代替。alpha test无法实现半透的效果,只能让物体的某些部分按照alpha的值“透”或者“不透”,alpha blend可以做到半透。在移动设备上,blend具有更高的性能。这也不是绝对的,有些情况还是alpha–test性能高。
  • 不使用或少使用动态光照,使用light mapping和light probes(光照探头)
  • 避免使用实时阴影,如果必须使用实时阴影,可以在 Quality 质量设置中选择合适的阴影质量,在画面效果和渲染效率之间找到平衡点;也可以自己用Shader实现。
  • 使用GPU Instancing可以一次渲染相同网格的多个副本,但是每个实例可以有不同的参数(例如:Color或Scale),以增加变化。在渲染诸如建筑、树木、草等在场景中重复出现的事物时,GPU Instancing可以有效减少每个场景DrawCall数量,显著提升渲染性能。但是使用GPU Instancing有如下注意点:兼容的平台及API、渲染实例的网格与材质相同、Shader支持GPU Instancing、不支持SkinnedMeshRenderer
  • Shader需要兼容SRP、SRP Batcher暂时不支持粒子系统、Shader变体会打断DrawCall的合批
  • 开启多线程渲染后,主线程的渲染耗时就会有很明显下降
Shader
  • 尽量减少复杂的数学运算
  • 尽量减少Discard操作
  • 不要在Shader中添加不必要的Pass
  • 浮点类型运算:精度越低的浮点计算越快
  • shader尽量剔除背面
  • Shader.WarmupAllShaders,推荐的使用方式是游戏场景加载完之后,可以调用这个API,把你场景当中引用的Shader帮你预先编译一下,这样游戏过程当中再使用的时候就不会再去调用编译的操作,这样就避免你游戏过程当中产生Shader编译导致的卡顿。
  • Shader的编译耗时,尽可能将少量Shader放入到Always Included Shaders中,减少不必要的启动编译耗时
  • 项目前期介入美术效果制作流程,规范shader宏定义使用,防止TA为了美术效果过度使用宏定义的情况,以过往项目经验来看,到后期进行此项工作导致的资源浪费非常之大
  • 内存中ShaderLab的大小和变体成正比关系。从减少内存方面应该尽量减少变体数量
音乐
  • 长时间音乐(背景音乐)压缩格式:mp3
  • 短时间音乐(攻击等等)一般不压缩存储格式为:wav
  • Decompress On Load:适用于小文件,Compressed in Memory:使用于大文件
  • 音频一般采用率20k就够,一般情况下用单声道效果是足够的
格式
  • 更为明智的选择是尽量减少纹理的色差范围,使其尽可能使用硬件支持的压缩格式进行储存
  • 使用导入器的pre和post函数来约束,所有口头或文档的规范,都是无效的,不可能得到严格遵守,也就是说,不能用程序检测的规范都是空规范,全部做成Editor菜单检查校验
  • Texture:分辨率大小(<1024)
  • 图出成2的次方
  • Generate Mip Maps:如果不是3D模型贴图,则禁用,否则会多出约33%的内存开销。Mipmaps主要为远处的物件生成较为清晰的小贴图,减少渲染导致的画质损失。像UI贴图,则完全用不到
  • Read/Write Enable:如果你不需要运行时读取图片的像素信息的话,禁用,否则启用后纹理的内存消耗会增加一倍。
  • Android 透明使用两张ETC1压缩(更高级一张ETC1上下Alpha分离),ETC2只支持OpenGL ES3.0设备,在不支持的设备上会自动转成RGBA32/ARGB32格式,对于RGBA Compressed ETC2 8bits纹理内存占用就增大4倍
  • 老设备: iOS 透明使用一张RGBA PVRTC 4bits或RGBA16或两张RGB PVRTC Alpha分离,尽量不要使用RGBA32位
  • 新设备:iOS推荐使用ASTC,ASTC一般推荐ASTC 6x6,如果清晰度达不到需求,可以设置为ASTC 4x4。注意:ASTC仅在iPhone 6以后的设备被支持,如果需要支持iPhone 6之前的设备,可以设置为PVRTC。
  • 使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图
  • Unity 运行时通过动态图集来降低Draw Call
  • 让美术在做图的时候,做出来一律以压缩模式的纹理来走unity导真机包看效果,千万别只是把美术图导手机相册里看效果
物理
  • 真实的物理(刚体)很消耗,不要轻易使用,尽量使用自己的代码模仿假的物理
  • 如果可以,尽量不用MeshCollider,如果不能避免的话,尽量用减少Mesh的面片数,或用较少面片的代理体来代替;
脚本
  • 自建list维护管理update,减少MonoBehaviour的update使用
  • 经常需要进行随机下标访问的场合,优先选择数组(Array)或列表(List)
  • 经常需要进行查找的场合,优先选择字典(Dictionary)
  • 经常需要插入或删除的场合,优先选择链表(LinkedList)
  • 尽量少用模运算和除法运算,比如a/5f,一定要写成a*0.2f
  • 不要去频繁获取组件(GameObject.Find、GameObject.GetComponent、Camera.main、GameObject.tag),加cache
  • 不要频繁实例化和销毁对象,建议对象池管理
  • 尽量不要再update函数中做复杂计算,如有需要,可以隔N帧计算一次;执行逻辑尽量简单,就算有复杂的情况也应该把复杂的情况缓存起来
  • String的相加操作,会频繁申请内存并释放,导致gc频繁,使用System.Text.StringBuilder代替
  • 少使用的函数:pow,exp,log,cos,sin,tan等
  • 尽量不使用foreach,Unity5.4以上解决了GC问题
  • Mono内存泄漏使空闲内存减少,GC频繁,mono堆不断扩大,最终导致游戏内存占用的增大
  • 大部分mono内存泄漏的情况都是由于静态对象的引用引起
  • 不再需要的对象将其引用设置为null,使其可以被GC及时回收
  • 尽量删除脚本中为空或不需要的方法
  • 避免实例化对象时造成cpu峰值。比如某一个特定时间,集中创建很多对象,这里可以使用协程做一些间隔;
  • 使用内建数组如使用Vector3.zero而不是new Vector(0,0,0);
  • 如果需要比较距离,而非计算距离,用SqrMagnitude来替代Magnitude可以避免一次耗时的开方运算。
  • 自建事件机制取代SendMessage
  • Debug.Log异常耗时,正式发布版本时,将其关闭
  • 反射是一项异常耗时的操作,因为其需要大量的有效性验证而且无法被编译器优化。而且反射在iOS下还可能存在不能通过AOT的情况,所以我们应该尽量避免使用反射。我们可以自己建立一个字符串-类型的字典来代替反射,或者采用delegate的方式来避免反射。
  • I2LCPP自动生成C++代码提升性能
  • DOTS、ECS系统、Burst Compiler和JobSystem让游戏性能提升
  • 在Script Backend是Mono的情况下,如果选择的是旧版本里的Mono 2.x,或者新版本里的 Net 3.5(Runtime Version),那么这个值是只升不降的
  • 在Script Backend是Mono的情况下,如果选择的是Net 4.x (Runtime Version),那么这个值是可以下降的(但不确定具体是从哪个版本开始的)
  • 在Script Backend是IL2CPP的情况下,那么这个值也是可以下降的
  • 关注内存碎片化,考虑预先申请连续内存对象池使用。
Lua
  • 局部变量相对于全部变量有以下几点好处: 1. 读写更快 2. 可以避免不经意的全局变量名污染 3. 在作用域结束时,会被自动标记为垃圾,避免了内存泄漏
  • 如果同时需要创建较多的小表,我们可以通过预先填充表以避免rehash
  • 在大字符串连接中,我们应避免..。应用table来模拟buffer,然后concat得到最终字符串
  • 大量类似结构v={{x=0,y=1},{x=10,y=1},{x=0,y=11}}简化为v={{0,1},{10,1},{0,11}}一个顶点都需要一个哈希部分来储存。如果放置在数组部分中,则会减少内存的占用
  • 尽可能减少Lua和C#的耦合
  • 避免Lua和C#循环引用
  • Lua数据表:获取字段中使用最多次数的值作为默认值,并且删除默认值字段
  • 分级的lua和c#日志汇报,提供web平台分析日志
数据
  • 将CSV转化成其他存储结构,protobuf、MessagePack、Sqlite、FlatBuffer、Thrift
  • 关注反序列性能,按需加载释放数据
  • 客户端预先校验发送通讯包数据,服务端强校验
  • 通讯数据包带时间戳和序列号,加密处理(公钥加密算法)
  • 弱联网机制:离线也可以游戏,并且能将离线操作同步到后端,保证数据的一致性
资源管理
  • 个人专门负责检查和整理资源,工具完成的资源检查和设置
  • 如果某个资源大于5M,那么要仔细查看是否真的必要;
  • Resources目录下的资源不管是否被引用,都会打包进安装包,不使用的资源不要放在Resources目录下
  • 资源初始序列化的耗时,Resources下的资源数量太多,会导致启动很慢;
  • 不同目录下的相同资源文件,如果都被引用,那么都会打包进资源包,造成冗余,保证同一个资源文件在项目中只存放在一个目录位置
  • 使用对象池时,应当可以支持把物体移除屏幕,连续使用的物体可以只是移出屏幕,只有长时间不使用的物体才隐藏
  • iOS设备出现本地存档丢失:苹果设备上,当系统提示存储空间已满时,发现本地的存档会丢失。在默认情况下,本地存档放在了/Library/Caches下面,根据苹果官方的描述,放在/Library/Caches目录下的任意文件将在系统弹出存储空间将满的警告时被系统清空。将所有数据和热更新文件放在/Library/Application Support目录下,此目录下的所有文件在收到空间将满警告时不会被移除。此外,这还避免了放在Documents目录下可能会被苹果在审核中干掉的风险。
  • AssetBundle引用计数管理,分级别控制释放(瞬时释放、LRU释放、常驻、引用释放)
  • 关注AssetBunlde冗余、循环依赖打包,工具化分析
  • 按需加载、静默加载、小包、大包、渠道包、代码资源热更新、灰度测试、测试版本、线上版本、回滚机制
  • 核心资源、未来活动资源提供流程化工具加密
  • 启动到进入游戏,各个关键点提供详细的打点数据分析
  • 在Unity5.3之后,尽可能建议通过LoadFromFile(Async)来对AB进行加载
  • StreamingAssets下的AB数量也需要特别注意,不要过多,超过1w+,启动时间很有可能会受到影响
  • 分离二个工程,一个UI逻辑工程,一个无脚本应用纯资源工程。
  • AssetBundle 粒度规划:AssetBundle粒度建议不宜过细。对于AssetBundle文件的文件大小其实不必再限定于1MB之内
  • 打包流程拆解细粒度按需打包提高效率:脚本打包、包体打包、场景等资源打包、ui资源打包、数据表打包
  • 关注CDN的Cache规则
  • 关注DNS污染
美术规范、渲染分级
  • 项目定下目标人群,最低兼容手机价位,同屏可见角色数,场景大小,视野范围
  • 资源制作限制:角色的面数,角色的贴图数量、大小,角色的骨骼数量、动画数量及时长;场景的面数,场景的贴图数量、大小
  • shader:Unlit、Blinn——phong、Blinn——phong/Normal、PBR/Normal
  • 动态阴影:无、圆片、平面、实时
  • 场景光照:无、烘焙、实时
  • 后处理:Bloom、HDR、ToneMapping、MotionBlur、DOF等
  • 水深度:水面实时反射
  • 昼夜系统:无、切换、过渡切换
  • 天气系统
  • 雾效
  • 游戏分辨率
  • 场景Lod组件
  • 特效层数
  • Lod bias
  • 骨骼blend数量
  • 同屏玩家数量
打包
  • 阅读Unity release文档,有些bug是unity本身问题
  • Jenkins是大部分研发团队的首选
  • Xcode 7以上默认不支持http请求:打开Xcode,编辑Info.plist或选中target的Info栏,新增字段App Transport Security Settings,将其内键Allow Arbitrary Loads设置值为YES。
  • Android最大支持2G的包
  • Android权限问题
  • iOS刘海遮挡问题、游戏界面未占满全屏
  • 苹果禁用API:dlopen/dlsym/respondsToSelector/performSelector/method_exchangeImplementations/uiwebView(怀疑使用网页支付)。出现了【dlopen】【dlsym】,可以直接在项目中搜索“System.IO”,缩小目标范围
  • xcode9 ipa包需要1024分辨率icon
  • 涉及到的第三方支付问题全部都清理干净,敏感词:alpay pay
  • 19年9月google play强制要求app都支持64位
  • 苹果发现你有额外下载的内容,但是却没有告知用户并提示。加一个对话框,让用户选择是否加载即可
  • il2cpp到ios出ipa,可执行文件大小在ios8不能超出60m(ios7以前版本40M,ios7是60M,ios9以上代码段限制是900M)(开启Strip EngineCode)
2020年设备
  • gfxbench.com上跑分
  • 6G以上内存设备成为主流
  • 2340 X 1080占比31.4%,1920 X 1080占比22%
  • 8核占比96.3%
  • 高通CPU占比49.2%
  • OpenGL3.2占比91.29% OpenGL2.0占比0%
  • iOS一档机 iPhoneX/iPhone8 PeakFootPrint<=1100MB FPS>=25
  • iOS二档机 iPhone7/iPhone7Plus PeakFootPrint<=900MB FPS>=25
  • iOS三档机 iPhone6s/iPhone6s Plus PeakFootPrint<=800MB FPS>=18
  • Android一档机 Oppo Reno/荣耀9x 最高PSS<=1400MB FPS>=25
  • Android二档机 华为P20/Vivo x20 最高PSS<=1200MB FPS>=25
  • Android三档机 Oppo A5/荣耀畅玩7x 最高PSS<=1000MB FPS>=18
  • 低配画质需要考虑的适配问题:可能不支持Shader定义8张以上的贴图纹理、可能不支持ETC2格式的贴图纹理、可能不支持OpenGL ES3.0
  • 高配画质需要考虑的问题:支持PBR、线性空间
工具
  • Unity Profiler、Unity Memory Profiler、Xcode Instrument
  • Fiddler、Wireshark
  • RenderDoc、Graphics Frame Analyzer 2020 、Adreno Profiler SnapDragon Profiler、Nsight
  • codeworks-android
服务
  • Unity企业支持服务
  • uwa企业支持服务