找回密码
 立即注册
查看: 371|回复: 0

网络游戏研发,该选帧同步还是状态同步?

[复制链接]
发表于 2021-12-8 11:30 | 显示全部楼层 |阅读模式
近年来,使用帧同步(Lockstep)的游戏越来越多,关于帧同步和状态同步的讨论争论也有不少,那么到底该选哪种同步机制呢?两种机制都使用过,各有优缺点也都踩过不少坑,这里对帧同步和状态同步进行一下总结和讨论。
  
首先需要说明,这里的帧同步其实是指LockStep是指服务器按帧转发客户端的操作,客户端进行确定性运算和一致性模拟(同步操作两边客户端通过完全一致的操作计算出完全一致的状态)。每帧同步状态这里也认为是状态同步
  
这两种同步机制都是为了达到即时同步不同客户端的状态的目的,帧同步需要参与者去管理和维护其自有的那份拷贝,通过施加一致的逻辑来推动所有的状态去同步地更新;状态同步则随着时间的流逝不断地比较和发送最小的状态变化和差异。
  
我们先看一些使用这两种同步机制的案例:
  




  
可以看到大部分类型游戏,两种同步方式都可以使用,但绝大部分游戏使用状态同步,大量玩家战斗的游戏只能使用状态同步。
  








网络模型发展历程
  
下面简单介绍一下网络同步模型的发展。我们可以参考DOOM和QUAKE (I/II/III)的演化来看到同步模型的发展,可以参考DOOM3网络模型的演化与网络架构这篇文章。同步方式的历程大概是帧同步(Lockstep),快照同步(Snapshot synchronization),状态同步(Statesynchronization),目前绝大部分多人游戏都使用状态同步。
  
P2P 模型 (DOOM)
  
DOOM (1994) 的网络模型是基于P2P的帧同步。有着帧同步的各种问题,后面会介绍。并且因为没有主机,每个玩家直连其他所有玩家任何一个人卡所有人都卡,是一个非常古老的同步技术。
  
Packet Server (包的简单中继)
  
在原版 DOOM 的基础上增加了一个 Packet Server,负责转发所有的 tick  command。玩家不再直连其他所有玩家,而是连到服务器  (也可以是某个玩家的机器)来以获取最新的状态。这样改进后,同步量降低了,一个玩家卡只会自己卡,当然如果服务器卡所有人也会卡,同时如果主机是服务器可以避免绝大部分作弊情况,但如果主机是玩家主机作弊就没办法了(可以参考魔兽争霸,主机卡所有人就卡,主机作弊没办法防)。帧同步的其他缺陷也没有得到解决。
  
Client Server (Quake I/II/III)CS架构
  
Quake I/II/III 实现了比较典型的 C/S 架构 (1996)。这个模型中服务器负责所有的逻辑判断,客户端本质上只是一个渲染终端。玩家把自己的操作和输入发送给服务器,收到一个实体列表用于渲染。服务器把压缩后的快照按照固定频率发送客户端  客户端使用这些快照来插值或推导出平滑连贯的体验。
  
这时候同步机制已经变成了服务器同步操作客户端计算逻辑,服务器同步状态的状态同步了,解决了帧同步的大部分问题。但Quake I还做的比较简单,和帧同步不同的是把所有逻辑相关的放在了服务器,客户端在发送操作之后就要等服务器同步状态。延迟问题还是没有得到解决,同时因为要同步所有状态信息带宽占用很高,当游戏越复杂带宽就越高。
  
Quake III做了进一步的优化。客户端不是等待服务器而是会预测可能的游戏状态,预测状态和服务器端逻辑使用一套代码,如果服务器和客户端确实不一致,则服务器为准强同步。
  
预测也是降低延迟感的一个重要方式,对延迟要求很高的FPS特别重要。并且状态同步服务器永远所有信息也能允许玩家中途加入和退出了。但同样的开发复杂度也变的更高了,代码需要区分服务器和客户端,需要逻辑和表现分离,需要处理一些联调的问题(服务器和客户端处理时间不一致,预表现差值问题,强同步问题)。
  
