通信协议的选择

由于tcp在网络不稳定时延迟严重,所以我们需要udp通信。由于udp的不可靠的特点,前期我们可以使用 使用别人封装好的可靠的udp协议,例如使用很广的kcp协议。如果发现kcp仍然不能满足我们的实时性要求,我们可以直接使用udp协议,自己控制丢包,乱序问题,例如每次发包都带上前两帧的数据以保证最大程度的低延迟,每次都发送两次udp包等。据说kcp+fce(前向纠错码)效果很好,可以试一试。

帧数据编码的选择

由于帧数据收发很频繁,所以尽量使用二进制流,而不是ProtoBuf,即使是无GC的ProtoBuf相信也没有二进制流快。

帧同步服务器工作流程

帧同步服务器固定时间(例如66ms)收集各个客户端的帧数据,然后转发给各个客户端,如果客户端某一帧数据未能及时发送给服务器,服务器不需要等待,而是将该客户端看作是在这一帧什么也没做。

关键数据判断

对于关键数据,例如比赛结果,需要服务器来判断,当战斗结果不一致时,取多数一致的结果。当然如果有能力最好使用客户端逻辑层作为校验服务器,当客户端数量少于3个或者关键数据不一致时,使用校验服务器重跑所有操作判断。

外挂检测

客户端不定时上传关键数据的hash,服务端通过对比客户端上传的hash值判断哪个客户端有作弊嫌疑。

回放观战服务器

当游戏开始时,帧同步服务器在发送帧数据给客户端的同时也发送给回放观战服务器,回放观战服务器存储帧数据,如果有其它客户端请求回放或者观战,则将存储的帧数据发送给客户端。

逻辑层和表现层

客户端要做到逻辑层和表现层分离,表现层先行(游戏画面),逻辑层等服务端指令再处理,逻辑层能独立编译。逻辑层更新的时间需要等间隔的,表现层则不一定,表现层的帧频率要高于逻辑层。

逻辑帧的频率由服务器控制,为了避免网络波动造成逻辑帧频率不稳定,客户端可以建立一个逻辑帧缓冲区,以固定频率处理逻辑帧。

碰撞检测

由于unity自带的物理碰撞检测系统使用的是浮点数且是乱序的,所以我们要使用自己的物理碰撞检测系统。

帧数据封包优化

由于帧数据收发很频繁,所以每一个包要足够小,最好在一个MTU以下,Internet上的标准MTU值为576,对于udp数据长度最好在576-20-8=548字节以内,如果使用封装udp的协议,需要考虑减去该协议自身的数据大小。

断线重连

客户端掉线重连时需要向服务端请求从头开始的所有帧数据,然后加速跑一次,考虑到客户端硬件配置可能不好,速度不能过快。

浮点数

由于浮点数有随机性,逻辑层的浮点数操作都需要改为定点数,表现层可以使用浮点数。

随机数

游戏开始时,所有客户端都需要使用相同的随机种子和随机算法以保证每个客户端表现一致。

待解决的问题

  • 客户端在上传帧数据时,帧同步服务器如何分辨客户端是否是伪造的?
  • 帧数据收发很频繁,是否需要加解密,需要的话是否影响性能,不需要的话会不会造成安全性问题?
  • 客户端表现层先行,如果收到的逻辑层数据和表现层不一致怎么办?例如人物转身了,但是由于网络延迟,服务器将我这一帧看作是空操作的情况。