-
Notifications
You must be signed in to change notification settings - Fork 7
Home
这个项目简单地实现了一个网络移动的同步机制。
虽然名称叫做“同步机制”,但关键思路并不是关于如何实现同步,而是该如何在客户端实现类似插值的欺骗机制。。。
思路介绍如下:
客户端监听用户的移动操作,把移动方向direct发送给服务器端。若用户的操作是停止移动,则direct=(0,0)。(direct)
服务器端在每个逻辑帧计算单位移动,根据客户端的移动方向更新移动速度向量,并把最新的单位位置pos和移动速度向量v发送给客户端。(pos,v)
global pos -- 单位的位置(pos.x,pos.y)
global v -- 单位的移动速度向量(v.x,v.y)
LogicFrameUpdate(dt): -- 服务器端逻辑帧调用,比如每秒5次
pos+=v*dt -- 计算单位的移动
v=UpdateVectorFromMessage() -- 在移动结束后,对客户端的操作消息进行解析,获取到新的移动速度向量。实际上是v.x,v.y=(direct==(0,0)?(0,0):speed*cos(direct.x/(direct.x^2+direct.y^2)^0.5),speed*sin(direct.y/(direct.x^2+direct.y^2)^0.5))
SendMessage(pos,v) -- 把单位的位置和移动速度向量发送给客户端
基于此,在服务器端的单位移动效果大致如图:
global pos -- 单位的位置
global v -- 单位的移动速度向量
OnMessage(msg): -- 客户端收到服务器端消息的调用
pos=msg.pos -- 更新单位位置
v=msg.v -- 更新单位移动速度向量
UIFrameUpdate(dt): -- 客户端渲染帧调用
pos+=v*dt -- 单位移动
基于此,在客户端的单位移动效果大致如图:
显然,在此机制之下,服务器端在下一次改变移动状态之前会按照上次的移动速度向量一直运动,而客户端同样如此,所以:
如果网络稳定,那么服务器端的每段移动时间和客户端的每段移动时间一样,客户端将能够看到单位连续的移动。
如果网络发生抖动(延迟变化),那么服务器端有可能快于或慢于客户端的移动,客户端将不再是连续的效果,而是如上图那样的间断移动,在游戏中会呈现出一跳一跳的样子。
实际上,网络抖动导致的移动不连续,源自于单位位置突然的偏移量,因为客户端的pos直接被置为服务器端消息中的pos,如图中的虚线所示:
所以针对这种情况,我们将这个突然的偏移量分散到后续的若干秒或若干帧(比如120帧)中即可,将该值(120帧)称作偏移量分散时间。
更改客户端逻辑如下:
global pos -- 单位的位置
global v -- 单位的移动速度向量
global offset -- 与服务器消息中的单位位置之间的偏移量(offset.x,offset.y)
global fakeFrame -- 剩余的偏移帧数
OnMessage(msg): -- 客户端收到服务器端消息的调用
offset=msg.pos-pos -- 每次收到位置消息之后更新偏移量与剩余偏移帧数
fakeFrame=120 -- 根据偏移量分散时间120帧设置fakeFrame
v=msg.v -- 更新单位移动速度向量
UIFrameUpdate(dt): -- 客户端渲染帧调用
if fakeFrame>0:
pos+=v*dt+offset/120 -- 单位移动,包含了分散偏移量
fakeFrame--
else:
pos+=v*dt -- 单位移动,不包含分散偏移量
于是依此方法,在接下来的120帧中,原本肉眼可见的巨大偏移量offset,被分散成了每帧offset/120的微小偏移量。
那么,如果单位一直处于移动状态,offset/120较于v*dt是不易察觉的偏差,玩家视觉上将会看到圆润的移动效果。
①偏移量分散时间该如何设置?这涉及玩家的游戏体验:偏移量分散时间很小时,客户端能直接准确地呈现出服务器端的单位位置,但单位移动会有肉眼可见的不流畅。而偏移量分散时间很大时,单位移动流畅,但客户端会在一段时间里呈现不精确的单位位置。
此外,也可以根据具体的偏移量和移动速度动态地设置偏移量分散时间,本机制未具体考虑。
②当单位停止时客户端可能需要做些不同的处理。同时,本机制未考虑单位运动的动画效果、单位转身、单位碰撞等该如何处理。
③在若干秒内一直未收到服务器端消息的情况下,一些游戏会让单位停止移动而改为原地踏步,并一直等待服务器端消息。这个等待功能涉及到具体的帧数,本机制未实现。