导读:Unity3D

导读:Unity3D 游戏产品优化技术综述

序言

游戏产品在研发过程中都会涉及到产品优化。这是产品迈不过去的坎,因为游戏产品需要适配不同的设备,而设备从硬件到软件彼此差别比较大,这也导致了游戏产品在不同的设备就会有不同的表现,有的设备运行顺畅,有的就出现卡顿,或者出现画面渲染问题等,而我们就是要找到这些问题根源,从而解决它们。

关于优化 Unity 游戏产品,这个题目所蕴含的内容很多,我们开发一款产品,需要一整套流程才能保证产品顺利出来,本达人课主要分为开发工具的运用、资源优化、代码优化、Shader 的使用、算法运用等几大部分。站在开发者的角度去思考解决问题,先从产品的每个细节入手,如果处理不好,一时不会影响效率,但积少成多就会影响到游戏效率的运行。目前市面上大部分游戏产品的开发都是基于 Unity 引擎开发的,遇到需要优化最多的问题是各种机型的适配,这个适配不是简单的 UI 适配,而是运行效率适配,尤其适配不同厂家的 Android 系统的机型,由于他们使用的芯片不同,系统不同,也决定了要使用不同的解决方案做适配,在做适配之前先给读者看看2017年官方发布的手机使用的芯片分布表,见下图。

enter image description here

这些芯片是目前市场使用最多的芯片,我们不是搞硬件的,它们的内部原理对我们来说是黑盒子,但是它们为我们解决手机适配提供依据,我们可以根据手机使用的硬件对它们进行分类,这样有助于我们定位问题。

在解决手机问题上,大部分开发者会认为,无非是资源、代码、Shader 编程。作为程序员来说,更愿意把这些问题推给美术资源,通常的做法就是让美术模型减面,减少使用的美术材质,同时对图片进行压缩,在游戏中对动态网格进行合并,对静态网格做 Static 处理,减少摄像机裁剪距离等等,但是这样的处理方式一是降低了游戏品质,二是根本没有实质性的解决问题,这也是项目开发中遇到卡顿、卡帧问题的主要原因。虽然开发者解决了一部分机型适配,另一部分机型还是有问题,最终适配还是有问题。其实归根结底是没有从根上解决问题,在这里给读者解释一个误区,优化产品并不是说,在项目开始阶段程序逻辑就可以随便乱写,模型面数没有限制,优化并不是在项目结尾时再进行的,它是一直贯穿于项目从开始到结束。而且我们优化产品,要养成好的开发习惯,这个习惯并不单单指的是编码,从开发产品工具的使用到产品的管理测试,这一整套作为开发者都需要掌握的。那如何才能做到呢?我们主要从以下几个步骤讲起,只要我们贯彻好了,项目优化就好做多了。

开发工具的运用

我们开发游戏产品都是团队作业,美术、程序、策划都需要项目管理工具,美术提交资源,策划提交文案,程序提交代码。我们主要使用的项目管理工具有 SVN、Github,二者选择其一,或者结合使用,以我们项目组的开发为例,以前我们使用 SVN 作为项目管理工具。在项目初期使用时,没有问题,但是到了后期,项目要分版本开发,大家知道项目上线后,要接不同渠道的 SDK,最多时接了50个左右,刚开始也使用了 SVN 的 Branch 分支,但是每次更新速度都很慢,而且也不利于多人开发,被折磨了一段时间后,我们将其换成了 Github,感觉轻松多了。当然美术和策划还是使用 SVN,相对来说,Github 操作比 SVN 复杂一些,而美术和策划也不需要像程序一样搞几十个版本。另外,我们项目对资源和代码逻辑做了分离,逻辑代码都是动态的绑定到资源对象上,这是因为资源经常变动,经常修改,如果上面挂脚本,一旦出现漏挂,会出现一些问题,还需要人去查找这样的问题,浪费时间,所以最好不要直接绑定逻辑脚本。

上面介绍了项目管理工具,再说说项目跟踪工具,策划、美术、程序在项目开发协作方面也需要配合,比如策划给美术和程序下达任务以及完成时间节点。作为策划,他要清楚美术和程序的进展情况,如果我们只是靠口头去传达,容易出现扯皮的事情发生,而且时间和质量不好把控,延误肯定会发生的。这就需要用到项目跟踪工具,我们的项目开发工具使用的是 JIRA 或者禅道等等,策划会将任务传到 JIRA,主程分配任务,程序领任务,策划可以定期在上面查看各个任务进度,及时发现任务是否有延期的隐患,及时纠正。

