代码改变世界

位不同

2010-07-31 18:14  宝宝合凤凰  阅读(312)  评论(0编辑  收藏  举报

位置同步

 

多人游戏位置同步——网络游戏的位置同步

2010年3月27日  | 标签: 位置同步, 多人游戏

有关位置同步的方案实际上已经比较成熟,网上也有比较多的资料可供参考。在《带宽限制下的视觉实体属性传播》一文中,作者也简单提到了位置同步方案的构造过程,但涉及到细节的地方没有深入,这里专门针对这一主题做些回顾。

  最直接的同步方案就是客户端在每次发生位置改变时都向服务器报告 ,服务器再转发给周围的其他玩家,其他客户端将对应的游戏实体移动到新的位置上。

  但是这样存在一个问题,每个玩家的位置都是自己先开始移动,一段时间之后才在其他玩家的客户端上表现出来。如果只是希望每个客户端上看到的游戏对象都同时开始移动,那可以让玩家的每一步操作都由服务器确认之后再执行,这样误差将缩减到不同客户端之间的网络延时差。但是显然的,这样的做法不可能真正被采用,因为这将使得玩家的游戏体验非常的糟糕。有谁能忍受连每走一步路都要卡一下的游戏呢?

  既然一定存在先后时间差,那需要一种方法来让不同客户端上看到的玩家位置不至于有太大的误差,尤其是不能有影响到游戏公平性的误差存在。根据误差出现的直接原因:时间差,我们应该能够想到一个解决方案,那就是让其他客户端设法弥补掉这段时间差内少走的距离。这样的话也就要求我们的消息包中多带一个开始移动的时间数据,用于其他客户端在收到这个消息包时计算对应的玩家实体已经移动过的时间和距离。

  我们以一个实际的例子来说明如何减少这种误差的影响。假设玩家A以速度V从P1点去到P2点,A的网络延时为T1,在A旁边有个玩家B,他的网络延时为T2。B收到服务器转发过来的移动包时,A在其自己的客户端上已经移动了T1+T2的时间,在这段时间内他自己已经走过了V*(T1+T2)的距离。如果这时在B的客户端上开始将实体A从P1移动到P2,那显然两个客户端上看到的A的位置始终存在V*(T1+T2)的误差。

  为了使A在B客户端上显示的位置与其实际位置的误差尽可能的缩小,一个简单的做法是直接将A的位置向前拖V*(T1+T2)然后开始移动,这样两者之间的误差便消除了。但这样会使得客户端的显示太鲁莽,要让其看起来平滑一些,我们可以考虑使用一些算法,比如计算出A从当前位置走到P2点还需要的时间,然后加快其速度使其在规定的时间内到达P2点,这样A和B看到的最终时间是相同的,但中间过程还是存在较多误差。另一种较好的做法是先让A以一个可接受的较快速度移动到其当前应该所在的位置稍前一点的地方,然后以正常速度移动到P2点,这样后面的移动情况与其实际移动情况基本吻合了。

  看起来这个方案很完美,但是这里却忽略了一个问题:我们假设的是每次移动时都知道玩家要去的确切位置。这在靠鼠标点击来移动的2D游戏中是正好合适的,但是在WOW一类的靠键盘来移动的3D游戏中却没有办法采用。WOW中的移动消息都只能向服务器报告当前的坐标及朝向信息。

  这类移动的位置同步其实也可以采用类似方案,服务器将移动玩家的当前位置信息广播给周围的其他玩家,当然其中也包含了时间戳。当其他玩家收到这个移动包后,表示的是在过去的某个时间里该玩家移动到了这个位置。如果只是简单地将其对应的实体移动到这个位置,那同样的,也存在位置误差。

  与上一种情况类似,如果我们知道该玩家的移动速度,再通过数据包中的时间戳,假设该玩家还在以相同的速度朝相同的方向移动,那我们也可以预测出该玩家从开始移动到现在这段时间内他走了多远了距离。我们也可以将其位置做适当的修正,并使其继续移动下去。

  我们需要先停下来考虑一下这些算法的部分细节。其中出现了一些数据是否应该包含在我们的每个消息包中,也就是我们需要考虑的另外一个问题:移动同步的消息中应该包含哪些数据,以及这些数据到底应该向哪些玩家广播。

  对于2D游戏的情况来说,我们的算法需要的数据有:玩家的速度V,玩家开始移动的时间T0,收到数据包的时间T1,玩家当前位置P1和玩家要去的位置P2。T1和P1从当前客户端上都可以取到,而速度V一般来说不会经常改变,至少不是每次移动时都不一样,所以我们可以为速度的改变设计单独的消息码,这样V值也是可以在客户端上取到的。最后,每个移动消息中包含的数据只需要有移动到的位置P2和开始移动的时间T0。

  其他客户端在使用特定算法将玩家移动到P2后会将其停在此处,直到收到下一个移动包时再开始移动。而对于在移动过程中又收到了新的移动包的处理过程基本类似,不做过多描述。

  对于3D游戏的情况,算法是基本相同的,但是没有目标点,替换为移动方向,比如是向正前方移动,还是向左或向右移动等。在这种情况下,只要没有收到玩家停止移动的消息,其他客户端上都会以最后一次收到的移动包的状态来继续模拟移动。

  所以,在网络偶尔卡一下的时候也会出现一些奇怪的现象。比如WOW中,看到队友直冲冲地走下了悬崖,你刚喊了句“怎么掉下去了?”只见队友又从身后走出来,还冒出一句:“没啊,我就在你旁边!”

  关于数据要向哪些人广播的问题,其实很简单,哪些人会看到这个玩家就需要向哪些人广播。不管是直接在主屏幕上看到还是在大地图上看到的代表其位置的一个点。但是,针对不同的人使用的广播策略还是存在差异。

  在《带宽限制下的视觉实体属性传播》一文中提出了一个方案很值得参考。该方案提出的基础是因为3D空间透视的原因,离你很远的一个玩家移动了10米,最终在你的显示器上看到的位移可能只有一个象素;而离你不到一米的一个玩家虽然只移动了一米,但最终显示出来的位移可能会有几十个象素。所以,远处玩家的移动并不需要非常严格的关注,但近处玩家的移动同步需要非常高的优先级。

  这个方案的实现还依赖于另一项技术要求,玩家的属性更新以一定的频率来进行,更新时比较一下当前属性值与上次更新时的属性值,如果存在差异则通知客户端更新,另外如果中间跳过了某次更新也不会对客户端表现及游戏公平性造成什么影响。比如这里要处理的玩家坐标,第一次移动到A点,第二次从A点又移动到B点,如果移动到A点的更新包没有发送,直接发送了移动到B点的更新包,这将不会对游戏逻辑产生大的影响。

  这套方案基本上是为3D游戏的实体属性广播优化而设计的,在2D游戏中很难使用。一是斜45度视角的2D游戏中屏幕顶端、中间和底部任何一个位置上的玩家移动,其距离和象素比是完全相同的,因为画面不存在透视,所以也就没有远处对象更新频率低这一可能。另外2D游戏中的移动与3D游戏也存在差异,具体情况前面有详细描述,2D游戏中基本上每一次移动都需要广播,不能跳过哪一个,否则玩家看到的现象就是在乱跑,这也必将影响到技能的使用等游戏逻辑。

  有关位置同步所涉及到的一些技术细节及优化方案上面描述了一部分,但是在实际的应用中是否会使用还是要看具体游戏的需求。比如大话类型的游戏,其本身对位置的精确同步就没有要求,两个客户端上出现一前一后的移动也不会影响任何的游戏逻辑,所以前面提到的同步方案可能都用不上。

  而对于一些同步要求很高的游戏,如WOW中盗贼这类职业的需求,上面的方案可能还不够细致,还需要设计更加有效的同步方案。

  另外,在位置同步过程中还有一个不容忽视的问题是外挂。我们不能像WOW那样完全依赖客户端,如果没有暴雪那样强硬的封号措施,游戏也就成为了外挂们的温床。所以,如何在服务器端模拟每个客户端的移动,如何检测出客户端是否存在作弊行为,也是需要重点关注的一个问题。

[转载自
http://school.ogdev.net/ArticleShow.asp?id=6625&categoryid=3]

 

---------------------------------------

 

多人游戏位置同步——带宽限制下的视觉实体属性传播

2010年3月27日  | 标签: 位置同步, 多人游戏

简介

The Sage of Ryzom是一款在2004年9月发布的MMOPRG,最开始发布在欧洲和北美,目前已经被本地化为3种不同的语言。它由Nevrax在2000年开发,在2006年末时由Gameforge接管。Ryzom由Nevrax团队独立开发,包括了Nevrax Library (NeL),该库基于GPL协议发布。在NeL之上,开发出了一项服务器技术用来处理高度仿真的虚拟世界。这篇文章关注于所开发出的如何平滑地移动实体和将动态属性传播给所有相同区域内其他客户端的网络技术。

工作目标

我们的客户端软件需要显示一个包含移动实体及其动画的3D动画场景。玩家会通过类似鼠标点击等行为来直接控制自己的角色。与需要对移动完全进行同步和平滑显示的第一人称射击游戏和体育游戏不大一样,角色扮演类游戏通常并不需要非常快速的输入处理。
在Ryzom 项目启动的时候,其他游戏中的实体经常会出现一些位置回退和跳跃前进的情况,我们希望在我们的游戏中,这类可以看见的位置跳转或者位置颠簸情况要比其他MMOG少。另一个重要特性是允许玩家在一个无缝的环境中移动,不会出现一些预先定义的小地理区域的加载过程,因为在这些加载时间里,玩家只能停下来等待。
在这篇文章中,将会解释一些我们曾经尝试过和最终我们所选择的技术。视觉属性是指客户端软件在渲染玩家或NPC实体时所需要的任意的动态状态或者描述属性, 比如位置和头顶文字,3D模型ID,服装,当前播放的动画,等等。我们假设客户端软件能够在本地获取到类似于3D模型,贴图和动画这些静态数据。这篇文章的大部分内容将重点关注于位置的广播,因为当平滑自然的位置路径广播未实现时,客户端将非常容易地看出来。

1、挑战
对一个在线游戏,特别是一个大量玩家同时在线的游戏来说,带宽的限制将使得我们不能直接将所有的位置改变消息发送给所有的玩家。在Ryzom项目立项的时候,56K调制解调器在玩家中仍然被广泛使用,ADSL仍处于起步阶段。我们的MMORPG必须在56K甚至14K的连接下工作。
此外,对一款在线游戏来说,作弊行为完全地毁掉了受害者的游戏体验,所以必须有技术手段来防止这类行为的出现。“不要相信客户端”已经成为一句越来越流行的格言了,而最近Nevrax创始人的计划是要将Ryzom客户端在自由软件协议之下发布,这更加迅速地显示出客户端只是一个显示和输入设备,所有的游戏逻辑都必须在服务器端完成。
在服务器端处理动态的游戏信息将有效地防止作弊行为的出现,比如一些玩家制作的用于偷看其他玩家数据的雷达。然后,避免使用点对点的网络方案,这样也导致带宽的限制不仅仅只出现在使用低带宽的玩家电脑上,在租用带宽的服务器机房也会出现。
MMORPG的游戏体验与延迟是联系在一起的,经常在发出的动作指令与其显示效果之间出现一段可以看见的而且令人非常不愉快的停顿,有时是动画停止,有时是动画颠簸。这种延迟现象必须通过设计来使其最小化。它通常是由于在严格的带宽限制和高延迟的网络环境下使用了不合适的信息广播系统而导致,也有可能是由于服务器的CPU处理导致了延迟。
我们的工作就必须关注于学习当前已知的视觉属性传播技术,并且尽可能地创建一个更好的技术。为了避免高的时间开销,软件的CPU性能也是很重要的。

2、导航预测算法 — 推理

早期的分布式仿真项目,比如DIS,只处理具有高度惯性行为的移动对象。在这项飞机飞行模拟实验中,只是标准的匀速直线运动,然后做一些渐进的变化。这样它才能通过模拟一个实际实体的复制品来复制其移动,并且应用一些行为改变事件。
比如,如果实际的实体降低速度s,一个更新数据包将会发送给它的复制体以使其也降低速度s。当然,这个更新包将会有一段无法避免的传输时间。在更新包到达之前,该复制体对象的位置还在继续按原来的速度改变,这样就必然导致了需要有一个位置修正,也使得在复制体上观察到的对象移动路径与其实际路径会有一些轻微的偏差。
这种位置上的偏差可以通过在更新包中引入额外信息来降低:比如一个表示速度从什么时候开始改变的时间戳可以使得复制体的移动路径最终与实际路径合并上,虽然在更新包传输的过程中,复制体还是会产生一个不大一样的路径。
这样,我们需要实现一种算法来将临时的不准确路径与其正确的路径进行混合,最终的结果依赖于混合的深度(比如可以使用之前收到的位置历史数据)。

为了降低更新的频率,我们可以只在原始路径和复制体的移动路径发生指定数量的偏离时才发送更新包(这可能需要在主处理器上同时运行主实体和其复制体的运动模拟,以用来对这两条路径进行比较)。现在我们就有了一个导航预测系统,该系统以古代航海学上在星星不可见时用来导航的技术而命名。


但是,在大多数MMORPG中,主要被控制的对象都是人类角色,他们的运动都是高度随机的。使用导航预测算法可能会导致非常频繁地发送状态校正更新包,网络流量和视觉上的震动都会大大增加(因为当混合后的复制体路径与原始路径相差较大时,有时候不得不做一些迅速的位置跳转)。
导航预测算法仍然可被用于交通工具,AI控制对象这类具有大体上固定移动规律的实体。为了能够处理大量玩家控制的实体,我们转向了另外一种不同的策略:传输”good-old”位置更新包,同时使用插值的方法模拟一段时间内的自动移动,并且保持对更新频率完全控制。

3、虚拟的时间空间 – 插值

Vavle的Yahn W. Bernier在2000年的游戏开发者大会上做过一段关于Half-Life & Team Fortree Networking的发言。其中有一个想法是,除了试图预测未来之外(也就是导航预测算法的原理),为什么不可以将过去的情况作为现在来处理呢?当一个客户端接收到场景内所有其他实体的所有更新包后,他们就可以非常平滑地显示出场景的实际动画。
这也是为什么会有一个延迟,也被称为滞后补偿时间(LCT),被引入到实际动作和在网络传输管道的另一端显示之间。LCT越高,所接收到的更新包也越多,移动也就会越平滑:如果一个角色到达了一个位置,但这时从服务器发来的另一个位置更新包又还没到,那么它将不得不停下来等待。
这将导致停停走走式的移动,特别是当位置更新包发送频率不规则时,这也是在通过internet发送数据时无法避免的问题。LCT的目标就是确保这样的情况不会发生,但是LCT又需要尽可能的小,因为它代表着滞后或时间不一致的情况。
如果两个玩家的屏幕挨在一起,他们显然会发现,他们的显示位置一直在交换。在另外一些情况下还可能会注意到:比如,当两个角色试图同时移动时,他们会感觉到另一个玩家一直在自己后面,因为他们控制的角色一直都没有发生位置交换(那将会是一个非常严重的操作体验)。


这也产生了一个显示在当前时间空间的对象(玩家自己控制的角色)与显示在过去时间空间的对象(远程角色对象,AI控制实体)之间的交互问题。一种解决方案是让LCT随着与角色对象的距离而变化。这一方案被称作暂时感知,或呈现时间,或者有时候叫做本地感知过滤,这是来自于对天空中星星出现情况的推理:距离越远,光线到达我们这里需要的时间就越长。
无论如何,如果我们希望使玩家附近的实体比远处的实体更准确地移动,那我们就应该将观察者附近对象的更新包的优先级设置高一些。目的是为了最大限度地减少误差幅度:跟前的一个实体(如正在战斗中的对手)最多只能出现1米以内的位置误差,而远处的一个实体尽管位置偏离了好几米,但显示出来的结果可能只有几个象素距离的误差,甚至还会更少。远处的实体应用低频率的位置更新,这样需要的LCT就可以比较大。灵活的LCT可以尽可能地减少近处玩家的延迟,同时也能够避免远处玩家实体的位置颠簸。这也意味着我们需要一种方法来控制被观察对象的更新包的发送频率。

时间同步

在多台机器之间维护时间需要一种同步机制。通常的同步方案是,在本地机器时间与服务器时间之间计算一个差值和参考时间,然后传输与参考时间之间的时间戳。但是,我们注意到一些机器的内部时钟速度不一样,这样会导致错误的同步。
此外,我们的服务器应用程序采用了一种灵活的时间系统,该系统基于一个可发送”ticks”的指挥服务,这样可以以所有的服务器应用程序都接受的比率增加当前游戏循环的速度:如果一个服务突然增加了工作负载,所有的服务都可以停下来等它,以避免恶意循环的阻塞。客户端时间同步因此基于收到的两个消息包的平均时间,假设服务器定期地发送数据包到所有的客户端。

4、更新频率控制 – 优先级策略

虽然大家都认为消费者的调制解调器和带宽在未来几年内会增加很多,但是保持一个低的传输率仍然具有降低带宽使用费用的优势。毕竟,运行一台支持数以千计的玩家同时游戏的服务器(这也是MMOG的本质)仍然需要很大的网络连接,而这也不可避免地是相当的昂贵。
在一个比较热闹的3D场景中以13kb/s的速度(一般设置的瓶颈值)传输位置更新包也给我们带来了以下问题:哪些更新包具有高的优先级 ?需要的CPU效率是多少 ?
我们也尝试了多种算法,他们的基本原理也都是相同的。对一个特定的观察者来说,在一个给定的时间:
在无缝的世界环境中决定哪些实体在观察者的周围,并且发送这个列表的更新到客户端。 根据距离来为列表中的这些实体指定优先级。 根据优先级高低顺序遍历这些实体, 比较他们当前的服务器端状态与客户端保留状态的差别,添加更新数据包到缓冲区,到缓冲区大小达到带宽瓶颈时停止。

计算可见实体

Ryzom中使用了两种主要的算法。
第一种被用于Ryzom的大陆。空间被一个网格所划分,首先获取与要计算的实体在同一网格的实体,然后以螺旋状的方式遍历周围的网格,并添加其中的实体,直接找到足够数量的实体。
对于Ryzom Ring副本来说,区域一般都比较小,所以采用了一种新的算法:根据实体当前位置与组重心的距离来创建动态组。如果实体移动到离组很远的地方,就将组一分为二:创建一个新组出来。
这两种情况的结果都是相同的:每个客户端游戏实体都与一个其可见的实体集合相关联。解释这些算法的细节已经超出了本篇文章的范畴,但接下来我们将看到这些关联和他们的属性是如何以及何时发送给客户端的。

优先级:信息的相关性

我们曾经尝试过的一种算法是通过以下方法构造的:试图将一个属性的更新频率与该属性更新所引起的观察者屏幕上受影响的象素数量相关联。实际上,第一个基于预算的算法被开发用来处理每个属性:每个元组(观察者客户端,观察的实体,属性)被赋予一个优先级,还有一个数据结构用于表示”架子和桶”,这些在发送更新包到客户端时会被浏览到。优先级帮助将一个属性的改变事件映射到一个正确的桶上。它是通过一些标准计算出来的:
从观察者到实体的距离
客户端所知道的状态拷贝与其实际数据之间的差值
对于位置来说,我们解决了当实体移动了一段很长的距离而实际差值却非常小时可能会引起的问题:比如,当在墙上行走时,开始点和结束点可能会非常接近,而在这种情况下的改变又是很重要的。我们通过比较”移动的数量”来代替移动的位置,这样我们也就不得不在每次检测到实体移动之后都进行累积。
其他属性也同样被列入考虑。比如,如果一个被观察实体突然戴上了他的帽子,而并没有移动,这个实体也同样会获得一个比较高的更新优先级。
这个算法在我们25*250*1000个元组(每个前端服务有1000个客户端,每个客户端显示250个实体,每个实体有25个动态属性)的目标上被证明是太过于耗费CPU资源的。因此,另外一个不同的算法又被开发出来,通过下面的标准为每个元组(观察者,被观察的实体)计算分值:

观察者到实体的距离

实体分值的增长与它到观察者的距离成反比,当状态更新包被发送到观察者的客户端后,分值被重置。另外一个步骤用来对特定的被观察的实体进行处理,以决定哪些属性需要发送给观察者。

决定哪些属性需要被发送

当根据优先级遍历被观察的实体时,我们会将实体当前的属性与保存下来的上一次发送更新时的属性值进行比较,发送改变过的部分。如果他们不匹配,我们会在有足够的资源时将他们包含进更新数据包中并且发送。上一节中描述过的路程跟踪被保留用来做位置仲裁。这个比较也是一个非常关键的部分,因为其被调用的次数非常多,我们也使用了相当多的手段来对其进行优化。我们随后通过仅比较那些与实体类型有关的属性来对整个步骤进行了优化。比如,我们提前已经知道在Ryzom中智能植物是一种位置固定的实体,这样我们就不需要比较它的位置属性是否已改变了。
优化
为了减少一个游戏循环中所做的操作的次数,我们添加了一个用来将游戏循环分隔为多个游戏循环的系统。这样,一次只会有一部分的可见实体做优先级计算。
另一项优化策略在于使用多处理器的优势:计算部分和发送部分被安排在不同的线程中。
低级的优化处理也做过:通过显示c++编译器生成的汇编代码,我们重新设计了数据结构,以使得处理器在获取这些数据的时候尽可能地减少缓存未命中的情况。

[转载自http://hi.baidu.com/ahidef/blog/item/e42c6718d276bc4c42a9ad53.html]