快节奏多人在线游戏网络入门系列教程(2):客户端预测与服务器协调

简介

上一篇文章中,我们简单介绍了权威服务器的体系。客户端发送交互信息给服务器,服务器周期性的更新游戏状态,然后返回游戏状态给客户端。
这个简单体系会导致用户发送命令时和屏幕渲染响应之间的延迟。产生延迟的原因是客户端发送命令给服务器,加上服务器返回结果给客户端时网络的延迟。
这里写图片描述

网络延迟产生的效应

在因特网环境下,延迟达到0.1秒时,玩家的游戏体验会开始受到影响。更大的延迟会导致游戏体验的极度破坏。本文将寻找降低甚至消灭这种延迟的方案。

客户端预测

虽然说存在作弊的玩家,但是绝大部分时间中,服务器处理的是正常的请求(如正常玩家的请求,以及作弊玩家的非作弊请求)。也即是说,客户端绝大部分输入应该是合理的。
我们可以合理的利用这一点来改进我们的架构。对于一个确定性的游戏世界(即,给定一个初始的游戏状态和一系列游戏输入,最终的游戏状态总是确定性的,即可预测的)。
下面用一个例子来做说明。(译者注:该例子可以理解为方格类游戏,按一下方向键移动一个方格)
假设客户端发送输入到收到服务器反馈的延迟是100ms,玩家移动一个单位方格的动画渲染时间也是100ms。采取简单的实现,整个过程需要200ms。如下图所示:

这里写图片描述

网络延迟+动画

由于游戏世界的确定性,我们可以假设客户端发送给服务器的命令一定会被正确执行。在这个假设上,客户端可以在用户发送命令后先行预测游戏结果。当然,绝大部分情况下,这样做都是正确的。
因此,客户端可以在用户发送命令后不再等待服务器返回新的游戏状态,而是开始渲染自己认为的新的游戏状态。当客户端收到服务器返回的新的游戏状态时,二者状态应该是一致的。如下图所示:

这里写图片描述

客户端动画播放与服务器验证状态同步进行

在这种情况下,玩家感觉不到任何的延迟,且服务器仍然是权威的。(既使玩家作弊发送了不合理的输入,该作弊玩家能够在自己的屏幕上渲染出他想要的结果,但是并不会影响服务器的游戏状态,且不会影响其他玩家看到的游戏状态。)

同步机制

注意在上面的例子中,我们精心设置的数字使得一切看上去过于完美。现在我们对数字做一些修改:假设延迟为250ms,动画时间仍然是100ms。进一步的,我们假设用户连续按了两次右箭头键——即用户向右移动2个方格。使用目前的架构,我们看看会发生什么:

这里写图片描述

客户端预测与服务器权威状态发生冲突

有意思的事情发生了。当时间t=250ms时,我们收到了服务器返回的状态x=11,而此时客户端本地的状态是x=12。由于服务器的状态是权威的,客户端必须服从该状态而将状态更新为x=11。而当t=350ms时,服务器又返回状态x=12,于是客户端又必须服从该状态而将状态更新为x=12。
站在玩家的视角上,整个过程中我们看到玩家按了两次右箭头键后,玩家从x=10移动到x=12,动画持续200ms,而50ms后突然跳到x=11,而100ms后又突然跳到x=12的诡异现象。

服务器协调

解决这个问题的关键是要理解客户端看到的游戏世界是“现在时”。由于网络的延迟,其从服务器获取到的最新游戏状态是“过去时”。当服务器发送最新游戏状态给客户端时,服务器并没有处理所有客户端发送的命令(有些命令还在路上)。
解决这个问题并不困难。首先,客户端给每个命令加上一个序号。例如,第一个指令为#1,第二个为#2.当服务器返回状态时,将包含他最后处理的那个命令的序号。如下图:
这里写图片描述

客户端预测+服务器协调

现在,当t=250时,客户端收到服务器的返回“我收到你发送的最后指令是#1,你现在的状态为x=11”。服务器的权威状态里x=11。
那么客户端如何处理呢?我们假设客户端本地保存了一份他发送给服务器的所有命令的队列。收到服务器的返回后,客户端知道,#1命令已经处理完毕,因此可以把#1命令从队列中移除,同时客户端也知道,服务器还没有处理#2命令。因此,客户端先用服务器返回的权威状态更新自己的状态(即x=11),然后再次预先执行#2命令。
因此,t=250时,客户端接收了服务器返回的“x=11,最后执行指令为#1”状态,更新当前状态到x=11,并从队列中移除#1指令,而重新获取#1之后的所有指令(这里只有#2)进行预计算,从而将玩家移动到x=12的位置。
时间继续前进。当t=350ms时,客户端再次收到服务器返回的状态“x=12,最后执行指令为#2”。此时,客户端将#2从队列中移除,更新当前状态到x=12。而此时队列为空,无需进行预计算。此刻,客户端状态与服务器状态完全一致。

其他事项

上面的例子是以玩家的位置移动来作说明的,在实际中,几乎任何命令都是与此类似的。例如回合制战斗游戏,一个玩家攻击另外一个玩家,你可以马上显示击打特效(血花四溅)以及头上冒出的掉血数字,但是你不能马上更新被攻击玩家的生命值,除非服务器给你发送了该玩家生命值的最新状态。
由于游戏状态的复杂性,有些状态是难以做逆运算的。例如除非服务器告诉你一个玩家被你砍死,否则你是不能让玩家死亡的,哪怕被攻击玩家生命值已经为负数(考虑这种情况:A玩家最后一击将B玩家血量打到负数的时候,B玩家使用了急救包,而这个信息服务器还没来得及传送给A玩家。)
(译者注:如果A玩家客户端直接播放B玩家死亡动画,一段延迟后A玩家收到B玩家最新状态,B玩家突然又满血站起来跟A玩家战斗,会显得万分诡异。)
这来带了一个新的有趣问题——既使游戏世界是完全确定性的,既使没有玩家作弊,在当前的客户端预测与服务器协调的框架下,客户端的状态与服务器的状态仍然会发生冲突。如果只有一个客户端与服务器相连,这种冲突不会发生。而这恰好是多人在线游戏中必须考虑的问题。我们将在下一篇文章中讨论该内容。

总结

在使用了权威服务器时,客户端要利用等待服务器返回的时间中作出预响应。具体的,客户端提前模拟发出的命令。当服务器返回状态时,客户端用最新的状态更新本地状态,并重新计算服务器还未来得及处理和返回的命令。

================================================
原文链接:http://www.gabrielgambetta.com/fpm2.html

阅读更多
文章标签: 服务器 游戏 网络
个人分类: 游戏 服务器
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