项目跟踪也有了,项目顺利进行了,接下来,就是测试了。众所周知,使用 Unity 打包,如果工程很大,每次编译都需要耗费一些时间,而且策划或者测试会不定期的让程序打个包给他看看,这样容易打断程序员开发思路。另外,项目在每个节点也需要发包测试。总之,打包的次数非常多,如果不将其工具化,程序员的时间都将浪费在这上面,每次打包资源整合都会耗费程序员的大量时间。鉴于手动打包总总不利因素,自动打包工具应运而生,我们可以使用一个自动打包工具 Jenkins,它可以采用 Shell 脚本编程,自动更新 SVN 或者 Git 服务器的内容,打包人员只需要点一下按钮,服务器就可以生成当前最新的包,傻瓜式操作,将其下载下来安装测试,非常方便。这样,打包的事情策划和美术都可以操作,彻底解放了程序。

最后,我们在项目开发时,会根据策划需求自己写一些依附于 Unity 编辑器的工具,这个也是必须的,比如资源打包、文本文件处理、资源检查、包体大小检测、单元测试等等。作为程序开发者,工具当然是越多越好,如果一款游戏只需要操作工具就可以生成,程序员只需要专心维护工具,而策划只需要专心使用工具,美术制作模型,一款产品很快就可以研发出来,类似汽车生产的流水线作业。

游戏中资源优化

游戏资源的优化,这个是老生常谈的问题了,无非是从资源面数、材质数量、骨骼动画数量、特效粒子数、图片的压缩、网格合并等等入手,网上这方面的介绍比较多。以上这些操作可以帮助我们解决部分问题,但是除了这些还会有其他的问题。这里举个例子,如果我们场景中有很多草呢?或者说有很多透贴的树木呢?场景中很多怪物,类似国战这样类型的游戏,双方都需要很多 NPC 开战等等。这些 NPC 是不可以用网格合并的,因为它们是单个的个体,有自己的属性,是可以被击杀的。另外透贴的树木也是非常耗时的,其实这种问题也是可以解决的,利用 GPU 编程实现,具体如何解决会在后面的章节介绍。

另外资源需要将其打包成 Assetbundle,一部分资源会上传到服务器上,进行资源的更新,这就要涉及到 AssetBundle 包体的数量、包体的大小、打包的依赖、内存的管理等等,作为开发者都需要认真考虑的。详情请查看下一篇“资源优化,教你合理调配游戏资源”这一课程。

合理运用 Shader

Shader 渲染提升了游戏的品质,但是如果过渡使用同样会引起帧数的降低、卡帧情况的发生。举个简单的例子,游戏中的模型会涉及到双面显示的,开发者一般的做法是在 Shader 中加一条语句 Cull off 就可以开启双面显示。但是对于 GPU 来说就是一种消耗,如果在游戏中使用过多,会引起帧数下降,其实能美术解决的问题就不要用程序解决,这只需要美术再做一个面就可以实现,而不是使用 Shader 去处理,还有在场景中大量使用透贴,也是一种消耗。比如下面游戏使用的草和树木,如果处理不当就会在手机端出现卡顿情况,如下图所示。

enter image description here

另外,在使用 Unity 自带的 Shader 或者使用网上提供的 Shader,复杂的处理会涉及一些函数的使用,比如exp、log等,如果使用过多也会影响执行效率,解决办法是将它们适当地替换掉。如果你的产品面向的是高端机型,效率没啥影响可以直接忽略。还有 Shader 中变量的声明,从精度来说,float、half、fixed 依次降低,但是它们的效率也是依次升高,如果游戏品质在可接受的情况下,可以适当的使用精度低的变量类型,这样游戏运行效率会得到保证,同样 float2、half2、fixed2、float3、half3、 fixed3、float4、half4、fixed4 跟前面的类似。做 AR 产品时,就是通过降低变量精度实现适配的,同时品质在可接受范围内。我们在项目开发中还用了实时阴影、残影、角色的透明处理等等,都是使用 Shader 编程实现的。

代码优化

一提起手机效率优化,诸位是不是都感觉都头疼,有时找了半天都无处下手,或者是根本不知道哪里出的问题,在此我们先分析一下 Unity 的内存管理。