半条命(基于Quake引擎开发)在这个基础上引入了一种延迟补偿 (lag compensation),当玩家向某个目标  (若干毫秒前的状态)  射击时,做实际检测的服务器会采用该目标若干毫秒前的状态来检验是否击中。这么做需要服务器把之前一小段时间的状态持续地保存下来,这样不仅增加了实现复杂度,而且导致了某种程度的不一致性。延时高的玩家反而更容易因为补偿获得更有利的判断,严重影响游戏体验。这种补偿只能对目标的位置回滚,而所有其他环境状态的改变却已无法倒退,这也会影响实际的体验。
  
Quake III里对同步信息做了进一步压缩和优化,只有在 PVS 内的实体才会被同步状态,而且被同步的是压缩后的与上一次同步的差值 (delta compressed relative to the entity states from a previoussnapshot,Delta技术) 。
  
可以看到当玩家比较少的时候,帧同步只需要同步操作,流量会比较小,非常适合同屏大量小兵的情况(小兵不需要同步任何信息),极省带宽。但是当玩家多的时候每个玩家的操作都要互相同步,带宽就会指数增长,无法优化,反而状态同步可以通过分区域的方式同步支持更多的玩家
  
Quake III 不同,Doom III的服务器和客户端使用同一份代码来更新/预测实体的状态,这样不用担心服务器和客户端逻辑的互相干扰,同时客户端和服务器也一相同的逻辑帧率运行60fps,每帧客户端上传玩家输入,服务器按固定间隔同步PVS范围内的状态快照,也可以理解为按帧同步的状态同步
  
Doom III在网络上使用UDP,自己通过冗余包和滑动窗口保证服务器消息不丢失和有序并且允许客户端上行丢包,在弱网情况下比TCP延迟更低,不需要TimeOut机制。
  
两者的优缺点对比
  
那么我们再来对比一下帧同步和状态各自的优缺点:
  




  








怎么选
  
那么依据前面的分析,我们可以总结一下,对于大部分游戏来说,两种同步方式都可以使用。但相比之下状态同步适用型更广,特别适合复杂度高,延迟要求高,玩家多的游戏,例如FPS,MMO等等。帧同步相对适合小兵很多,玩家少且固定,单局时间短,对打击感公平性要求高,追求一致性的游戏,例如格斗,运动,RTS,卡牌,MOBA等。
  
从技术角度来说,帧同步有一些技术限制,大量玩家战斗,随时进入退出,难以预表现等,而状态同步有更多的优化手段可以更好的降低延迟感。可以说用帧同步的一定能用状态同步,但反过来不成立。
  
当然帧同步也有自己的优势,实现成本相对简单开发比较快速(一套逻辑不太需要联调),在玩家较少小兵较多的情况下(由于只同步事件而非状态,所以网络传输的数据和游戏里的对象数量无关)服务器性能和带宽开销极低,甚至可以没有服务器(服务器可以完全不跑战斗逻辑只在需要反挂的时候跑),有点去中心化的意思。也非常适合一些单机游戏改成联网得游戏,非常适合中小公司(之前开发的一个MOBA游戏只有一个服务器同学)。
  
我们在选择的时候需要综合考虑游戏类型,未来需求,战斗时长,游戏模式,网络带宽,延迟响应,防作弊,开发成本周期和实力等因素来选用不同的同步方案,甚至混合使用没有最好的技术只有最适合的技术。下面是一些在选择时可以思考的一些参考问题:
  




  






帧同步的难点
  
因为帧同步的一些限制并且使用的相对较少,最后再补充一些帧同步的优化方案和心得。
  
延迟方面:帧同步难以做预表现,所以对网络和延迟要求更高,必须在网络层上有更深度极致的优化(一些状态同步的游戏优化不好开可以通过预表现蒙混过关)。
  
以下是之前尝试过的一些优化手段:
  
