简介

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

网络延迟产生的效应
在因特网环境下,延迟达到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

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

  1. 快节奏多人在线游戏网络入门系列教程(1):简介

    简介 该系列教程主要讨论快节奏多人在线游戏的网络相关的技术和算法.这是该系列教程的第一章,如果你对多人在线游戏有一定了解,可以跳过本章. 开发任何一款游戏都是一个挑战性的任务.而多人在线游戏增加了更多 ...

  2. html5游戏制作入门系列教程(八)

    今天,我已经准备了一个新的游戏 – SkyWalker.基本上 – 这是用飞飞行模拟射击类游戏.我们的目标到达终点线.这个游戏还有其它一些特点,例如使用飞机运动动画和爆炸动画,多按键处理(例如同时移动 ...

  3. html5游戏制作入门系列教程(七)

    我们继续这一系列文章,使用HTML5的canvas组件进行游戏开发.我们将要更新完善我们的第4课html5游戏制作入门系列教程(四)的游戏实例,并增加了火球,敌人和碰撞检测等功能模块.所以,现在我们的 ...

  4. html5游戏制作入门系列教程(六)

    我们继续这一系列文章,使用HTML5的canvas组件进行游戏开发.今天,我们将创建我们的第一个完整的游戏 – 打砖块.在这一课中,我会告诉你如何检测基本的碰撞和HTML5的本地存储.您可以使用鼠标和 ...

  5. html5游戏制作入门系列教程(五)

    我们继续这一系列文章,使用HTML5的canvas组件进行游戏开发.今天,这是相当完整的游戏例子 – 它会回顾经典的旧电脑游戏 – 坦克大战.我会教你使用阵列地图并教你如何检测活动对象(坦克)与环境( ...

  6. html5游戏制作入门系列教程(四)

    今天,我们继续一系列文章,使用HTML5的canvas组件进行游戏开发.今天我们要学习下元素:声音控制与动画.在我们的演示中,你会 看到一个飞龙.我们会听到持续的翅膀拍打的声音(我们将循环这个声音), ...

  7. html5游戏制作入门系列教程(三)

    今天,我们继续一系列文章,使用HTML5的canvas组件进行游戏开发.接下来,我们将开始学习如何添加动画以及一些更有趣的功能.我 们的演示将包括一艘太空船飞越时空,并使用一个新的游戏元素 – 对话框 ...

  8. html5游戏制作入门系列教程(二)

    今天,我们继续html5游戏制作入门系列的系列文章.今天,我们将继续基础知识(也许甚至是高级技巧的基础).我要告诉你如何具有渐变颜色填充对象,绘制文本,使用自定义的字体绘制文本,基本的动画,以及最重要 ...

  9. html5游戏制作入门系列教程(一)

    从今天开始,我们将开始HTML5游戏开发一系列的文章.在我们的第一篇文章中,我们将讲解在画布canvas上的基础工作,创建简单的对象,填充和事件处理程序.另外,要注意在这个阶段中,我们不会立即学习We ...

最新文章

  1. C# 中的委托和事件(1)
  2. c# redis 如何设置过期时间_Redis 过期时间与内存管理
  3. 获取清空textarea的文字内容_运用|你会做 词云图(文字云) 吗?
  4. MySQL查询实验报告_实验报告数据库的基本查询'
  5. Matlab Tricks(一)—— figure(1)
  6. chrome插件系列一:Secure Shell(替代ssh客户端)
  7. 循序渐进,一文详解微服务架构!
  8. 《软件创富----共享软件创业之道》读后感
  9. 链接脚本(Linker Script)应用实例(一)使用copy table将函数载入到RAM中运行
  10. js中使用jQuery读/写cookie的值
  11. PHP 毫秒级时间戳生成
  12. android 控制POS机图文打印(一)
  13. 跟着团子学SAP EPPM: CPM主要底表
  14. window10 彻底关闭自动更新
  15. 手把手教你如何搭建团队知识管理体系
  16. mysql 5.6 不同步_MySQL5.6配置同步复制的新方法以及常见问题的解决方法
  17. MediaPlayer播放异常问题
  18. 2022年深圳中小学生学位补贴申报时间及对象
  19. 如何理解统计中的特征函数?
  20. 引导图(TapTargetView)

热门文章

  1. 一张图片测试你的好色程度
  2. google i/o_谷歌在I / O 2017上宣布的最好的东西
  3. 杰理之ANC喇叭腔体设计【篇】
  4. CDN加速原理(转载)
  5. 又是一个夏天要过去了
  6. dayjs获取当月的下月的第一天时间
  7. eclipse关于图片上传,图片不显示,tomcat路径问题
  8. html标签的记忆巧法,巧记英语单词的“旁门左道”
  9. 用cygwin从本地向ubuntu某路径下传文件时Permission denied解决方法
  10. 瑞典爱立胜ALiSENSOR激光对中仪维修A1518/A1519/A1520等型号