Unity3D培训:MMORPG游戏优化经验分享(一)
来源:
奇酷教育 发表于:
奇酷教育-unity3D培训_unity3D游戏_unity3D教程
Unity3D培训:MMORPG游戏在优化
Unity3d游戏时,我们一般从四个方面:CPU、GPU、内存、工程配置等入手,它们都可能是影响游戏性能瓶颈的关键。
我们平常游戏的很多性能瓶颈都在CPU。例如:MONO内存分配带来CPU开销,当Mono内存从50M、60M、70M,一直增大到100M,这些内存分配都相当于CPU的开销。当在Update函数中存在比较复杂的逻辑时,很容易出现每一帧都触发内存分配,如图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来进行静态合批。同时,各个模型的材质也要针对静态合批来制作,毕竟相同材质的模型才可以合并。
动态合批对于大部分有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的性能比较差。可以通过配合射线检测和自定义高度图数据控制角色高度。
以上就是
奇酷为大家分享的“Unity3D培训:MMORPG游戏”谢谢大家观看,如果对
unity3D感兴趣的话,想学
unity3D培训的,也可以在线咨询,我们将竭诚为你解答。