1.首先要分析延迟是否有逻辑问题,比如我们一开始有部分延迟是因为逻辑在FixedUpdate里执行,因为更新顺序问题导致客户端相应操作慢一帧,解决之后有较大提升;
  
2.网络层面极致优化,使用自己开发的UDP,通过冗余包和滑动窗口的方式保证可靠性降低延迟。冗余包的个数依赖MTU并不固定,当然前提是每个帧操作的包也要极致优化(方向操作分段,按位压缩等)。此外客户端上行可以允许丢包允许非可靠,客户端关键操作立即发送(发送频率比逻辑帧高),网络多线程多地多线部署匹配等等;
  
3.控制发包频率。非关键操作降低发送频率,服务器收到的操作会进行合并和优化,进行帧聚合,避免堆积。甚至可以有选择的舍弃一些包;
  
4.客户端可以进行一些预表现。比较好的做法逻辑层和变现层分离,客户端表现层可以预表现一定的位移,然后通过航位预测算法逐渐差值到逻辑层的位置。对于帧同步来说解决平滑位移的基础还是网络层的极致优化,然后再考虑平滑差值和预表现
  
性能方面:帧同步需要每个客户端都完整计算所有逻辑包括小兵AI等,特别是很多帧同步游戏又是对帧率要求非常高非常稳定的,不能卡顿,也需要有更好的性能优化需求。
  
以下是之前尝试过的一些优化手段:
  
1.常规的一些客户端性能极致优化,保证客户端帧率稳定。例如多线程,分帧分摊,优化GC,预加载预编译,优化算法,使用更优的平滑算法在渲染层差值,网络卡顿时进行分担等等;
  
2.帧同步有优势的一些优化手段。逻辑帧和渲染帧分离,降低逻辑帧,一般来说MOBA游戏逻辑只需要15帧即可,在渲染帧较高时利用负载均衡拆分逻辑帧等消除毛刺等;
  
3.甚至一些复杂运算可以分布式计算或者服务器计算,一人计算后同步给其他玩家。
  
开发效率:帧同步实现简单但维护和查bug极难,对工具,开发素养,规范都有非常高的要求。特别事帧同步只有一点不一致就会导致双方不一致(一个运算,一个错误导致一些代码跳过,更新顺序等),并且往往是在第一时间表现不出来的,累积到一定时间之后才会爆发。
  
以下是之前尝试过的一些优化手段:
  
1.程序框架上避免,开发时要求0warning0Error,详尽的保护,逻辑层和表现层分离更新帧率不同,一些随机数,顶点数学库等,底层库也要区分开,有自动化的导表转换工具等,防止逻辑层使用浮点数,不是固定随机等;
  
2.提供自动化的检查工具。定期计算关键数据的hash值每帧校验,表现层使用逻辑层库会自动报错等;
  
3.出错时的排查工具。详细的本地日志,不同步时每个人上传服务器前100帧的日志;录像功能,记录每个玩家同步操作信息,同时出问题之后可以重复触发跟踪等。
  
一些小误区
  
·   帧同步会受网络最差的玩家影响,延迟依赖于最差玩家,一个人卡所有人都卡:这是早期的P2P技术导致的,一个玩家要等所有玩家的操作都到了才到。最早Doom就有这个问题,如果使用Packet  Server(服务器或者主机)就可以避免这个问题,卡的玩家只会影响他自己,就是乐观帧方案。
  
·  状态同步服务器需要跑逻辑:状态同步也可以使用客户端之间的状态同步服务器只转发,帧同步也可以服务器跑完整逻辑(实时跑和结算后快速演算)。
  
·  帧同步或者状态同步只能选一个:针对不同的需要也有项目同时使用帧同步和状态同步两种做法例如梦三国等,平时使用帧同步线下比赛的时候针对比赛玩家使用状态同步解决全图挂的问题。
  
·  帧同步难反作弊:除了全图挂等(客户端有完整信息)更容易反外挂,王者荣耀反外挂做的就很好。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-25 19:39 , Processed in 0.098717 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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