本文讨论实时多人在线游戏的服务器和客户端技术。
实时多人在线游戏主要包括2类,FPS(quake系列,UT系列,CF等),ACT(DNF,龙之谷这类)
其共同特点是需要用户操作尽快的得到体现。并且所有客户端的结果要一致。
同一世界玩家数量较少(这是有原因的,下面讨论),有较真实的物理效果模拟。
通用的C/S做法:
A. 客户端采样输入数据->Server处理数据->分发结果。
其他不可靠的实现方法:
B.客户端采样计算动作结果->通知server(校验)->分发结果
或者p2p ->>>>>>>>>>分发到其他client
这里输入数据就是用户的操作,比如开火/技能,移动这些。
A.做法所有数据在服务器上面处理,因为客户端不可能直接修改服务器数据,所以数据是可信的(利用bug除外)
由于数据在服务器处理,所以结果一定会有延迟(传输时间)
B.做法的不可靠在于数据的处理是由客户端进行,由于客户端hack可能具有很大的作弊空间(DNF/洛奇全屏秒怪什么的)。加上server端校验可以一定程度上减少作弊。
好处是所有结果本地立即获得,客户端体验很好。
另外在客户端相互作用时,因为延迟的关系,可能会有很奇怪的同步效果(跑跑卡丁车被影子撞飞)。
下面只讨论A的实现
==============================================================================
采样方式分固定采样和消息采样。
1.固定采样是以一定的时间间隔,采样所有输入的状态,主要用于fps游戏。一般为20-40hz。
2.消息类型的采样,只在输入状态变化时发送变化的数据。(就像KEYDOWN,KEYUP这样)act游戏很适合这种。
fps类游戏只能固定采样的原因是鼠标输入都是连续的数据。如果用消息的话一次鼠标move就可能产生很多的采样请求。
另外可以1,2组合为混合采样,对鼠标用固定采样,其他按键用消息采样
为了减少延迟,客户端在发出采样后,可以立即本地模拟计算结果,然后开始做预测移动。
server收到采样数据后,根据时间戳来计算真实可靠的结果,分发到每个客户端。
客户端收到自己的结果后,计算预测结果和实际值的误差,误差超过一定范围后做误差修正。
客户端收到其他客户端结果后,插值显示其他客户端状态。
延迟的影响:
假设采样速度为1单位.
设客户端A/B到服务器S ping 延迟为2单位,那么路由对称情况下a->s == s->a == 1 , b一样。
server time: 0 1 2 3 4
S 发送:s0 ar1+br1 ar2 +br2
接收: as1+bs1 as2+bs2
A 发送: as1 as2
接收: s0 ar1+br1
B 发送: bs1 bs2
接收: s0 ar2+br1
上面s0是server 的初始化消息,as/bs是a/b的采样包,ar/br是采样对应的结果。
由以上表格可见:
对A来说,他的动作会经过1/2 ping后在server体现,同时他看到的B是1/2 ping以前server上的状态。
也就是说,如果ping是2秒的话,你看见一个人,瞄准后开火,基本上都打不中人。
(里我们不关心B实际的位置,因为B实际在这个时间的位置是客户端B自己模拟的结果。我们只关心本地和server上的区别)
降低延迟影响的tick。
1.历史快照
CS Source做法是保留客户端最近的一些快照,对于高延迟玩家,选择历史快照里面最近的一个来做射击时的判断。
CSS资料里面说保留0.5秒以内快照(可设置)
引入的问题:
假设一个战壕或是其他什么东西,角色蹲在里面是完全不可能被打中的。
一个玩家蹲下后,还是有可能被高ping玩家击中(因为历史快照里面可能还是站起状态)。
2.另外一个办法是本地预测其他客户端:
因为A动作执行在1/2ping后,B现在位置是1/2 ping以前的,如果能精确计算出B在time=time+ping以后的状态的话,A瞄准预测后的B应该和Server端结果一致。
不过因为预测结果的不准确性,可能会使得玩家体验变得很差(插值预测结果可以稍微改善这个情况)。
3.Server延迟计算(Server模拟客户端状态)
令T=min(所有玩家的ping)
Server收到输入后,用输入时间-T/2 以前的server状态做射击判断,生成下一帧,发送并缓存。
一定时间后,更新所有玩家状态到下一帧。
对上面表里面情况来说
time 2时,server计算的是1时射击检测,生成的是3 的快照。
time 3时,server计算的是2时射击检测,生成的是4 的快照。
看起来很完美了。唯一的延迟在于击中效果的滞后。
和1不同的是这个算法相当于对所有玩家的ping 减去(T/2+sample rate)。例1里面的情况基本不会出现(除非所有人ping都很高,但这时仍然可以说是完全公平的)
现在的问题是,假如某个客户端ping很低,其他人ping高,那么这个算法就基本不起什么作用。
幸运的是高ping模拟低ping很难,但是低ping模拟高ping是比较容易的事情。
一个简单的做法是缓冲低ping玩家输入输出包,使其变为ping >=T的效果 ,这样可以把T设置为一个固定的值,比如100ms。
因为客户端采样帧/结果的错位,事实上所有lag降低算法都无法完全解决延迟的影响,只能是改善不同ping玩家之间的优劣势。