在之前的文章中《帧同步和状态同步》提到过状态同步在打击感表现方面弱于帧同步。打击感在游戏中讲究的是拳拳到肉的快感,也就是说操作、技能释放、打击反馈、扣血都能精准的衔接。
在这篇文章里来聊聊做状态同步在扣血匹配上遇到的坑。
一、近战攻击遇到的问题
对于近战攻击的扣血,可以在动作帧上挂扣血触发事件来实现比较精准的扣血匹配。但是,当攻击单位在攻击过程中死亡时,会出现攻击动作没有完成却扣了血的现象。
第一种方案
服务器给客户端发送近战攻击命令,同时服务器直接扣血,客户端等动作到砍中帧进行扣血(为了保持客户端和服务器扣血一致,即使攻击单位死了也会在同样的时间扣血)。
这个方案带来的问题:
- 如果这一刀会导致被攻击单位死亡,那么这个单位直接会在服务器死亡,死亡消息被发送到客户端,结果看到的现象是:攻击单位一抬手,对方就死了。
- 即使攻击单位在砍中帧之前死掉,被攻击单位也会在预定的时间执行扣血。可能看到的想象是:某单位没有被砍中就被扣血。
第二种方案
为了解决问题1,服务器延迟扣血。服务器给客户端发送近战攻击命令,服务器等到固定的时长(动作起手到砍中帧的时长)后进行扣血。客户端等动作到砍中帧进行扣血(为了保持客户端和服务器扣血一致,即使攻击单位死了也会在同样的时间扣血)。
这个方案解决了问题1,但是导致了问题2的衍生现象。如果已死单位已出手的这刀会杀死某单位,会看到的现象是:某单位没有被砍中就死了,比问题2现象更明显。
第三种方案
为了解决问题2以及衍生现象,尝试了扣血取消机制。新的方案是:服务器给客户端发送近战攻击命令,服务器等到固定的时长(动作起手到砍中帧的时长)后进行扣血尝试(攻击单位死了就不扣血),客户端等动作到砍中帧触发扣血(攻击单位死了就不扣血)。
这个方案带来的问题:服务器客户端血量不一致。因为加入了扣血取消机制后,扣血是否执行是不确定的。产生血量不一致的关键在于,服务器和客户端对于扣血的取消是不一致的。是什么导致了不一致:
-
从发起到扣血尝试的时间间隔不一致,如图所示,T1和T2的长度是不一致的。这是因为:(1)如果客户端采用动作帧事件触发扣血,客户端发起动作、动作到达砍中帧都存在时间误差。(2)如果客户端采用时间计算触发扣血,客户端和服务器帧的间隔不一致,有计算误差(可以尝试用统一的帧间隔处理扣血问题,但是这样会降低客户端处理扣血的帧频率,导致扣血匹配精度下降,再加上动作和时间计算之间本来就存在误差,导致近战扣血难以完美匹配动作)。
-
攻击单位死亡不一致。攻击单位的死亡由服务器推送,网络消息包的延迟是存在波动的,这就导致判定结果出现不一致。
血量不一致导致的一个比较明显的现象是:单位明明看起来没有血了,但是打不死。
第四种方案
由于血量不一致的问题感受更差,选择回到第二种方案,并加以改进。第四种方案在第二种方案的基础上加入单位死亡时flush自己发起的近战扣血的机制。这个机制消除的现象:攻击单位已经死亡(消失)一段时间后,还打出了伤害;产生的现象:攻击单位死亡时瞬间打出伤害。从感受上来说,会更容易接受一些。
二、远程攻击遇到的问题
远程攻击要解决的问题是,客户端子弹的命中和服务器推的扣血如何精准匹配。由于子弹的飞行速度很快,服务器不控制客户端子弹的飞行。服务器和客户端的子弹各自控制自己的子弹飞行和命中。理想情况下,客户端和服务器的子弹飞行时间相同,延迟稳定。扣血和客户端命中完美匹配。
然而,现实世界是不完美的。
-
飞行时间差异 这个一个原因是目标位置误差,目标单位在服务器和客户端的位置是有误差的。另一个原因是服务器和客户端的帧处理间隔不一致,这就导致两者判定命中的时间误差是不同的。
-
网络波动问题 子弹产生的消息延迟和扣血消息延迟不一致,导致客户端扣血和命中无法匹配。
以上两个比较难解决的问题,会导致扣血匹配不一致。另外还有一些容易解决的问题要注意:
- 创建即走问题。子弹在创建的当帧不要计算移动
- 子弹原点问题。有的子弹形状比较长,要注意原点是箭头、箭中、箭尾
- 客户端异步创建耗时。客户端创建子弹模型可能是异步的,不是马上创建出来的。如果创建完成才移动会存在移动误差。
还有一些措施可以掩盖扣血匹配精度问题:
- 服务器推的扣血晚到的情况:通过客户端子弹产生的命中效果来提高打击感,减弱对真实扣血的关注,可以比较好的衔接扣血。
- 服务器推的扣血早到的情况:扣血可以等待客户端子弹命中再表现出来。
另外一种远程攻击的命中方案:
在开发的过程中采用过的一个方案是:服务器不处理子弹的移动和命中,客户端子弹命中后向服务器发起一个RPC调用,返回扣血结果。这样扣血延迟是一个RTT时间。
优点是:没有各类误差
缺点是:1.RTT时间本身就有一定的延迟 2.录像需要记录更多的数据 3.录像回放存在一定的误差