游戏开发论坛

 找回密码
 立即注册
搜索
查看: 4493|回复: 1

[转载] 1.7亿DAU背后,《跳一跳》踩过了那些坑(上)

[复制链接]

8717

主题

8783

帖子

1万

积分

版主

Rank: 7Rank: 7Rank: 7

积分
11952
发表于 2018-7-11 17:09:38 | 显示全部楼层 |阅读模式
文/孙汝亮三世

编者按:开年以来,小游戏一直是行业所关注的全新领域,作为小游戏代表的《跳一跳》更是用其1.7亿DAU惊人的成绩牵动着整个行业的目光。在今天下午举办的“小游戏 大能量”微信公开课小游戏·产品专场上,《跳一跳》研发团队,就《跳一跳》在研发过程中在性能、网络和后台反面遇到的困难和其应对的“突围之道”进行了分享。

以下为演讲实录。

开发者们下午好!

我是微信讲师徐嘉键,我来分享话题是《跳一跳》的突围之路,看一看《跳一跳》是怎么做优化的。大家可能好奇优化怎么会称为突围之路,因为其实作为研发团队,我们也和在座的各位一样碰到了很多问题,比如性能怎么优化,卡顿怎么解决的,网络问题怎么优化体验等等,这些我们会进行一个分享。

首先想跟大家回顾下《跳一跳》的发展历程,从2017年12月18日发布,7天不到就DAU破亿。在2018年春节我们发布了他的社交玩法接龙模式。

今天是我今年第三次来讲这个技术分享了,第一次是年初的微信公开课,第二次是五月的QQ技术论坛,所以这次大会找我来我说我应该用什么方式来分享呢?难道要把年初的PPT再讲一次吗,所以这次我想讲的更深入一些。

之前我只讲了阴影优化,其实我们还做了很多渲染方面优化。比如说网络,我只提了网络分片,在这一点还有很多问题。比如说弱网怎么解决,如何让用户在弱网情况下也可以保证流畅。其次就是1个亿的用户情况下怎么去保证用户体验,以及最后,它的后台是怎么能保证游戏的正常运作的呢?

今天我们团队会有像以上三点来跟大家分享这一块,那么现在有请我们的第一位讲师,讲一讲性能优化之道。

性能:合并渲染 平衡体验

大家好,我是微信公开课讲师李一奇。

首先,我介绍一下《跳一跳》的框架,每一个框架有不同的优化接口去做优化。

《跳一跳》的定位是一个3D游戏,当初我们在最开始市面上选择了三款框架,都支持3D游戏开发,分别是Babylon.js、Laya.js、Three.js。Babylon.js是微软开发的用于游戏的3D渲染引擎,但Laya.js是非常适配小游戏的框架,Three.js是纯粹的渲染引擎。

微信图片_20180711165925.jpg

我们分析一下他们的优劣点:Babylon.js除了渲染功能还有碰撞引擎,还有交互系统;对于Laya.js来说当时主要适用2D游戏,对于3D游戏缺少一些支持,所以我们在开发《跳一跳》时遇到了一些问题;最后,我们就定位到了Three.js,它是性能非常优异的纯渲染引擎,它不像Babylon.js集成了物理引擎和交互系统,但是它能够在完美实现设计稿的同时保持高的性能,因为它对于WI-FI信号的高速扩展都几乎支持了。对《跳一跳》来说,碰撞检测以及物理效果其实并不复杂,所以我们没有必要去选择引入物理引擎来支撑,于是综合考量之后我们选择了Three.js。

这里要说的是框架没有优劣之分,但是要选择最适合自己我们的。

微信图片_20180711165931.jpg

这是《跳一跳》的开发历程,从第一个版本里的方块小人,到黄色小鸭子,到白色小人,再到绿色气泡狗,最后到我们的小黑人,其实每个版本都有它各自的特色。大家其实也可以看到这里面有非常多不同的效果,例如水波效果,云彩效果以及发光效果等。这些都证明Three.js是一个非常棒的框架。

然后大家再聚焦于我们每一个版本里的这些模型,其实模型它还是挺多变的,但构建模型的方式是不变的,我们要说的是版本的构建中我们都遇到了共性问题,我们在每个版本中都遇到了性能瓶颈。我们当时在一些低端机测试时,发现小人卡顿,但是这非常影响用户体验,尤其是对于我们需要在微信平台上发布。

微信的庞大用户体量之下,是有着有许多低端用户的,那我们要照顾他们的用户体验。所以性能体验是我们要解决的重要课题。

