简介
基本上大部分游戏都有战斗
战斗的同步机制无非两种 状态同步和帧同步
帧同步当中笔者还了解过一个分支(帧校验)
状态同步
原理:玩家发送指令到服务端 服务端进行战斗逻辑运算 将计算结果(比如玩家的位置 血量等等)发送给客户端 战斗逻辑在服务端
通俗的讲 客户端a和客户端b同一时间看到的画面可能是不一样的 但最终玩家的状态(位置 血量等)不管在哪个客户端那里看 数据都是一样的
客户端收包时机:玩家状态发生变换时会收到服务器发来的数据
好处:防作弊 以服务器的计算结果为准
缺点:宽带消耗大 服务器压力大
适用场景:回合制游戏 射击游戏(射击游戏还有客户端预测加回滚做优化)
回合制游戏
回合制游戏是最经典用状态同步的了
比如rpg和一些slg
对于回合制rpg来说
服务器处于选招状态 玩家选招
-->服务器计算结果
-->服务器处于播报状态 客户端播报
-->通知服务器播报完成
-->服务器切换成选招状态
也就是说当服务器处于选招状态时 玩家不管谁先完成选招都没有影响
这对于帧同步来说就不一样了 帧同步的是先完成选招的玩家会先出手
对于一些slg游戏来说 就没有玩家选招的状态 英雄的出招是自动的 服务器直接计算完战斗结果返回给客户端
客户端常识
一般来说客户端的代码会分为表现层和逻辑层
为了保证结果准确 服务也需要跑相同的代码 这个代码指的就是客户端的逻辑层代码
fps常见问题
射击游戏可以说是用了状态同步加帧同步的结合
对于fps游戏来说 为了降低延时感 会让客户端先做表现 或者说叫客户端预测 等到服务端运算完发回包后 进行校验是否做回滚
也就是说玩家的血量要以服务器的为准
客户端发来数据时 服务器做转发 同时服务器也在进行运算
服务器做转发的数据 客户端用来在表现层做处理 服务器运算的数据做在逻辑层
笔者的认知当中认为 客户端发指令后 服务器进行转发 每个客户端都按照指令作为输入进行战斗表现 比如播放动画 特效啥的 同时服务器也在跑战斗代码 但当服务器发来了状态信息 比如(玩家的血量 位置) 客户端在刷新这些数据
同时fps笔者认为也用到了之前的aoi九宫格的广播 比如吃鸡游戏 一张大地图这么多玩家 玩家只需要获得当前所在地图的格子和周围8个格子的玩家数据 然后进行跑战斗代码
fps游戏还有个说法是 未来的自己打过去的玩家
也就是跑帧的时候 玩家攻击了 这个时候会取前几帧其他玩家的状态来攻击 判断是否打中或者扣血
一般来说fps用udp协议
帧同步
原理:玩家发送指令到服务端 服务端只做数据转发 将指令转发给其他客户端 其他客户端收到后在本地作为输入进行战斗计算
通俗的讲 一般来说很多游戏都是60帧 或者有些是30帧 或者240帧等等
以60帧为例 每一帧就是1/60 0.16秒的间隔 每0.16秒的时间服务器都会等待客户端发来指令 如果没有指令也要发空包 然后服务器打包起来转发给每个客户端 每个客户端收到后在本地进行战斗计算 客户端每0.16秒也就是一帧看到的画面都是一样的
客户端收包时机:每一帧都要收到服务器发来的数据
好处:服务器压力小 对实时要求高的游戏体验好
缺点:容易作弊 且不适合同个画面多单位的场景
适用场景:moba游戏 fps游戏
常见问题
数据一致性:每个客户端接收同样的指令跑一样的代码 按理说得到的结果是一样的 但帧同步开发过程中最头疼的就是客户端之间数据不一致的问题 因为每个客户端跑的机器可能不一样 一些随机函数 一些底层的设计不一样
常见的就是随机数和浮点数
随机数一般都是服务器一开始做个随机种子发给客户端 然后开发者自己做一个随机函数 参数为随机种子 函数通过随机种子作为参数 输出另一个随机数 另一个随机数又作为下一次的参数
这样即可以得到一个随机数 但是每个客户端得到的随机数都是一样的 叫做假随机
还有个浮点数问题 每个机器的小数后保留的位数可能不一样
解决方法就是传输数据时不传小数 传分子和分母 然后客户端计算完后保留固定的位数
或者直接将数据放大1000倍 保留整数 再传数据
一般标准库中的hash也是不能用的 因为当你遍历hash结构时 顺序是不能保证的 一般要自己封装一套
还有一种假如某个客户端掉线了一直不发包怎么办
这种时候有两种
1服务端默认客户端发空包 不管
2服务端一直等待这个客户端 其他客户端全部卡帧等待 等到一段时间后判断该客户端下线
作弊
帧同步因为是客户端做战斗逻辑 代码在客户端 懂点技术的玩家是可以修改代码
解决方式:
1每次客户端发包的时候 把当前玩家的一些关键信息做个摘要 然后通过hash算法得到一个值
客户端发数据到服务器后 服务器把每个客户端发的值都对比 不相同的直接抛弃
2客户端发的数据 服务器同时也做一份保存 当战斗结束后服务器再跑一次逻辑 然后把结果和客户端进行对比 类似帧校验
当然这些只是防止了部分作弊 还有一些不是关键信息的作弊 比如王者荣耀的开全图 或者百里守约的自动秒准 就没办法了
开全图也有个解决办法 那就是在地图中某个地方 这个地方(正常情况下是一直看不到的)如果被看到就当作弊
协议
一般来说为了实时性 传输层的协议都是用udp 市面上比较成熟的可以用kcp
帧校验
和帧同步防作弊的思路一样的 就是客户端跑一段时间后 把多个帧的数据打包发给服务器
服务器跑相同的代码 然后验证结果 有点像帧同步和状态同步的结合 就是最终结果以服务器为主
但客户端可以先行跑逻辑
相似点
笔者认为状态同步和帧同步 除了计算战斗的逻辑分别在服务器和客户端外
笔者认为有个相似点就是
帧同步是每一帧都要求数据一致
状态同步是要求同步状态时数据一致 两次同步状态的间隔之间数据可以不一致
其实帧同步和状态同步对数据一致的要求是一样的 都是同步的时候数据要一致 只不过同步的时间间隔不一样 帧同步可能是0.16秒 状态同步可能是几秒