2.4 游戏同步

请问在游戏同步方面有哪些分类方式?帧同步与状态同步有何优劣?

问题分析

随着竞技游戏的日渐火爆,越来越多的实时对战游戏被开发出来。我们知道,网络交互是需要时间的,如何才能在多人对战的场景中,将同步信息的延迟降低,以保证玩家的游戏体验,是游戏同步主要研究的问题。同步策略通常需要根据不同的项目类型单独制定,后期还需要经过大量玩家的检验才能完善。

游戏模式

根据游戏中玩家之间的互动方式,可以将多人交互类型的游戏大体上分为两个类别:MO与MMO。这两种典型的模式有着完全不同的侧重点和技术架构。

多人在线游戏(Multiplayer Online, MO)指的是少量玩家聚集在一起的竞技游戏。它的侧重点是实时对战的战斗乐趣,因此游戏追求高速的响应时间,以及频繁的交互操作。相对应的,游戏时间通常在几分钟到几十分钟不等,通常采用房间或副本的形式进行游戏,玩家数量控制在20人以内。常见的类型为ARPG、FPS、格斗、RTS、竞速等。

大型多人在线游戏(Massively Multiplayer Online, MMO)则是期望大量玩家进行长期的社交性游戏。它追求的是一种在虚拟社区中的存在感,因此游戏周期通常几个月以上。以最成功的MMO类型的游戏魔兽世界(World of Warcraft)为例,整个游戏运营已接近十五年。更是有新闻报道少年继承亡父的账号,足见此游戏的魅力。由于要处理大量的玩家数据,难免会牺牲响应时间,因此在MMO游戏中,通常不会有高速的交互。为了能长期运行,系统的稳定性与可维护性也是框架的重中之重。

MO与MMO在游戏内容上有很大差别,所需的技术也截然不同,但一个游戏中同时存在两种模式并不冲突。常见的做法是,将MO类型的游戏以副本的形式嵌到MMO中,并分别采用独立结构对游戏逻辑提供支持。这样就可以兼顾MMO的玩法多样性与MO对战的乐趣性。在WoW中,野外战斗与大部分副本都是以MMO架构实现的,而少数在短时间内需要玩家紧密配合、历尽艰难才能通关的副本则是通过响应更快的MO架构实现的。

通信方式

1)C/S架构

从通信方式上看,同步可以分为C/S与P2P两种。C/S是Client/Server的缩写,指的是客户端与服务器连接的消息传递方式。在这种框架下,所有消息都通过服务器转发。它的优势在于可以很好地控制非法行为。服务器知道一切数据,通常也会对数据进行模拟计算,因此,作弊行为很难出现。另一方面客户端也可以只做表现,完全由服务器的数据驱动。这种方式常用在不需要,或很少需要预表现的游戏中。

这种结构的最主要弊端是网络延迟较大,通常会控制在200ms以下。另一方面,中央服务器上大量的运算也会增加服务器的设备成本。在商业项目上线之前,通常会进行压力测试,主要关注点在带宽、连接数、响应速度等。通过模拟大量玩家的操作,来测试服务器硬件是否能够应付预期数量的玩家。

2)P2P架构

与C/S架构不同,P2P架构是两个客户端之间直接连接。它与C/S结构的优劣正好完全倒置,它有低延迟的优点,也可以节省大量的服务器运算,却很难避免作弊的行为。例如,DOTA中的全图外挂无法禁掉,根本原因就是客户端拥有其他玩家的数据。

数据模式

1)全网状数据模式

全网状数据模式指的是,每个客户端需要给网络中的每个设备广播自己的数据。因此每个设备上都有完整的数据,各个终端之间传递的只是控制设备的输入信息。可以预想到,随着网络中终端数量的增加,信息量会急剧增长。由于消息量大,因此不同客户端之间通常只会传递用户输入的信息,这就需要保证每个客户端的初始数据与每次操作的运算结果必须完全相同,否则就会表现不一致。

2)星型数据模式

另一种情况是网络中的成员并不平等,所有终端都要依从中央终端的数据。这个终端在C/S架构中被称为中央服务器,在P2P架构中被称为主机,这里为了方便说明,暂称为服务器。在标准的星型结构中,服务器在收集完所有设备发送的数据之前,一直处于等待状态,接收完成后再将数据广播出去。因此它是将输入数据暂时集中到服务器上,这一点与全网状数据模式完全不同。

这种结构避免了终端过多导致的数据暴涨,但也会引起数据传输速度的下降,有利有弊,需要根据自己项目的实际情况做取舍。

同步方式

1)状态同步

本节中提到的状态同步和帧同步都是广义上的,虽然它们的底层实现都可以基于上面提到的模式,但从广义上讲,它们的同步思路不同。

状态同步指的是将其他玩家的状态行为同步,例如,怪物的AI控制、角色技能释放、战斗伤害计算等。纯粹的状态同步模式下,这些内容都由服务器运算,只是将结果下发给客户端,客户端根据得到的数据驱动显示即可。这种模式的缺点是流量消耗比较大,消耗的流量取决于场景中需要转发数据的人数和内容。另外,先天的结构导致响应速度存在问题,无法做到高频交互的顺畅体验。最明显的特点就是“拉扯”现象。它表现为某个角色会突然出现在某个位置,或某个技能效果突然出现,甚至角色忽然死亡等。引发这个现象的原因是,网络波动导致数据未能及时送达,而客户端进行了某些程度的预表现或航位推测。

对于大多数实时性要求不高、交互简单的游戏,一般会使用状态同步。通常来说,常规MMO类型的游戏只能使用状态同步。

2)帧同步

帧同步是指客户端之间只同步用户的输入指令,例如,向前走、按下哪些技能等,不同的客户端各自计算自己的结果。由于消耗的流量只取决于指令数,因此会大大减少消息。另外由于消息结构的原因,帧同步的速度要比状态同步更快,因此适用于一些高频交互的游戏。

不过由于每个客户端需要独立计算,因此需要保证计算结果的一致性。理论上讲,相同的时机,输入相同的内容,会得到相同的结果。不过从实际情况看,做到完全相同还是有些难度的。以Unity引擎为例,一方面,各个脚本之间Start、Update等生命周期函数的调用顺序不确定;另一方面,使用Physic物理系统也不保证是确定性模拟。比如一个单位的坐标偏差了0.01导致技能未能击中目标,那么这个目标的血量判断就会受到影响。如果这个目标的行为受到血量的影响,那么最终结果会完全不同。因此让每个客户端计算结果完全相同不是一件容易的事。一些常见的注意事项如下。

◎ 不使用浮点数,用整数代替。

◎ 不同客户的同步频率要保证一致。

◎ 随机种子相同,并自定义接口防止其他公用系统干扰。

◎ 使用排序容器,保证遍历顺序。

◎ 逻辑显示分离。

◎ 使用补间过渡,调整速率,掩盖卡顿。

除了一致性的难点,帧同步还需要解决流畅度的问题。由于通过网络传输过来的数据一定会慢于本地,而我们又希望在相同的时刻输入信息,因此就会引发等待,反映到用户体验就是不流畅。

优化方法有很多,例如,在帧同步游戏中,由于广播的频率非常高,因此每次广播的数据就要足够小,这样可以节省很多消息处理的时间。对于消息,可以将需要所有客户端同时发生的内容提前广播给其他用户,采用时钟同步。客户端逻辑先行,显示通过平滑追赶的方式处理。很多改进是体验优化的范畴,需要结合具体游戏进行。

在传输层,移动端的同步建议使用UDP作为传输协议。TCP为了保证传输的可信性,很多机制不太适合波动较大的移动网络。在弱网络环境下,UDP的RTT几乎不受影响,而TCP的RTT波动比较大,特别是在丢包重发时影响比较明显。虽然使用UDP会引入丢包、乱序的问题,但可以通过冗余的方式来解决这个问题。比如每帧三个数据包,实际上是包含了过去两帧的数据,也就是每次发三帧的数据来对抗丢包。

总结

本章我们从不同的侧面了解了常见的通信方式与数据同步方式。并在此基础之上,粗略地分析了状态同步与帧同步的差别。这部分内容需要大量的实践来进行测试和验证。只有经历实际上线项目的历练,才称得上是成熟的同步框架。

扩展问题

请问帧同步中的锁帧(Lockstep)是指什么([:lockstep])?