我们研究一下哪些方面会影响卡顿,一是顶点数量。我们以一个球体为例子,我们想在屏幕上选出一个球体,其实我们做的是什么?我们其实是创造了非常多的顶点。这些顶点它构成了线,然后线构成了面,我们再加上它各自的颜色,它在屏幕上就是一个球体了。

这里我们要怎么做一个新的优化了?对于游戏来说,你顶点数量越少,其实你的游戏性能可能就会越高,但你顶点数量太少,你的模型可能就不够精细。所以在这里我们其实是找到一个平衡点,你的顶点数够少,我们要在这里找一个平衡点,顶点数要够少,但是你的球看起来又够圆。

第二是渲染命令数量,渲染命令指的是CPU对底层图形绘制接口的调用,命令CPU执行绘制操作。就是我想要在屏幕上显示一个球体,那么CPU就需要把球体的几何信息,这些顶点的位置、大小、颜色等信息发送给GPU,GPU收到这些信息之后再处理,然后显示在屏幕上的过程。但这个渲染命令对于CPU、GPU来说是非常耗费性能的,所以说渲染命令对于游戏来说是越少越好的。

那我们这里就介绍下怎么减少渲染命令。首先说黑色小人的模型构建,模型构建就是引起渲染命令过多的一个罪魁祸首。大家可以看到我们在最初构建《跳一跳》模型的时候,最初黑色小人分成四块,头部由一个圆球体构成,身体分别由一个椭球和两个圆台组成。

微信图片_20180711165933.jpg

按照之前说的,把这四个几何体渲染到屏幕上,CPU其实是需要发送四个渲染命令的。黑色小人需要的渲染命令已经这么多了,我们《跳一跳》的场景那么大,渲染命令可能要上百个,这样对于性能来说是无法接受的。那我们该如何去减少渲染命令呢?

我们可以感知到身体其实本来就应该是一体的,为了构建方便,才将它分为了三个这样的基础几何体,那其实我们完全可以把它合并起来。我们调用C.js的接口可以把他合并起来,这样使得渲染从四个变成了两个。

微信图片_20180711165936.jpg

为什么头部无法合并?因为合并操作只是限于逻辑上一体的物体,而身体的上部分和下部分不会往不同的方向移动,也不会往不同的地方去压缩,但头就不一样了,头会往和身体的方向运动,但你合并之后,集合体就被视为一体了,你无法单独操纵合并之前的那些集合体,因此我们不能把头合并到身体里。

那么对于无法大量合并的内容我们怎么优化呢?大家可以会好奇,我们分析一下渲染命令,它本质上其实就是把集合体的信息发送给CPU,这些信息里最重要的有位置信息、颜色信息、法向量的信息。

微信图片_20180711165937.jpg

这三个例子分成了三个命令把这些信息发送给了CPU,实际上我们是可以把这些信息全部收集起来做为一个信息发送给CPU的,我们把相关的信息都抽取出来,打包丢给CPU处理。GPU就同时把这三个例子全部显现出来。

微信图片_20180711165939.jpg

用这种合并属性的方式,其实这里面无论多个例子最终只会变成一个渲染命令发给GPU,所以其渲染性能是非常高的。这种合并属性的方法本质上和合并几何体实际上是一样的,只是应用层面的难易不同。合并结合体只要创建出几何体,调用一个接口就可以完成了,但是合并属性的方式需要了解到集合体的内部属性,需要手动抽出来再合并,打包之后在给GPU,这是难易程度上的差别。

目前为止,我们已经把《跳一跳》的顶点数还有渲染命令都限制到了一个非常小的范围了,那是不是性能就足够好了呢?并没有,在后续的兼容性测试中,我们发现还是有很多低端机型挺卡的。

大家知道,我们直接用小程序开发者工具就可以看到CPU和GPU的负载,我们发现在1-17毫秒的过程中,CPU的负载达到了5.5毫秒,我们有理由这5.5毫秒就是导致卡顿的原因,于是我们就分析一下什么事情还会在CPU上做。

所以我们就聚焦到另一个特效——阴影渲染。阴影就是在GPU上绘制制作的。为了验证猜想,我们就直接把阴影关掉,关掉之后是性能提升非常显著,直接从5.5毫秒降到了2.3毫秒,在真机测试上我们也发现低端机型也并不卡了,我们的猜想就成立了,确实是阴影卡顿。

微信图片_20180711165941.jpg

但是阴影对于3D游戏来说其实是非常重要的,没有阴影可能会导致画面不生动。那我们首先就给《跳一跳》小人加上阴影,因为小人实在是太重要了,它在画面中它可以跳跃,可以旋转,可以压缩,没有阴影会很奇怪。我们给小人加上阴影之后,发现由于小人范围比较少,并没有像《跳一跳》场景那么大的一个影响,其实它只是给CPU的负载并不多,只增加了0.6毫秒,到了2.9毫秒,低端机上也并不卡。

