基于帧同步的游戏框架说明
一,关于帧同步和状态同步的比较
帧同步 | 状态同步 | |
---|---|---|
安全性 | 比较差,计算都在客户端,服务器只做转发;有服务器校验的方案,比较繁琐 | 计算都在服务器 可以将重要的判定都由服务器决定 |
网络流量 | 比较小,每一帧只同步玩家的操作指令 | 如果单位数量多,需要同步的数据量会比较大 |
技能实现 | 比较容易,只用客户端实现即可,开发周期短 | 需要服务器和客户端实现相同的运算逻辑,如果是不同的语言相当于要开发两次;另外前后端机制的配合也比较复杂 |
录像回放 | 记录每一帧的指令即可,数据量小 | 不太容易做录像 |
一些限制 | 1,随机种子要一致,不能使用浮点数,导致在游戏逻辑层使用外部库要注意,包括物理引擎之类的都禁止使用; 2,代码要求比较高,如果出现异常就会出现玩家之间数据不一致,导致战斗结果无效。 3,断线重连比较复杂,通常需要客户端重头跑帧指令。 | 没有要求 |
二,实际项目《战争XX》帧同步方案分析
1,整体架构:
2,游戏类型是一款在moba游戏上加入rts元素的实时对战游戏,支持1v1,2v2的模式。
3,关于lockstep
简单的说就是游戏时间划分成一个一个的turn,每个trun玩家发送指令给服务器,服务器收集每个玩家的指令,在这一帧末尾广播给所有玩家;客户端收到指令后,执行相应的指令;
因为么个客户端的开始状态一致,那么经过相同的指令计算后,状态也应该是一致的;这就是帧同步的核心原理。
原始的lockstep要求每一个turn会等待所有玩家的指令,如果有一个玩家卡顿了,其他玩家也会受到影响;我们针对这种情况作了改进,如果超过一定时间就认为这个客户端这一帧没有做操作,
从而不会影响其他玩家。
4,模块说明
loginserver: 登录服务器,负责接入第三方账户系统登录
logicserver:大厅服务器,负责养成逻辑,可以横向扩展多个;基于skynet
gamecenter: 中心服务器,负责全局性的功能,如玩家状态管理,帮会,匹配等;基于skynet,可以按功能扩展
battleserver:战斗服务器,负责战斗过程,基于c++;核心逻辑是基于房间的概念,每场战斗就是一个房间,房间内的玩家进行帧同步处理
checkserver:验证服务器,负责验算战斗过程,用于当客户端战斗结果不一致的时候,服务器校验
replaycenter:验证中心服务器,负责管理验证服务器,负载均衡
5,一场战斗的生命周期
gamecenter匹配好一场战斗之后,根据负载均衡选择一个battleserver,将玩家信息发送给battleserver;
battleserver根据玩家信息创建一个房间room和玩家对象,返回给gamecenter战斗服地址;
gamecenter通过logicserver返回地址给客户端;
客户端连接战斗服,并且加载对应的战斗地图,加载过程会广播给其他玩家;
当一个房间中所有玩家都加载完成,战斗服务器广播战斗开始;
战斗中每秒15帧,每一帧会收集所有玩家的操作指令,在帧末尾广播出去;
帧指令需要缓存,因为需要做断线重连;
为了高效的管理这个缓存,这里设计了一个专门为此服务的内存池,每次分配一个固定块的内存,用链表记录起来;每次分配就从块内存中分配,不够再向系统申请;
当战斗结束的时候,直接将链表中所有的块内存全部释放。
当客户端判断战斗结束,会给服务器发送战斗结束协议;战斗服务器将战斗结果发送给gamecenter,即可结束这一场战斗。
6,关于作弊检查
客户端每一帧会将关键状态序列化计算md5发送给战斗服务器,战斗服务器每一帧会收集到每个客户端的md5;如果md5不一致即可知道有客户端状态不一致;
如果是2v2,我们可以用其中三个md5的一致来判断一个不一致的人客户端出问题了,这种情况会丢弃有问题的客户端的战斗结果。
如果是1v1,我们不能通过md5的方式知道正确的客户端。此时会用到checkserver,这个服务器上跑了客户端的战斗逻辑,得益于将战斗逻辑独立成一个lib,
我们在ubuntu系统上安装mono运行环境可以在服务器上跑战斗过程,就是将帧指令发送给checkserver验算战斗结果。缺点就是内存占用比较大,一场战斗内存在15M左右;
因此我们先通过第一种方式来检查,实在不行才启用第二种方式。
7,关于客户端
客户端需要将逻辑和表现分开,逻辑层每一帧会根据收到的服务器指令来更新逻辑状态;由服务器帧来驱动逻辑运算,要考虑由于网络波动造成的帧信号时快时慢,
逻辑循环要针对这种情况做快进或者等待;表现层考虑手感优先需要对玩家操作的单位快速做出响应,表现都是不用等待服务器状态的。比如移动,
客户端本地是可以自由操作自己控制的单位。表现层需要对玩家移动过程进行差值计算等,因为逻辑帧通常低于表现帧。另外表现层玩家坐标要考虑跟逻辑层的差异,
允许一定的误差,误差过大要考虑如果缩小;目前的处理就是当玩家停止移动的时候,跟服务器这一帧的状态同步一次,差异比较小的时候,玩家不会有明显的感觉。
参考资料: