前言
游戏场景中,玩家 A 释放技能给玩家 C 造成伤害,玩家 B 也要同步看到这样的过程,保证多人联机游戏中游戏的一致性
帧同步
帧同步,也称 LoackStep(锁步同步),是齐步行军的意思
帧同步特点
- 帧同步的战斗逻辑在客户端
- 在帧同步下,通信比较简单,服务端只转发操作 ,不做任何逻辑处理。
帧同步流程
- 客户端按照一定的帧速率(理解为逻辑帧,而不是客户端的渲染帧 )去上传当前的操作指令 ,服务端将操作指令广播给所有客户端
- 当客户端收到指令后执行本地代码,如果输入的指令一致,计算的过程一致,那么计算的结果肯定是一致的,这样就能保证所有客户端的同步,这就是帧同步。
帧同步优缺点
优点
- 数据传输量较小
- 逻辑计算在客户端 执行,服务器压力小
缺点
- 由于帧同步战斗逻辑都在客户端,服务器没有验证,带来的问题就是外挂的产生(加速、透视、自动瞄准、数据修改等)
- 由于逻辑在客户端处理,必须保证客户端状态的高度还原,网络条件较差的客户端会影响其他玩家的游戏体验 。(优化方案:乐观帧锁定、渲染与逻辑帧分离、客户端预执行、指令流水线化、操作回滚等)
- 不同机器浮点数精度 问题、容器排序不确定性、RPC时序、随机数值计算不统一(优化方案:可以用定点数来将误差控制在一个可接受范围)
帧同步优化方案------乐观帧
为了解决【网络条件较差的客户端会影响其他玩家的游戏体验】的问题,我们采用乐观帧锁定的优化方案:
由【每个玩家有操作客户端才发送消息给服务器 ,然后服务器再通报 】改为【服务端接受并累积客户端操作指令 ,然后服务端定时通报所有客户端】:
- 单个用户当前键盘上下左右攻击跳跃是否按下用一个 32 位整数 描述,服务端描述一局游戏中最多 8 玩家的键盘操作为:
int player_keyboards[8]
; - 服务端每秒钟 20-50 次向所有客户端发送更新消息(包含所有客户端的操作和递增的帧号):
update=(FrameID,player_keyboards)
- 客户端就像播放游戏录像一样 不停的播放这些包含每帧所有玩家操作的
update
消息。 - 客户端如果没有
update
数据了,就必须等待,直到有新的数据到来。 - 客户端如果一下子收到很多连续的
update
,则快进播放。 - 客户端只要按键按下或者放开 ,就会发送消息给服务端 (而不是到每帧开始才采集键盘),消息只包含一个整数。服务端收到以后,改写
player_keyboards
状态同步
状态同步可以理解为在服务器对数据进行状态管理,然后通过客户端将数据以游戏的形式展示出来
状态同步特点
- 状态同步的战斗逻辑在服务端
- 在状态同步下,客户端更像是一个服务端数据的表现层
状态同步流程
- 客户端上传操作到服务器
- 服务器收到后计算游戏行为的结果 ,然后以广播的方式下发游戏中各种状态
- 客户端收到状态后再根据状态显示内容
如今的状态同步:增量同步、RPC(远程过程调用)两种同步手段
目前状态同步多用于 CS 架构(Client/Server),客户端通过 RPC 向服务器发送指令信息,服务器通过属性同步(增量状态同步)向客户端发送各个对象的状态信息。我们可以才有预测回滚、延迟补偿、插值等优化方式
状态同步优缺点
优点
- 由于每一帧的状态都会进行保存,所以可以更方便的切换到指定的帧状态,进行回滚操作
- 符合传统互联网的B/C通信逻辑
- 由于逻辑计算都在服务端进行,所以安全性非常高。除非是设计不够完善或是进攻服务器,否则难以被外挂支配
缺点
- 开发复杂度较大。由于需要匹配对象的所有参数,所以数据规范化十分重要
- 由于每个对象都有大量的自有参数,所以传输数据量也非常庞大,相应的也会影响网络流畅度
- 涉及到动作动画等操作,需要修改为量化操作。开发比较复杂
- 游戏的核心逻辑计算都在服务器运行,所以服务端压力比较大
帧同步和状态同步的区别
- 帧同步 的战斗逻辑在客户端 ,状态同步 的战斗逻辑在服务端
- 状态同步比帧同步流量消耗大,例如一个复杂游戏的英雄属性可能有100多条,每次改变都要同步一次属性,这个消耗是巨大的,而帧同步不需要同步属性;
- 帧同步的回放&观战比状态同步好做得多,因为只需要保存每局所有人的操作就好了,而状态同步的回放&观战,需要有一个回放&观战服务器,当一局战斗打响,战斗服务器在给客户端发送消息的同时,还需要把这些消息发给放&观战服务器,回放&观战服务器做储存,如果有其他客户端请求回放或者观战,则回放&观战服务器把储存起来的消息按时间发给客户端。
- 状态同步的安全性比帧同步高很多,因为状态同步的所有逻辑和数值都是在服务端的,如果想作弊,就必须攻击服务器,而攻击服务器的难度比更改自己客户端数据的难度高得多,而且更容易被追踪,被追踪到了还会有极高的法律风险。而帧同步因为所有数据全部在客户端,所以解析客户端的数据之后,就可以轻松达到自己想要的效果。
属性 | 帧同步(LockStep) | 状态同步 |
---|---|---|
确定性 | 严格确定 | 允许小误差,定时纠正误差数据 |
表现与响应速度 | 传统严格帧锁定要等其他客户端消息全部到达,响应比较慢;乐观帧锁定可以做到本地立刻响应,但是需要回滚的时候,体验就没那么好了 | 一般会做预测,可以做到立刻响应。不做预测的话,响应时间是一个往返时间(RTT) |
带宽与流量 | 带宽随人数增加而增加,不适合MMO | 需要发送各种状态数据,带宽占用比较高。可以通过压缩、裁剪、增量等方式优化。人数较少时候不如帧同步省流量 |
网络延迟适应性 | 要求较低的延迟。如果延迟较高,所有玩家体验都不好。即使采用乐观帧锁定优化,高延迟下也容易产生卡顿 | 适应性较高,方便做各种插值优化。当然高延迟下,也容易产生位置突变 |
开发难度 | 初期开发减法,框架容易实现,但是后期解决bug和完善系统很困难。比如浮点数、随机数、执行顺序导致计算结果不一致,问题很难排查 | 框架比较复杂,客户端服务端一套代码,每个功能都需要客户端服务端联调。问题定位比较容易。也会出现时序问题 |
玩家数量 | 适合少量的玩家 ,比如ACT 、MOBA | 可以支持多人玩家游戏(100人及以上),比如绝地求生、和平精英等 |
跨平台 | 不适合跨平台,会有浮点数问题,可以用定点数来将误差控制在一个可接受范围,同时可以定时纠正结果 | 适合跨平台。有权威服务器 |
反外挂 | P2P架构不适合反外挂,如果引入战斗服务器来校验各个客户端结果,可以解决常见外挂,但是透视和全图视野防不了 | 与服务器加入校验机制,可以起到比较好的反外挂效果。但是一样防不了透视外挂 |
断线重连 | 比较复杂 。可以在断线的时候,通过快捷播放服务器同步的帧数据来快速跟上游戏 | 容易。由于实时记录了各个对象的状态信息,所以重连的时候,直接创建这些对象,并同步信息即可 |
性能(客户端) | 客户端要跑完整逻辑,还要执行渲染逻辑,开销比较大 | 可以灵活优化,客户端跑较少逻辑 |
回放(离线) | 本身收集了所有玩家的输入信息进行逻辑推进,天然支持回放,且回放文件比较小 | 可以支持回放,但是逻辑比较复杂,需要不断记录状态信息,同时回放时候需要读取合适的时间。回放文件大 |
回放(实时) | 比较复杂,客户端需要本地对全场状态进行序列化,才能回到目标时间。播完回放后还需要加速追上实时游戏状态 | 相对容易,可以方便的记录快照信息,并按照录制内容随时播放 |
帧同步和状态同步混合
这两种方式组合起来就能实现更强大的同步系统
正常同步时使用帧同步 ,当掉线、存档、回滚、或回放的时候,使用状态同步。