2100万日活FPS手游怎样做优化?百元机稳定30帧、适配98%机型
大型手机游戏对不同手机机型的适配,始终是贯穿手游发展过程的挑战。尤其在前几年,高配置的智能机还没有普及开的时候,产品优化是研发环节的重中之重。而作为对于网络状况、数据传输和服务器架构等有着极高要求的游戏品类,FPS更是优化难度最大的品类之一。并且随着硬件发展和产业品类演化(战术竞技类对大世界内容研发的要求),还会不时伴生新的问题。
对于这些架构、适配、优化以及大世界预算管理等难题,2015年立项的《穿越火线:枪战王者》在研发和后续运营的过程中都经历到了。
而经过一系列的优化调整,《穿越火线》手游最终做到了能够良好适配市面上98%的机型、上百种模式帧率稳定在45~50帧、战术竞技模式平均29~39帧的成果。并且在去年上线“荒岛特训”玩法后DAU突破2100万。
能够达成这样的优化结果,《穿越火线》的技术团队做了哪些工作?在上周结束的Unite 2018大会上,《穿越火线》手游的主程序郭智分享了他们对Unity引擎的使用心得、优化方法等。在他看来:
传统FPS网游的网络架构,核心运算在客户端,会面临各种各样的外挂问题;服务器比较受限,难以做复杂玩法;容易受到移动网络的影响,弱网络在这种情况下不流畅。
通过单独搭建实现BS服务器,跑完全游戏逻辑。服务器和客户端会运行、执行一致的移动和逻辑计算。按照一定的帧率把数据同步到客户端。这样可以做高强度的反外挂,并且可以在弱网络下做更多决策,从而达到更良好的同步和更好的用户体验。
通过高性能的并行架构,处理多核调度和并行化,以及性能功耗均衡,便于将适配做到最优,从而做到跨平台、跨厂商、跨芯片的适配。
以下为郭智演讲精华整理。
各位好。我是腾讯穿越火线项目组的郭智。先做一下个人介绍,我是2011年加入腾讯,现在是《穿越火线:枪战王者》的主程序,也负责多款手游客户端研发工作。我有十年的工作经验,历经了MMO、FPS和端手游各类项目,有比较丰富的自研和一些商业化引擎链接的使用经验。今天主要跟大家分享在《穿越火线》游戏里所遇到的问题,以及使用Unity中我们所做的技术拓展。
一、项目技术背景介绍
先说一下整个项目技术的背景,这关乎我们在开发流程里跟随Unity版本进化的技术演化。大家可以看到,我们项目是在2015年3月份的时候开始做技术预研的,经历过五个月的时间,9月份第一次对外测试。经历两轮技术测试之后,11月进行三轮测试,2016年进入稳定运营期,2017看到发布的非常重磅的版本——生存特训模式,也就是现在非常流行的大世界的一些战术竞技玩法,在整个三年里我们技术演化经历了三个阶段。2015年属于研发期,当时我们用的Unity4.6版本,移动设备刚刚发展起来。所以说我们做的时候,面对五个核心问题:一个是游戏架构,一个是反外挂,再加上弱网络同步,还有游戏手感,最后是画面和性能。这是2015年刚开始,在研发期遇到的五个核心问题。
2016年我们处于多玩法的稳定运营期,我们尝试了十多种不同类型的玩法,每个月都会推出一些玩法给玩家,来保持热度。这些玩法也带来一些技术挑战,比如我们出一些塔防玩法、多人配合打Boss的玩法,也会有一些像生化统领的放置类和控制类玩法。2017年是开放世界技术爆发的一年,我们投入到开放世界开发,也遇到一些问题,最主要的四个问题是:大世界的五个方案、工具链、对整个Unity引擎功能做出拓展和合理使用,最后是对大世界技术里重要的性能优化与适配。
二、FPS手游网络架构最佳案例
前面说到,我们面临一个比较大的挑战就是FPS游戏的网络建构,这是业界一个标杆架构的存在。当时在研发期间,我们通过对网络调研和实地测试,可以看到,虽然无线网络延时不断向固网靠近,但仍然会有抖动和丢包的情况。弱网络情况下如果直接套用当时的一些端游技术方案,流量和电量消耗比较大,玩家体验不会那么好。所以可以看到弱网络情况下,当时的环境是这么分布的,而现在我们玩家里这么分布的。在这样的弱网络情况下,我们实现了FPS游戏实时对战,并且强调竞技,让用户有很好的体验。
我们看一下,传统FPS游戏架构是服务器不进行逻辑运算,它只对一些战斗计算和移动计算数据校验相应做转发,直接把移动、战斗计算通过服务器计算进行转发。这种方案会有以下的优势:
服务器运算逻辑比较简单,性能也会高;
开发门槛也会比较低,比如说客户端开发完直接把DL放到服务端就可以了。
但是也会遇到下面的问题:
核心的运算在客户端里,会面临各种各样的外挂问题;
服务器是比较受限,没办法做各种各样复杂的玩法;
容易受到移动网络的影响,弱网络在这种情况下不流畅。
基于这样的特点,我觉得对于品质要求高的手游来说,这个架构不适合。所以当时我提出了我们CS的架构,我们单独搭建实现我们BS服务器,跑的完全游戏逻辑。这时候服务器和客户端会运行、执行一致的移动和逻辑计算,然后按照一定的帧率把数据同步到客户端。这样可以做高强度的反外挂,并且可以在弱网络下做更多决策,从而达到更良好的同步,比较好的用户体验。
所以能看到我们有三大优势:高强度的反外挂;在弱网络下我们会做补偿,做良好的同步;会做各种各样客户端和服务端延时补偿,整个客户体验会很好。但是也有局限,就是开发效率都会比前面的方案低一点,但只有这个方案才能让我们走得更远。这就是2015年投入的整个方案。
从2015年到现在这个方案不断演化。来看一下我们的整体架构,2015年搭建的这套框架,我们是基于CS基础上搭建的这个架构图。首先我们的底层都是Unity,Unity里封装了物理引擎等一系列方案。同时我们服务器也跑的完整物理引擎,我们在服务器编译的开源的。在数据层写了相对应的导出工具,导出碰撞模型和关卡数据提供给服务端使用,并且保证一致。在逻辑层客户端是用Sesharp编写的,服务器为了高性能,使用C++,保证关键数据是一致的。客户端和服务器都会做同样的移动计算。服务器会在同样的位置进行运动伤害计算,并且通知客户端要验证结果表现。此外,我们再在上层的逻辑同步上面在做了我们移动同步的封装,基于这个架构,基本能囊括业界的所有方案。
我们可以看到当时我们的视图,一个是客户端的引擎视图,一个是服务器的物理视图,是基于我们跟米那服务器引擎合作做的服务器视图。可以看到总体来看服务器永远是权威,客户端是哑终端,这样的话可以杜绝很多外挂的出现,这是这么多年来我们对抗外挂一个比较强的手段。
基于这个架构提一下,我们接触了各种各样的同步方案,但是这些同步方案导航预测算法、影子预测算法里都会使用,但是会在大世界里遇到各种各样的挑战。比如高精确的物理同步、快速行驶的汽车。业界都会遇到如下问题,比如我们要实现载具同步的时候,会发现载具同步比较快,延时也比较高,服务器运行帧率低,最大的服务器跑30帧,这样没办法做准确的物理运算。人眼对一些不规则的速度会非常敏感,所以会看到在参与不规则物理运算的时候,一看就比较假。
基于这项特点,可以看到业界做的手游,做载具相撞的时候就停下来了。这时候我们提出了Move Blending方案,核心是通过碰撞模拟做一定的相对的混合。比如说在正常移动过程中我们走最左边的预测同步的阶段,在这个阶段里完全做,由服务器驱动做导航预测算法,做预测的同步。当预测到相对应发生碰撞的时候,我们采取物理模拟动态授权的方案,把所有物理模拟授权分发给各个客户端,然后客户端做物理授权,做自己的物理表现。这个时候客户端可以做准确的物理表现。
最后,再通过了碰撞物理模拟阶段之后,我们再回到了最后的物理混合。只有基于这种物理混合的方式,我们才能做一些复杂的物理表现和同步的方案。这是我们在2017年做的相对的技术演化。
除了整个网络架构,我们还会面临一些挑战,比如硬件发展趋势下一些高性能的并行架构,对于CF手游卖相来说,2015年是小型场景里的精确同步。比如你可以有10V10的团队爆破,最多不会超过20、30个人。竞技化要求我们能准确同步每个人行为、能够完成整个竞技化的需求。当年是这个样子的,到了2016年变成小型场景大量同屏战斗,曾经有一个场景打50个Boss,有60个怪。
到了2017年变成这样子,就是超大世界高复杂度的战斗,区域有500个关卡要素、有3千多渲染等等。在大世界里实现复杂高的东西,同时手机计算能力也是非常有限的,由于CF手游一直给人的印象性能非常好,像小米1的手机都能跑。所以当时定义大世界的方案里需要有百元机也能跑30帧这样的基调。同时还要求有充实的元素,画质品质也要优秀。再下来我们还要考虑功耗,就是在这么高要求的、画面要求计算量那么大情况下我们还可能Farpass。我们怎么解决的?这个解决方案我们也经过三个阶段的技术演化。
三、高性能并行架构
第一阶段,提出了高性能的并行架构,主要是处理多核调度和并行化,还有性能功耗均衡,然后适配也要做到最优,做到跨平台、跨厂商、跨芯片的适配。
第二阶段,基于整个高性能并行基础,做大世界的管理与剔除。也就是说我们要做到管线更加优化。比如当时提出来,在业界里处于非常顶尖地位的一个可见剔除方案,在这个方案再做大世界的管理。有以上两个基础后,我们再不断做引擎的拓展以及整个技术整体性能的调优。为什么做并行方案,芯片包含了大小核,这是HMP的架构。对于老的芯片来说,它其实都是SMP架构,也就是说2大核或者4大核的架构。可以看到无图式这样架构的特点。
如图可以得到如下的结论:不同内核它的工作内耗是不一样的,支持的计算能力不同。计算能力越强功耗越高。还有计算量走到高频的时候,像到90%的时候功耗会抖帧。所以做出了这个策略,选取合理的功耗内核,降低大核。我们基于整个Unity机制,封装了整个引擎flg的调度层。基于这上面我们封装了整个并行引擎的管线,再基于这个管线上做自己所有并行化任务,比如Gameplay并行任务等等。后面案例会涉及到这些并行任务在这儿是怎么跑起来的。
有了并行架构我们有很多优化,进入第三阶段,就是开放大世界技术的实现。大家可以看到开放大世界里一个典型场景,在这里可以看到——这个位置有60万个面、有700个DrawCall和15+人和载具。这个量级渲染主流的机型,像小米4直接渲染出来不做任何优化的话,只能泡不到15帧,所以我们要把它优化好。
我们想到的第一点是大世界里要做剔除,要有合理的剔除方案,包括遮挡剔除。合理就是指基于Unity上建造一套最合理的、在业界最顶尖的遮挡剔除系统。当时提出了CF可见性剔除方案,先回到刚才的游戏世界,把游戏世界拆开成两个世界,一个是不变的部分,这个叫静态世界;变的部分是动态世界。人,载具,特效,UI这些都是变的部分。静态部分只会在世界开始那一帧的时候是不变的,比如决定哪些场景渲染的时候这些是永远不变的,只会受摄像机的移动影响。
核心原理是:我需要有一条单独的线程做可见性检测。静态部分抛离Static Data,使用Static Data做软件光栅化,得到深度结果。查询深度结果驱动渲染,我们可以看到中间的线程是拿软件光栅化得出剔除结果决定哪些要渲,哪些渲染。如图可以看到这条管线里有两个非常重要的实现,一是数据的提取,还有是运行时,就是软件光栅化。
说一下我们的离线数据提取,这个包含业界很多Paper的算法。首先拿到原始模型,然后对这个模型做集合的简化,得到每个模型里哪些有共面的三角形。之后再做平面空间的简化,把相应的共面三角形做到平面空间的简化,最后得到平面空间简化的结果,再切割到视图上。然后依据像素范式,就是光栅化像素提取轮廓做简化,最后得出一个最优集成集合。再返回到世界空间里,就可以得到运算超快、数据表达超简单、能够快速做软件光栅化的一些数据。
经过我们提取后,389个面变成72个面。可以看到做的两个图,沙漠图和绿地图,基本上是是数据的27%和24%。整体算法是选取合理的图形算法,各个维度对模型做最极大的简化。(我们的算法)相比业界的算法还有更优的一点——业界的简化算法只能提取无法进入的建筑,会导致一些不准确的运算。如图,像即使一条缝的人也要保证他准确的结果。这样的话能够保证我们在APS里的公平性,不可能这个人裁掉的时候打不到。
总结一下我们的可建性剔除方案,就是小型场景还会有Unity的剔除,大世界会用自己的剔除方案。剔除方案其实是可建性剔除是纯CPU方案,它适用所有移动端硬件,可以做良好的引擎集成,就引擎做并行化、优化,其实就像我们基于Unity做一些Jobsystem优化。还有做里面封装一个共线剔除,做最优的LD管理。同时它的运行速度非常快,得到最优的剔除结果,同时它能自动化生成一些Occuder,也可以优化美术师的一些工作。
经过软件光栅化剔除后,还会面临一些问题。比如Drawcall还很高,美术为了品质会增加资源、复杂度高、动态控制加载距离不同倍镜等需求。要控制住高中低配硬件流畅运行,首先需要有一个整体空间管理方案,基于管理方案我们先做相对应的资源优化,提取收益最高的LD集合,LD集合代替单个物体LD。执行完之后我们会进行性能的规范,会使用规范算法调整策略。比如说我们会执行严格的预算,考虑每一块场景里面的一些性能损耗是什么样、性能参数是怎样的、它是不是符合性能规范,会做高层次规划算法。我们会做自动化测试、专业测试和上报的监控。基于这管线不断的迭代和轮训,我们可以得到最合理的场景。
四、开放大世界技术的实现
再细说一下我们空间管理是怎么做的。手游做空间划分,可以划分物理层,细节层等等。每一层可以单独定义每个Care的力度,比如它的加载距离是50×50,还是100×100,高度是,三维高度每一百米要做一个划分,像高度是100×100×100,进行距处的分组,如图,绿色是物理层,黄色是环境层,也就是说我们的建筑,蓝色是细节的小物件和细节的表现,每一层走可以单独进行它的一些相对一些定义,然后做管理,最后在这个层次里把空间做出很好的划分。
之后做距处分组,这个其实就是整个距处资源优化过程。整个过程就是选取LD距处集合合并,然后预计算它的收益比。也就是说,我们对这些距处进行Mach后,它的收益和损耗到底是怎样的。最后再根据算出来的每个收益的损耗,要用算法得出最优收益比怎么跟LD集合。
比如说,可以看到我们在不同Tele里是不是要把这两个物件合并起来,可以根据刚才说的预算分值进行衡量,如果分值比较高、比较合理,就合起来。但如果合起来Draw Call面少了,但是收益低,就不合。永远通过这些运算得出合理的大世界的预算管理。有了这套后再执行距处替换LD策略——就是在最近的时候可以看到做模型的LD切换,在稍远的距离里我们会使用我们刚才合并的替换模型;在最远的距离,其实我们还是会对不同LD级别替换不同的模型。这样能让我们整个损耗达到最少。核心原理就是说“越近的地方,越用最精简的模型;越远的地方,不同的LD用我们距处优化模型。”这样可以得到最合理的资源优化。过程需要不断的测试和调试,不断地调整策略,然后达到不同平台里最优的表现。
大家可以看到如下这个图。这个图里就是说,在离线烘焙阶段,我们会算站在每个地图角落里所看到的三角形、看到的面数、看到的数据。可以算出来每一块里的收益和损耗比,然后做离线的规划。用我们的离线规划算法做离线规划,再对它的资源做合并。最后核心还是要保证每个位置里性能是完全可控的。基于完全可控的地形,再把一些可控的结果做相对的LD替换。
五、引擎功能拓展及整体性能调优
基于这个开放大世界的管理之后,我们其实就进入了技术演化的第三个阶段。其实Unity引擎有一个很重要的理念,就是要让它变成自己手上的引擎。团队组建这么多年以来,一直在演化。我们要成为国内最顶尖的设计游戏团队,就要对整个引擎进行不断拓展和性能调优。我做了如下的事情,这是我们整体的架构图,可以看到分层是非常合理、严谨的。首先,最下面是平台独立层。封装了我们UDP整个网络层。刚才说到的网络同步方案,是基于这个UDP网络层做网络同步的。同时,我们也会封装自己的多线程调度库和基于Unity本身的多线程方案封装自己多线程调库。还会封装我们数学库和指令库,同时也会对一些手柄和平台做接入。这是我们平台层。
再是Unity,我们CF手游高性能下做的。基于做的底层各种各样的渲染优化,大世界里的植被系统的渲染应该怎么做,还有Inpost的LD怎么做,多线程的Package怎么做,还有我刚才提到的整个大世界的管理方案,和系统整个方案,同时骨骼动画来可以做GPU Skin等的操作。我们也对它整个物理算法做出了封装,保证个服务端一致。
基于这个技术引擎层可以推出我们GamePlay工作架构流,像我们工作逻辑是怎样的、前后端交互框架怎样的,再加上大世界的管理都在这上面。基于这个工作流,我们在上面开发了现在不止50个游戏模式了,有超过100多个关卡、500多种枪械,还有各种各样的人物形象和载具。任何需求对我们团队都没问题。我们也会提出自己一些创新的突破性项目。
在我介绍里所说的PPS数学和物理库我们做出一定的封装。我们提出业界最快、最适合移动射击游戏的数学物理库。我们其实是基于Unity引擎数据库进行封装的,比如我们会PPS检测,像球、胶囊体的算法得到最优。可以重新定义射击游戏的物理的模型,然后重新定义射击手游里的一些标杆、一些手感。比如支持数百种枪械射击手感的数据模型,也可以定义认为的标准。我们多款移动射击游戏其实是性能和手机的基石,成为了整个行业的标杆。同时我们图印象也不断做拓展,保证高中低配效果都会是最优。可以看到我们这个屏幕都非常高清了,大家可能还是看不出来,我们哪个是高配、哪个中配、哪个是低配,通过不断的图形优化,让品质永远达到最优。
有了图形之后我们不断的会对情况做出优化。我们当前基于Unity做到了适配市面上98%的一些市面机器。安卓上百种模式针对稳定在45帧。我们生存模式,平均帧率到29帧。上百种模式帧率到50。我们可以集成一些电量策略盒子和集成了现在整个Vetext性能测试。还有一些适配做支持。所以说我们永远保证了性能是最优的。