基座怎么办?我们发现在《跳一跳》游戏过程当中,基座并没有像小人那样那么多变,它其实就像一个静态贴图一样。那么我们能不能直接用静态贴图去替代动态渲染的阴影了,我们试一下直接把一个静态贴图贴到了我们的基座底下,然后手动的去模拟它的移动、它的压缩,最终我们发现,其实贴图这个过程并不影响性能,贴上之后只对GPU的负载造成了2.9毫秒。但对于小人来说这种方法就不适用,因为小人的阴影的变化实在是太多了,如果用贴图的方式其实是不太可取的。

微信图片_20180711165943.jpg

最终和顶点数量一样,我们要在性能和体验上也找到了一个平衡。所以我们对于《跳一跳》中的阴影,就是以半实时渲染和半阴影贴图这样的方式来去做阴影。最终通过这两个优化之后小人在跳跃过程中是非常流畅了。

最后,在提升性能这块而总结下的话就是合并渲染,平衡体验。这是我对于性能优化的分享。

网络:及时纠错 实时可靠

大家好我是微信公开课讲师赖荣钦。接下来我来为大家分享《跳一跳》在网络同步上遇到哪些问题和我们如何应对的。

基于微信平台的小游戏,有着得天独厚的社交属性,在《跳一跳》上我们有“围观”和“接龙”两种玩法。我们针对每种玩法的特点不同,采用了不同的实现方式,目的是为了在保证用户的体验下,我们的开发成本能够尽量的低。而这其中最重要的一环是网络同步的实现方式。

在此我会以先“围观”然后再“接龙”的来给就两种玩法分别来为大家讲述一下。

围观模式标志着《跳一跳》从单人游戏到多人游戏的转变,它本质其实是一个比较小众的一个玩法,我们不希望做的很复杂,我们希望在保证用户体验和游戏可靠性的基础上,让客户端和服务器的逻辑能够尽量的轻和简单。

基于这种思路有了下面的同步方案:当某一个玩家进行操作之后,他会把他的游戏状态数据发送给服务器,这里的游戏状态数据包括了基座的位置与样式,以及小人的起跳点、落脚点,还有它的整个跳跃过程的信息。而服务器对于玩家传送过来的数据,只是单纯的进行转发,发送给其他的围观者,其他围观者在收到了数据之后,就会根据游戏状态信息去还原整个游戏场景,来播放玩家跳跃的过程,

微信图片_20180711165945.jpg

我们来看一下这个同步方案,首先它的服务器逻辑非常简单,只是单纯地做转发的过程,其次是这个方案的可用性非常高,为什么?因为我们每一次同步的数据都包含了能够还原整个场景的所有信息,所以对于客户端来说,只要接收到什么把它按序排好,然后播什么就可以了。如果在网络条件不好的状态下,哪怕是丢了一两个数据,它也是能够正常稳定的去继续围观下去的。这是我们围观模式的方案。

接下来我们讲讲接龙模式,首先我们谈到网络同步的话,一般会考虑两点:第一点是实时性,第二点是高可用性。较高的实时性往往带来的是成本的增加,而在复杂的网络环境下所带来一些问题要如何去应对,则是高可用性的问题。

微信图片_20180711165946.jpg

让我们来回忆一下刚才围观模式的同步策略。当玩家A开始按压,一直到按压操作结束之后,才向玩家B同步操作的信息,这时玩家B才开始播放按压的动画,一直到它播放结束。

微信图片_20180711165948.jpg

这个方式最大的问题是玩家B在接到 A操作信息的时候有延迟。这对于围观模式可以接受,但是接龙模式,我们肯定希望玩家B开始按压后A那里就有动画了,所以我们采取了这样的方案:

微信图片_20180711165951.jpg

当玩家A开始按压的时候,此时就不停的告诉玩家B说,我是处于开始按压操作的玩家,B在接受到玩家A的同步信息之后就开始播放按压动画,然后当玩家A松手了之后,玩家B播放按压动画结束,开始起跳。

微信图片_20180711165953.jpg

对比一下两种同步方式,可以看到我们节省的这段时间整好是玩家A的操作时长,这就是接龙的高实时性方案。但是我们再回过头看一下,又发现了另外一个现象,在方案一的时候,同步频率是很低的,用户每次操作只同步一次;在我们的高实时性同步方案上面的话,同步频率会变得非常高。所以我们如果像围观模式一样,每次都去同步整个游戏的状态、信息的话,流量会变得非常大,所以在接龙模式下面,我们每次同步的仅仅是用户的操作信息。

