网上有人从策划角度,分析过关于该游戏的一些核心玩法,以及如何做到吸引玩家的,但是还没有从技术角度对该款游戏进行过分析。
吃鸡游戏采用的是 UE4 虚幻引擎制做完成,作为程序爱好者,我们应该本着学习的态度去玩,深入理解游戏产品背后的技术支撑,这样我们从玩中也能学到知识,否则就会走向另一个极端沉迷其中,把自己玩废了。
本篇文章从以下四点关键技术角度对其关键技术进行解读:
- 角色动作
- 游戏渲染
- 角色网络同步
- 大地形加载
掌握了这四点就掌握了这款游戏的核心技术,UE4 引擎市面上使用的比较少,而且它对程序员编程要求也比较高。
我们就以最常用的 Unity 引擎为例给读者介绍吃鸡游戏使用的相关技术,成熟的商业引擎,它们的技术大同小异,UE4 能做的事情,Unity 引擎基本也可以做到,吃鸡游戏的角色动作特别多,我们就先从角色动作讲起。
角色动作
玩 3D 游戏时,经常会看到游戏中的 3D 角色非常绚丽的动作表现,游戏中的角色动作主要是通过美术制作完成。
制作方式有如下两种:
- 通过动作捕捉,然后再由美术人员根据捕捉到的数据完成美术制作。
- 美术利用 Max 或者 Maya 工具直接制作完成。
相比前者动作比较逼真,成本也高。我们这里不讨论使用哪种方式,对于动作类游戏,吃鸡游戏的角色动作是最多的。
它包括:跑,走,跳,翻墙,趴下,驾驶载具,扔手雷,装备不同的枪械,使用不同的枪械射击动作,救护,拾取弹药装备等等。
为了让读者有个总的认识,通过图示的方式,展示如下所示:
上图,我们列举了吃鸡游戏中角色的大部分动作,这些动作美术都需要完成的,这里就涉及到一个工作量问题了。
我们将其分成两类:
- 一类是按照动作要求全部把动作做一遍,工作量是相当大的,因为你不但要制作还要维护这些动作。
- 另一种是采用引擎提供的动作融合技术,这需要我们将动作进行分类,目的是减少一部分动作的制作,这种是最优的方案, UE4 引擎也是支持动作融合技术的,下面就给读者详细介绍动作融合技术的使用。
动作融合
不论是虚幻引擎、Unity 引擎还是自研引擎,成熟的引擎都会提供动作融合技术。
为了方便学习,在这里我利用 Unity 的动作融合机制实现了吃鸡游戏中角色的大部分动作,下面先看看我们实现的动作融合状态机。
Unity 为我们提供了动作状态机,鉴于吃鸡游戏动作的复杂性,上图是我们模拟实现了一下,我们在这里将动作进行了分类,分几层无所谓关键是能解决问题就可以。
我们这么分的原因是方便对角色动作进行融合,层与层之间的播放可以设置优先级播放,也可以同时播放,这样我们就可以实现动作的融合了。
我们为什么这么分层?如果使用一层来实现角色动作状态机,这么多动作显得非常臃肿而且动作之间的转换非常复杂,灵活性不够,让开发者很容易深陷其中,逻辑很难理顺。
所以我们采用了分层的策略,这样动作的播放顺序,动作的融合在逻辑方面会很清晰。
首先看 Base Layer,它是最核心的层,该层主要实现的是角色基本动作的播放。
我们通过思维导图的方式展示,如下图:
我们将 Base Layer 层设为父类,再看 OnlyArms 层,它继承于 Base Layer,如下图所示:
OnlyArms 层主要是在 Base Layer 层的基础上做了一个扩展,比如加了一个角色拿不同的武器待机状态,继承原理跟 C++,C# 等面向对象语言的继承方式类似。
OnlyArms 层的部分有限状态机如下图所示:
另外,UpperBody 层主要是针对角色上半身的动作状态机,便于与 Base Layer 或者 OnlyArms 层做动作融合,UnderBody 跟 UpperBody 类似,它是下半身的动作状态机。
下面我们以角色装备武器时的走,跑为例给读者讲解动作融合,先展示角色装备武器的基本动作。
待机,走,跑动作混合树示意图如下:
动作之间的过渡,我们运用了 2D 笛卡尔插值计算,2D 笛卡尔插值是系统为我们提供的。
走的动作就有四种走动方式,它朝四个方向行走,如下所示:
我们已将其实现出来了,它在我们的 Demo 中的效果如下所示:
吃鸡游戏中的画面如下所示:
讲解如何使用动作融合解决问题,我们以角色拿枪移动中进行射击为例,我们可以将这个动作分解成两部分:一是角色拿枪走动,二是角色站立射击。
这样我们可以通过程序把二者做个融合,融合的结果是:角色的下半身采用角色拿枪走动,而上半身采用站立的射击动作。
这样我们就把两个动作融合在一起了,在具体实现上,我们有 UpperBody 层,该层会与我们的 OnlyArms(该层继承 Base Layer)做动作融合。
先展示上半身的射击动作状态机:
射击动作包括:步枪射击,手枪射击,RPG 射击,将它与 OnlyArms 层的站立射击进行动作融合就实现了角色边走动边射击。
后面我们会提供案例实现代码,实现效果如下所示:
这样角色的边走动边射击的动作完美融合在一起了,关于动作融合在这里也要注意,不能所有的动作都考虑到融合,融合也要本着一个原则,动作融合时要保持动作的协调性。
比如不能把不带武器的走跟射击动作去融合,那样就会出现动作的不协调性,因为角色空手走动与拿武器走动是完全不一样的动作表现。
吃鸡游戏在动作融合这块做的比较多,我们采用动作融合可以帮我们减少美术的工作量而且角色动作要制作成独立的动作文件,这样有利于动作的调优,我们是通过技术推理实现了吃鸡游戏中的动作播放。
在吃鸡游戏中,角色可以使用不同的武器,而使用不同武器的动作也是不同的,相应的武器的子弹效果也是不同的,武器效果可以通过特效和数值表现,不同武器的子弹发射轨迹可以通过插值算法完成。
在此我们也模拟实现了不同武器的发射效果,图一是肩扛式的火箭筒发射效果图:
图二是手枪射击效果图:
图三是散弹枪射击效果图:
另外还有冲锋枪等,吃鸡角色的其他动作实现跟我们上面讲的类似,这里就不一一介绍了。
装备的切换
我们可以看到在吃鸡游戏中,角色在地面上能捡到很多装备,包括枪支,弹药,背包,头盔等等。
这些物品除了放在游戏中的背包外,我们也会选择一些装备挂接到角色身上,装备切换怎么实现的呢?
它跟我们的换装类似,需要在角色的骨骼上挂载不同的虚拟点,具体实现如下图所示:
图中画红线部分就是我们说的虚拟点,它们都是作为骨骼动画下面的子节点,一定要注意是在骨骼下面,因为这些虚拟点是跟随角色一起动的。
换句话说,它是角色身体的一部分,在吃鸡游戏中的角色装备画面如下所示:
当然,我们也验证了我们的切换武器方式,实现的效果如下所示:
当然角色本身还需要做一些优化处理,我们对角色使用了 LOD 网格,根据摄像机距离远近使用不同的 LOD 角色,角色动作我们就介绍到这里,下面我们讲讲游戏渲染技术。
游戏渲染
不论是什么类型的游戏,游戏的渲染效果直接决定了游戏的品质,吃鸡游戏也不例外,游戏渲染包括两方面:
- 一方面是物体的材质渲染。
- 另一方面是场景的后处理渲染。
物体的渲染主要是针对材质贴图的渲染,比如高光,法线,反射,折射,环境映射等等,另一方面是场景的后处理渲染也称为滤镜渲染。
UE4 渲染非常强大,对于材质要求也比较高,相比 Unity 更容易掌握,下面我们还是以 Unity 为例给读者分析,先分析吃鸡游戏的角色渲染,如下图所示:
上图显示的角色钢盔,枪支都有明显的高光效果,它的枪支和头盔都是用了高光法线效果,Unity 也为我们提供了该技术。
我们用 Unity 模拟实现了一下效果,如下图所示:
效果也很炫的,以上是针对角色的材质渲染,Unity 给开发者提供了材质渲染的 Shader,如下图所示:
另外,场景中的一些物件渲染,比如草地,树木的生成,这些都可以使用 GPU 编程实现,程序员如何编写 Shader?
引擎也为我们提供了 Shader 编辑器,比如 Unity2018 使用了 Shader Graph,还有一个 Shader Forge 插件,UE4 的蓝图,这些编辑器不需要开发者编写程序代码,直接通过拖拖窗口界面就可以实现。
吃鸡游戏也使用了后处理渲染,比如实时阴影,还有游戏中的 Bloom,Blur 效果等。
其实这些都是成熟引擎的标配,关于渲染,UE4 使用了多线程渲染,Unity 从 2018 开始也有了自己的多线程编程渲染,下面再给读者介绍吃鸡游戏角色同步机制。
角色同步
吃鸡游戏采用的是开房间性质的,使用的网络同步是帧同步,游戏中的同步方式分为两类:帧同步,状态同步。
状态同步在 ARPG 游戏中使用广泛,因为角色的动作需要跟服务器做一个验证,这样客户端之间会有一定的延迟,但是数据一定是准确的。
而帧同步则不需要这么操作,它要求的是动作的一致性,如果某个客户端慢了,后面为了赶上角色动作,会加速播放,以满足所有客户端同步,以前的游戏比如 CS 游戏使用的也是帧同步,还有篮球游戏等等。
帧同步和状态同步没有好坏之分,根据游戏产品的需求不同,采用不同的同步方式。
每种同步都有自己的优缺点,因为吃鸡游戏使用的是帧同步,本篇文章重点介绍帧同步。
我们先分析帧同步的注意事项:数据传输会有浮点数的问题,不同的平台处理浮点数的方式也是不一样的。
我们需要将浮点数改为整数进行传输采集,用的方式是把浮点数乘以 1000 或 100 然后取整,针对特定的数据对象来编写序列化函数。
模拟实现代码如下所示:
另一个注意事项是加速播放,由于各种原因客户端收到“过去时间”里的一堆网络帧,因此,客户端必须要有处理这些堆积起来的网络数据的能力。最简单的做法就是加速播放(快进)。
实现效果如下图所示:
对应的实现代码如下:
最后一个要注意的问题是断线重连/中途加入,需要做到在服务端保存每一份同步包。
这样服务端只需要记录每个玩家的初始数据,在新玩家加入游戏时,首先发送每个玩家的初始数据给新玩家同步,然后再把所有同步包打包发送给新玩家,让新玩家一次性 Update,即可完成中途加入。
当然不要忘记给现有玩家发送新玩家的数据。模拟的代码如下所示:
帧游戏收发频率,通常是服务端每秒 20-50 次向所有客户端发送同步包。我们采用的是乐观锁,就是服务端每隔一段时间发送同步包,然后客户端每隔一段时间接收并应用之,如果在那段时间内没有收到,就持续等待。
每 Update 一次即是一帧,每次 Update 的间隔时间为 17 毫秒,这个数字是根据(1/60)秒取整得出。
每隔三帧服务端便会发送同步包,而客户端则是每帧都会接收,每隔三帧便会应用之,通常称为同步帧。
帧同步游戏技术,并不存在一种可以让游戏流畅的通用做法,而是需要和游戏具体做很多结合,在减少数据包,优化游戏快进体验,控制发包速度上尽量调优。
同时还需要和游戏产品策划一起,平衡一致性、实时性、公平性的策略,才能真正达到流畅游戏的目的。
地形加载
吃鸡游戏中使用了大地形的加载方式。大地形的加载方式,首先是分块,然后采用预加载方式进行。
为了效率优化可以使用多线程的方式,UE4 引擎本身就支持多线程,更容易实现大地形加载。
下面就介绍如何使用多线程实现大规模地形的加载,吃鸡游戏的场景如下所示:
从吃鸡游戏中的地形我们可以看到,它里面的建筑物是非常少的,这么做的原因是把内存留给了网络数据通信。
毕竟每个玩家都是通过帧同步的,每个玩家会同时发送和接受大量数据,这需要占用很多内存,这也是做了一些优化操作的。
如果树木和建筑物多,那就需要做一些裁剪操作比如 OC 裁剪,但是这样会影响游戏体验,所以吃鸡游戏就做了一个折中方案,减少建筑物的渲染,做好帧同步。
我们回到大地形加载方案实施上:首先对数据进行分块存储,将人的视点所能观察到的范围的数据作为一块,将整个地形分成若干个这样的块,以块为单位进行存储。
我们采用的是双缓存技术,当视点在 A 区域时,加载九块到显存中,开辟一个数据读取线程,将磁盘中的 J-Y 数据块加载到内存中。
当视点从 A 区域移动到 l 区域时,将显存中的对应数据块与内存中的数据块替换,同时从磁盘中加载新的数据块,放到内存中。
给读者进行案例展示如下所示:
另外,大场景地形中使用了非常多的贴图,下面介绍如何优化。
所有程序用到的贴图会被 pack 到几张非常大的虚拟贴图上,模型的 uv 也被转为虚拟贴图上的 uv。
这些虚拟贴图尺寸通常非常大,无法全部载入内存,每个虚拟贴图会被划分为很多 n*n 的小块,每个小块称为一个 page 文件存在磁盘上, 虚拟贴图会对应一张 indirection texture,这张图是载入内存的。
indirection texture 上存储了每个虚拟贴图的每个 page 块位置所对应到的真实贴图(或物理贴图)的位置。
当前画面需要访问某个虚拟贴图上的某个 page 的时候,通过 indirection texture 找到它在物理贴图上的位置。
如果物理贴图上不存在,就查找到它对应的磁盘上的 page 文件,将其载入物理贴图的对应区域。shader 在渲染的时候,结合 indirection texture 去访问物理贴图来采样。
对于虚拟贴图也要处理 mipmap,对于虚拟贴图也同时存在它的多个 mipmap 的虚拟贴图和 indirection texture。load 物理贴图的时候同时要 load 这些 mipmap。
虚拟贴图的具体实现方案如下所示:
虚拟贴图减少了内存占用,但是对于 io 是一个挑战,尤其在快速转动视角,飞行等快速切换渲染物体的情况下,很多时候加载贴图的时间会成为瓶颈,这是在实现中一个很大的问题。
一些优化方法包括每次对 page 优先加载 mipmap 最低一层的,最后加载高层的。
这样当所需的 mip 没有加载好的时候,可以先使用最低的那个 mipmap,然后待加载好之后再展示细节更高的。
总结
现在游戏开发中使用的技术都是开放的,条条大道通罗马,本篇文章从技术实现以及优化角度对吃鸡游戏的技术做了一个解析,希望对大家开发类似吃鸡游戏提供一个技术参考。
作者:姜雪伟
简介:3D 引擎专家,主要擅长 3D 图形学渲染,客户端架构,服务器架构设计,虚拟现实,C++ 编程等技术,曾就职于网龙,久游,趣游等多家IT公司,参与研发多款游戏上线;已出版著作《手把手教你架构 3D 游戏引擎》电子工业出版社,《Unity3D 实战核心技术详解》电子工业出版社,《Cocos2D-X 3.X 3D 图形学渲染技术》电子工业出版社等 IT 书籍。