游戏里面移动同步是一个重要的基础模块,通常游戏中的移动以服务端为主,当发现客户端的位置和服务端的位置不同时,强行将客户端修正为服务端坐标, 其目的是为了防止使用外挂等手段作弊,却也引入一些问题:
- 用户体验不好,即使不使用外挂,因为网络延迟等问题,会经常出现移动一段时间后被服务器扯回的现象。
- 无法实现对操作要求严格的游戏,如剑三中的跳桩任务,对玩家的移动控制要求非常严格。
为什么会出现客户端服务端坐标不一致
-
玩家使用了外挂等作弊软件,服务端需要鉴别出这种情况,并修正客户端坐标。
-
客户端和服务端移动的时间间隔不同。游戏中的移动通常是从客户端发送移动指令到服务端,然后按照相同的算法双方各自运算。 当客户端和服务端移动相同的时间时,角色所在的位置应当相同。出现了坐标不一致就说明 移动时间不同 。 造成移动时间不同的原因有:
- 网络延迟不平滑。
- 服务端、客户端的运行不平滑。(也就是单位时间内运行的帧数不相等,比如游戏卡了造成追帧。)
服务端移动校正算法
如果客户端发出新的移动指令时,发送 上次移动指令的移动时间以及当前客户端坐标 到服务端,服务端接收到移动指令后, 首先模拟执行上次移动指令,由移动时间计算出正确的角色坐标,该坐标应当和当前客户端坐标相等。 如果不相等,则说明客户端进行了非法移动,需要校正客户端坐标。 另外,需要验证客户端上传上来的 上次移动指令的移动时间 是否是合理的。
客户端接受校正后的修正
服务端调整客户端坐标后应用新的移动指令,并通知客户端进行坐标调整。 客户端从校正的坐标执行新的移动指令,客户端接收校正指令的时间应当大于等于客户端通知服务端移动的时间, 这个时间差属于客户端应用新移动指令的时间,此时客户端需要将校正后的坐标进行这一时间差的修正。 一般而言,出现移动调整就意味着出现了异常状况(正常情况是不会出现移动调整)
其他客户端的移动平滑
因为服务器端的移动校正.会导致从客户端看其它玩家瞬移,解决方法是对于其他玩家的当前坐标在一定范围内的误差,不纠正。
逍遥的设计(第一版)
设计方案:
当玩家登陆的时候,Server通知Client当前Server帧数,客户端收到指令时保存当前Client帧数和Server帧数, 当Client移动时,发送Client估算出的Server帧数(估算帧数 = 当前CLient帧数 - 登陆Client帧数 + 登陆Server帧数), Server收到移动指令后,首先验证估算帧数的合法性,判断条件为:估算帧数 大于等于 上一次估算帧数 本来估算帧数还要小于等于当前Server帧数,因为服务端会卡的原因,这种情况是存在的 根据估算帧数得到Client移动时间(Client移动间隔 = 估算帧数 - 上一次估算帧数), 根据当前Server帧数得到Server移动时间(Server移动间隔 = Server当前帧数 - 上一次Server帧数), 根据两者移动时间差调整该角色坐标(快进或者回滚),然后Server和Client的当前坐标是否相同,若不同,则校正客户端坐标。
存在的问题:
客户端会跑得比服务端快(Windows的时间流逝比Linux快),导致移动不容易快进和回滚
逍遥的设计(第二版)
设计方案:
玩家连接时(握手协议),校准客户端帧数,并每帧向客户端发送帧同步,保证客户端的当前帧
- 不能大于服务端当前帧
- 不能小于服务端当前帧2秒
当Client移动时,发送Client当前帧数到服务端(注意解决差1问题:当前帧该角色是否移动过的),Server收到移动指令后,首先验证估算帧数的合法性,判断条件为:估算帧数 大于等于 上一次估算帧数,且小于等于服务端当前帧数。根据当前Server帧数得到Server移动时间(Server移动间隔 = Server当前帧数 - 上一次Server帧数),根据两者移动时间差调整该角色坐标(快进或者回滚),然后Server和Client的当前坐标是否相同,若不同,则校正客户端坐标。
存在的问题:
受网速波动比较大,网速卡则游戏卡
逍遥的设计(第三版)
玩家连接时(握手协议),校准客户端帧数,并每秒向客户端发送服务端时间,用来调整客户端基准时间
- 移动协议不能大于服务端当前理论帧(使用服务端时间进行推算,以便忽略掉服务端卡)
- 所有的协议不能小于服务端当前帧2秒
逍遥存在问题解决记录
问题:移动中释放技能后再次移动发现当前坐标不正确
原因:Server有个技能队列,当每帧Activate的时,先执行当前Action,再从技能队列中获取新技能命令。这会造成移动中释放技能的时候,Server比Client多移动了一帧的bug。
解决:调整代码顺序
问题:Server和Client的估算帧和坐标不对应
描述:Client在估算帧25帧发送移动指令并应用该指令,Server正式应用该指令是在估算帧26帧。
原因:Server 每帧的处理顺序导致,先收包,然后增加帧数,再Activate NPC
解决:无需解决,不影响
问题:Server移动命令缓存带来的问题
原因:Server中会缓存当前移动命令,待到下一次Activate的时候进行处理,这时候存在一个问题,当Client间隔几帧发送了移动指令,而Server再同一帧收到该移动指令,Server的快进的目的地不正确。
解决:临时解决 快进之前执行 KNpc::ProcCommand
问题:Client少Activate了一次
原因:KRegion::Activate的中遍历NPC的bug
问题:Server再次多跑了一帧移动,少跑了一帧技能
原因:和技能的CmdList有关,技能队列中有个不在攻击范围内跑过去的问题
方案:去掉技能队列,移动和释放技能改为立即执行,而非下一次Active的时处理
备注:要区分发送移动的指令的时机是在自己本帧的 Actice中的移动处理 前还是后,Activate前算为上帧的帧间隔,Activate后算为本帧的帧间隔,幸好,NPC有LastActive这个变量。
问题:同步协议的坐标精度和游戏逻辑坐标精度不同,导致移动一段时间后会发生坐标不一致的情况
原因:因为游戏逻辑坐标扩大1024倍,而精度是不会同步给客户端的,会造成累积误差
方案:统一游戏内的坐标系,不再分为地图坐标和格子坐标
问题:客户端和服务端同时释放技能,会存在冷却时间,目标NPC可能不在施法范围内,目标NPC位置误差造成冲刺位置不同的问题
原因:
- 因为网络延迟的波动,Client和Server两次放技能的间隔会存在不同,会存在客户端冷却了,服务端没有冷却
- 因为目标NPC会移动,存在玩家的施法范围在客户端足够,服务端不够的情况
- 因为目标NPC会移动,存在玩家冲刺的目标位置不对的情况。
方案:
- (旧版本)客户端技能的冷却时间判断增加几帧,解决客户端冷却了,但是服务端还在CD中的问题.
- (现在版本)策划反应增加几帧的冷却时间等于减了攻速,改为了通过客户端的虚拟帧计算服务端的冷却时间。
- 服务端技能释放距离扩大一定的距离(注意:不要是倍数,而是加上一定的数值),解决客户端技能可以释放,但是服务端不再技能范围内的问题
- 如果是对NPC释放的技能,带上客户端目标NPC坐标,如果是冲刺等技能,其冲刺的目标点位客户端目标NPC坐标
问题:速度调整的buff的延迟导致的移动调整
方案:服务端记录客户端当前速度,当发生速度调整且客户端没有响应前,使用客户端的当前速度
思路:以Client收到后调整速度为准,Server一律相信Client发过来的坐标包,尽量信任客户端, 必要时做后验。
问题:减速50%采用了少NPC的Activate一次的做法
方案:保证Activate次数,服务段修改为移动的时候自己判断是否要减速,客户端的速度走速度调整的流程
问题:混乱状态(恐惧)不能用了
原因:以前混乱状态走的玩家的被动移动,现在改为单独的随机移动的动作
问题:击退和拉回导致的移动调整
方案:客户端不调整,因为其目标位置客户端和服务端一致,而且肯定是服务端先到达
备注:do_drag、do_knockback、do_rand_move 要清空服务端的移动缓存,可能出现差1的问题
问题:定身(定身、麻痹、眩晕、受伤、浮空、冻结)导致移动调整
方案:把禁止移动、禁止技能单独同步
备注:受伤 以前是走得单独的一个状态(do_hurt),需要归到站立状态,并且增加显示的标志位
问题:技能释放的到服务端发现目标已经死亡,服务端放不出技能,导致移动调整
方案:发现目标死亡了,修改为对点释放, 注意:部分冲刺技能有冲刺到目标旁的需求(大于三个Cell的冲刺,只冲刺两个Cell)
备注:和策划增加约定,移动相关的技能都要支持对点释放
问题:客户端其他玩家位置不对,误差还比较大,可能造成近战打不到怪
方案:误差通常是站立的位置和服务端不一致,增加玩家的站立协议广播(NPC就算了,误差不是很大)
问题:发现护送NPC偶尔会扯到下一个点
原因:护送NPC的AI实现的不好,当走到一个点后,立即走向下一个点,没有考虑网络波动
方案1:修改AI,两次寻路间做一下短暂的休息
方案2:客户端对于该类型的NPC移动指令的起始坐标进行一些容错。(选用这种)
问题:子弹打不到,首先怀疑是sin cos精度问题,但修改为射线算法后,还是打不中,通过增加log发现,子弹没有进入目标所在的格子
原因:子弹只搜索当前所在的格子,且当格子不变化时,不进行搜索
方案:因为上面两个条件,不能修改为飞行后判断距离(因为格子不变化,不进行搜索),所以该为了子弹射向目标点所在的格子中心
问题:发现技能执行的帧数客户端和服务端不一致
原因:攻击速度,施法速度客户端和服务端不一致
方案:和移动速度一样
周边:因为攻速问题导致客户端和服务端冷却时间不一致
问题:当需要移动调整时,发现有速度的延迟应用,这时候移动调整通知客户端的速度是多少?
方案:修改为速度提前应用
周边:这个选择有多种,修改为提前应用带来的危害最小,虽然可能出现未来几个移动指令因为速度不同发生移动调整
问题:轻功回滚造成坐标不同
原因:因为轻功的移动是不均匀的,所以正确的做法应当是保存轻功时候的多个状态
方案:为什么会出现轻功向前回滚的?貌似出现在移动状态改变的延迟应用中,在这个时候判断一下,如果没有必要的状态不进行移动调整吧
问题:发现了禁止技能的标志客户端和服务端不同, 延长等待时间为18帧仍然有协议超时的情况
原因:客户端现在发送地图加载完成后,在w2c_sync_player_data_end中s2cPlayerLoginEnd调用脚本tbMgr:OnEnterGame导致耗时太久
问题:发现技能仍然有因为CD时间问题
原因:因为Server和Client对于虚拟帧+1的时机不同
问题:有一些技能使用点数来释放(如:轻功,桃花岛的AOE),会出现客户端的点数还有,服务端点数没有的bug
原因:客户端使用点数后会预扣,当服务端改变后会通知服务端改变到的数值,这样就出了问题,两边的数值没有对齐。
方案:服务端通知客户端的点数增加上虚拟帧,客户端的预扣也增加上虚拟帧,当服务端同步来的点数所在的虚拟帧大于等于客户端的虚拟帧,才应用该帧。
问题:一般来说,客户端的预扣会引发服务端的预扣,但是当技能释放失败时,会造成两边无法对齐,但这问题影响不大,一般而言成功操作一次后就对齐了。
备注:暂缓,改动中发现客户端存在着一个当为0的时候,客户端自己增加的功能。改为客户端服务端各自修改,服务端失败后会通知服务端。从外网数据来看,问题越来越严重了,但还是没有好的解决方案。
问题:加速齿轮带来了技能CD刷新加速
原因:技能CD使用的是虚拟帧,但虚拟帧的算法为了简便,直接用了客户端帧。(时刻可以与客户端对齐,对不齐的帧仅是两次操作间的部分。)
方案:将虚拟帧单独计算
问题:看其他玩家释放技能时会扯
原因:接收到其他玩家释放技能时,其服务端放技能的点和客户端的当前点有偏差,目前的流程是SetPos到服务端的点然后释放技能
方案:修改为对于玩家,在客户端的当前点释放技能,理论上问题不大
问题:其他玩家被定住后会移动
原因:客户端补充移动的原因,比如,A定住B,A看B的位置肯定不是服务端的位置,这时候有两个办法:
- 强制把A客户端中的B设成服务端的定住的位置(表现就是扯过去)
- 让B跑过去(我们选择了这一种)
方案:保持现有流程
问题:跨地图后玩家始终处于前进不正确的状态
原因:现在在跨地图的时候将虚拟帧清0了,因为使用的是 虚拟帧-上次客户端移动帧,不清0会出现虚拟帧和客户端帧对不上的问题(因为慢太多)。但是存在这样一种情况,快进时触发了跨地图,结果导致了 虚拟帧为0 ,上次客户端移动帧不为0的情况。
方案:
- 增加上次虚拟帧的记录。
- 去掉跨服时清0的问题。(不去掉,现在的版本会出现CD不正确的问题)
调试技巧
写了一个工具,将客户端和服务端的每帧坐标进行对齐,然后使用excel进行比较 公式:
=IF(EXACT(A1,I1),"",1)
=IF(EXACT(C1,K1),"",1)
然后设置 条件格式 突出显示单元格规则 等于1