总的下来,虽然说我们的同步频率增高了,但是因为我们每次同步的信息量比较少,所以流量还是得到了控制。这个就是我们接龙模式实质性的方案。

微信图片_20180711165954.jpg

接下来说高可用性,在接龙模式的高可用行上遇到了两个问题:一是公平性问题,由谁来决定?游戏的进行由谁来判定某用户是否淘汰了,以及说游戏是否已经结束了,说白了就是谁说了算的问题,在这个问题上面,首先我们肯定既不想让客户端去说了算(我们无法相信客户端),又不想在服务器去额外的跑一份游戏逻辑去校验用户的操作,所以这是我们面临的比较大的问题;二是一个稳定性的问题,如何在一个复杂的网络状态下,要我们的游戏能够稳定正常的一个运行下去。

首先让我们来看一看公平性问题,我们是怎么解决的?公平性问题,我们采用了这样的一个同步策略:

微信图片_20180711165956.jpg

首先当操作玩家的操作指令结束之后,会把操作指令以及指令在客户端上计算的结果发送给服务器,服务器会保存,并且会记录指令和结果,但是却只把操作玩家的一个指令同步给其他等待玩家,其他等待玩家在接收到操作玩家的指令的操作指令之后,会在他们各自的客户端再进行一次计算,把它们的计算校验结果再发布一遍。服务器根据这几个玩家的各个客户端在相同指令下计算的结果,来进行游戏进程的一个推进,来判断谁被淘汰,具体服务器是如何进程的,我们可以期待下一位讲师为大家分享。

我们回来过来头来看一下这个方案,首先它解决的第一个问题,谁说了算,我们要相信谁?我们肯定是去相信服务器。第二个问题就是服务器上面需要再跑一份游戏逻辑进行校正吗?不,我们把计算的过程分发给了每一个客户端去进行。

这也引来了下一个问题,我们怎么样才能保证每一个客户端它的计算结果一致?特别是在我们玩《跳一跳》的时候会生成下一个基座,在多个客户端上面,我们要怎样保证多个客户端上面基座生成是一致的呢?

微信图片_20180711165958.jpg

这里我们采用的解决方案是开始在接入游戏开始之前,服务器会向所有的客户端同步一个相同的因子,每一个客户端在生成下一个基座的时候,都必须使用这一相同的因子,在相同的函数里进行计算,然后输出相同的基座信息。总的来说就是相同的输入得到了相同结果。

以上就是我们在接龙模式中解决公平性的方案。接下来我们再考虑稳定性。对稳定性我们换一个视角,我们从单一客户端上面来看整个接龙模式的运行过程。

假设一个客户端,处于A0的状态,它接收到了指令1,此时客户端就会基于A0的状态以及指令1来推演出下一个状态,即A1,以此类推,当处于A1的状态时候,接触到了指令2,客户端就会基于A1和指令2这两个条件来推演出AA的状态……

微信图片_20180711165959.jpg

这里我想告诉大家,与围观玩法不同的是,在接龙模式上面,我们所有的指令都是非常重要、不可或缺的。我们可以试想这样场景,当我们的客户端在处于A0的状态的时候,我们的网络条件非常的差或者我们切出了小游戏,小程序客户端会在5秒之后把小程序的网络给禁掉,禁止传输信息,所以是接触不到指令1的,整个游戏无法进行下去了。

此时,在《跳一跳》中的应对方案是:我们会有很多的判断逻辑去检测用户它的客户端状态异常的情况,当我们检测到客户端运行状态异常的时候,就会向服务器请求最新的指令。假设现在是状态A0而服务器上面最新的游戏状态是A2,我们就会去请求指令1和指令2,然后客户端会在A0的情况下做出快进的操作,立马就计算出A2应有的状态,把当前错误的游戏状态矫正回来,快进到最新的A2状态,这就保证了高可用性。

微信图片_20180711170001.jpg

无论是网络延迟比较大,是进度比人慢很多,还是网络已经断开,然后再重新连接回来的,我们高可用性的应对策略都可以解决。

微信图片_20180711170003.jpg

总的来说我今天的分享可以用这么三个词概括:首先是因景制宜,根据不同的一个业务场景来选择不同的实现方案,从而降低成本;其次是及时纠错,对客户端需要有一系列的监控,来监控游戏状态的异常;最后,这些也就保证多人游戏的实时可靠的。

via:手游龙虎豹

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-4-26 13:16

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表