先从代码编程说起,编码的细节问题也是面试时经常遇到的,这里也给读者举几个编码的例子,比如 string 字符串链接——“+”号链接,但是使用“+”号会产生副本,占用内存,而如果使用 StringBulider 中的 append 可以避免副本的产生;还有单例类和静态类的区别,有时程序员为了调用函数方便,代码中会涉及到很多静态类,静态类是常驻内存的,过多的使用也会占内存的,程序中不易多用;另外还有结构体与类的区别,值类型和引用类型等等。虽然这些都是一些细节问题或者说小问题,但是作为程序开发者,都必须要掌握的。另外,很多程序喜欢使用插件解决问题,插件是可以帮助解决问题但是也带来了隐患,举个例子,行为树 BehaveTree 插件,因为它内部为我们封装好了,我们只需要用它提供的可视化界面拖拖拽拽就可以完成行为的转换,如下图所示。

enter image description here

在游戏中使用过多的行为树,因为在运行时它要不停的进行状态切换,同样在手机端会出现卡顿情况,解决方案其实比较简单,简化行为树逻辑,同时使用 FSM 或者 Switch 语句代替,我在做赛车游戏时遇到行为树导致卡顿的问题,就是使用上述方式解决的。

关于代码优化,就不得不提架构设计,这个是一直重点强调的问题,先给读者介绍常用的架构设计——ECS 架构,详情可以访问这个网址

另外比较重要的架构设计是 MVC 和 FSM 结合着 State 状态设计模式使用,例如,UI 的设计,角色动作状态切换等;Observer 观察者模式在游戏中也经常使用,比如足球类游戏。

之前撰写的《Unity 3D 网络游戏架构设计》达人课中给读者介绍了网络架构设计模式就是按照这个设计的,有兴趣的读者可以看看。

最后再介绍一个经常使用的架构设计是委托,委托方式很容易将代码模块化,这对开发者也是一种架构选择。

算法运用

在游戏开发中,核心玩法很多都是基于算法实现的,作为程序员要学会学以致用,将书本知识用到产品开发中。很多人喜欢抱怨大学课本中学到的知识,没有任何用处,其实在游戏开发中,经常使用的算法,包括我们大学时学习的数据结构。作为程序员必备的技能,产品开发中,我们经常会用到队列、栈这些算法。我们要使用算法,就必须了解这些数据结构的特性,比如队列是先进先出,栈是后进先出。

另外用得比较复杂的算法,使用最多的是 A算法和 AI 算法。作为大部头的 MMO 产品,A算法是必须要掌握的,它不同于 Unity 自带的 NavMesh 算法。NavMesh 算法对 3D 场景应用的非常好,但是对 2D 游戏并不能完全满足需求,我们的游戏服务器也需要 A*算法的支持,这样才可以做到服务器与客户端之间的同步,插值运算比如常用的抛物线插值,角色同步时的线性插值,刀光拖尾的 Bezier 曲线插值或者 B 样条曲线插值等。同样,我们还需要重点掌握一些知识,这些知识帮助我们解决了很多问题——向量计算和矩阵计算。关于向量计算,举个例子,我们在使用导弹攻击目标时,导弹要一直跟踪目标,这就要求导弹的头一直对准物体,这会涉及到向量的点乘和叉乘运算。矩阵转换,同样也很重要,矩阵运算除了在固定流水线和可编程流水线应用外,我们还会用它做一些颜色格式的转换,比如摄像机获取到的视频数据格式是 YUV,我们要将其转化成 RGB 在 Unity 中显示出来。给读者看一下转换的代码。

varying vec2 v_texCoord;
uniform sampler2D yTexture; 
uniform sampler2D uvTexture;
**const mat3 yuv2rgb = mat3(
                        1, 0, 1.2802,
                        1, -0.214821, -0.380589,
                        1, 2.127982, 0
                        );**

void main() {    
    vec3 yuv = vec3(
                1.1643 * (texture2D(yTexture, v_texCoord).r - 0.0627),
                texture2D(uvTexture, v_texCoord).a - 0.5,
                texture2D(uvTexture, v_texCoord).r - 0.5
                );
    vec3 rgb = yuv * yuv2rgb;
    gl_FragColor = vec4(rgb, 1.0);
}

总结

授人以鱼不如授人以渔,关于优化的详细介绍,会在后面章节中介绍。本教程先从工具的使用讲起,逐步深入。

上一篇
下一篇
目录