网络代码帝国时代:每28.8 kbps调制解调器1,500个弓箭手

图片

译者注:这篇文章已经有17年历史了,仅从历史角度看才很有趣。 了解在28.8k调制解调器和首批Pentium时代,开发人员如何成功实现流畅的在线游戏非常有趣。

本文讨论了体系结构和实现,以及从为《帝国时代1》和《游戏2》创建多用户(网络)代码中学到的一些经验教训。 它还概述了Ensemble Studios在其游戏引擎中使用的当前和将来的网络体系结构方法。

多人帝国时代:结构要求


在1996年开始开发多人游戏代码《帝国时代》时 ,我们为实现所需的游戏性设定了非常具体的目标。

  • 与许多不同军事单位进行的大规模史诗般的历史战
  • 在多人游戏模式下最多支持8个玩家
  • 通过局域网,直接调制解调器连接和Internet进行流畅的游戏模拟
  • 目标平台支持:具有16 MB RAM和28.8 kbps调制解调器的Pentium 90
  • 通信系统应与现有引擎(Genie)一起使用
  • 在最低配置的机器上每秒稳定15帧

Genie引擎已经准备就绪,单用户模式的游戏形式开始形成。 Genie引擎是一个二维单线程游戏循环引擎。 在由瓷砖组成的世界中,精灵以256种颜色进行渲染。 随机生成的地图上充满了数千个对象:从可以砍伐的树木到奔腾的瞪羚。 执行引擎任务所需时间的大致分解(优化后):渲染图形的时间占30%,人工智能和查找路径的时间占30%,执行模拟和正式任务的时间占30%。

引擎还处于早期阶段,相对稳定,多用户通信必须使用现成的代码,而无需对现有(工作)体系结构进行重大更改。

使任务复杂化的是,完成每个模拟步骤所花费的时间可能会大不相同:渲染时间取决于用户是否观看单元,滚动或查看未开发区域,并且AI的漫长路程或战略规划会显着影响游戏动作的执行时间:振荡高达200 ms。

简短的计算表明,即使是很小的有关单位的数据集的传输,也试图实时进行更新,这极大地限制了玩家可以与之交互的单位和对象的数量。 如果仅传递X和Y坐标,状态,动作,视角和损坏,那么在游戏中最多只能有250个移动单位。

我们希望玩家能够用弹射器,弓箭手和战士摧毁希腊的城市,同时又要与海上的三大贵族进行围攻。 显然,我们需要其他方法。

同时模拟


我们不想传递游戏每个单元的状态,而是希望在每台机器上执行绝对相同的模拟,同时传递每个玩家相同的命令集。 从本质上讲,玩家的计算机必须按照战争电影的最佳传统来同步游戏过程,允许玩家发布命令,然后以相同的方式并同时执行命令,以确保游戏的身份。

最初,这种棘手的同步难以实现,但结果在其他领域带来了意想不到的优势。

基本模型增强

在最简单的概念级别上,实现同步模拟似乎非常容易。 在某些使用具有固定步长(锁定步长)和恒定时间的模拟的游戏中,这甚至有可能实现。

由于使用这种方法,它应该负责同时移动数百或数千个对象,因此即使延迟波动在20到1000毫秒之间,系统也必须保持可行,并设法处理帧处理期间的更改。

从游戏过程的角度来看,发送玩家的命令,确认所有消息,然后在继续下一步操作之前进行处理,这将是一场噩梦,不断的等待和缓慢的团队交换。 我们需要一种可以与后台并行处理游戏的方案,以等待数据交换过程的完成。

Mark [Terrano]使用了一种系统来标记将来应通过两次“数据交换动作”执行的命令( AoE中的数据交换动作与渲染帧本身是分开的)。

即,在进程1000中给出的命令被分配为在进程1002中执行(见图1)。 在1001期间,执行了0999期间给出的命令,这使我们能够接收,确认和准备要处理的消息,同时游戏继续绘制动画并进行模拟。


图1.通过两个“数据交换动作”执行的命令标记。

通常情况下,移动动作耗时200毫秒,而各队在此回合中被派出。 200毫秒后,移动停止并开始新的移动。 在游戏的每一刻,团队都要进行一步处理,然后将其接收并保存以进行下一步,然后在随后的两步中将其发送执行。

“速度控制”



图2.速度控制。

由于模拟应该始终具有完全相同的输入,因此游戏的运行速度不会比最慢的机器处理数据交换,渲染动作和发送新命令的速度快。 我们称该系统为更改笔画持续时间以在可变数据交换延迟和处理速度的条件下保持流畅的动画和游戏玩法的“速度控制”。

可以将游戏玩法称为“刹车”,原因有两个:如果一台机器的帧速率下降(或低于其他机器),则其他机器处理它们的命令,在指定的时间渲染所有内容,结果,它们不得不等待下一个动作。 在这种情况下,任何暂停都会立即变得明显。 此外,游戏由于数据交换的延迟而变慢-玩家必须等到机器接收到足够的数据才能完成移动。

每个客户端计算出认为可以始终实现的帧速率,该速率是通过平均几个帧的处理时间来计算的。 由于此值在游戏期间会根据范围,单位数量,地图大小和其他因素而变化,因此会在有关移动完成的每条消息中进行传输。

此外,每个客户还测量了自己到其他客户的“ ping时间”,反之亦然。 他还向有关移动完成的消息中的最长客户端发送了平均ping(总共2个字节用于控制速度)。

在每一步中,主机指定的机器都会分析完成消息,计算必要的帧速率以及对通过Internet传输数据的延迟进行校正。 然后,主机发送了新的帧速率和数据交换的持续时间。 图3-5显示了如何在不同条件下分解数据交换流。


图3.典型的数据交换流程。


图4.以正常机器速度通过Internet进行的高延迟数据传输。


图5.低机器速度和正常的数据传输延迟。

“数据交换进度”大约等于消息往返的ping时间,然后除以最慢的计算机在此期间平均可以执行的模拟帧数。

数据交换的持续时间经过加权,因此它可以根据Internet上数据传输延迟的变化而迅速增加,并缓慢降低至可以持续保持的最佳平均速度。 通常情况下,游戏只会在最糟糕的高峰时刻放慢速度并放慢速度-命令的传输延迟会增加,但保持平稳(并且每回合仅增加几毫秒),因为游戏会逐渐将延迟降低到最佳速度。 这创造了最大的游戏流畅度,同时提供了对不断变化的条件的调整。

保证交货


UDP用于网络层,每个客户端都参与命令排序,丢失识别和重传。 每个消息使用一对字节,指示计划执行命令的过程以及消息的序列号。 如果在移动后收到消息,则拒绝该消息,并保存传入的消息以供执行。 由于UDP的性质,Mark在接收消息时使用了以下原则:“如有疑问,应考虑丢失的消息。 如果收到的邮件混乱,收件人立即发送重新发送丢失邮件的请求。 如果收到的确认邮件晚于预计时间,则发件人只需再次发送邮件,而无需等待有关其丢失的信号。”

隐藏的好处


由于游戏计算的结果取决于所有用户执行相同的模拟,因此客户端(或客户端数据流)很难被黑客入侵和作弊。 任何以不同方式执行的模拟都标记为“不同步”,游戏停止了。 仍然有可能在本地作弊以泄露信息,但是在以后的补丁和修订中,这种漏洞相对容易修复。 安全已成为我们最大的胜利。

隐藏的问题


乍一看,似乎容易实现相同代码的两个实例的相同执行,但事实并非如此。 在项目的最初阶段,Microsoft产品经理Tim Znamenachek告诉Mark:“每个项目都有一个持续存在的错误,直到完成,该错误才会消失。 我认为就我们而言,这将是不同步的。” 他是对的。 发现不同步错误的困难随每一个小的变化而增加。 鹿在创建随机地图时的位置会稍有不同,但它们的移动会有所不同,几分钟后,由于没有肉回家,猎人会略微移开或错过长矛。 因此,有时看起来只是食物量的校验和上的差异具有很难追踪的原因。

尽管我们使用校验和检查了世界,物体,搜索路径,瞄准和所有其他系统,但总有些事情我们无法考虑。 巨大的消息跟踪量(每个50 MB)和世界对象的转储使问题变得更加复杂。 部分困难是概念上的-程序员不习惯编写在仿真中使用相同数量的随机数生成器调用的代码(是的,还生成并同步了随机数)。

图片

经验教训


在开发《 帝国时代》的网络部分时我们收到了一些可用于开发任何游戏多用户系统的课程。

了解您的用户。 对用户进行研究是了解他对多人游戏速度,所感知的刹车和命令传输延迟的期望中最重要的一步。 每种流派都是个体的,您需要了解适合您的游戏风格和管理风格的流派。

在开发过程的早期阶段,Mark和首席设计师对数据交换延迟进行了原型设计(该原型在开发过程中被多次修订)。 由于他们正在玩单人游戏,因此很容易模拟不同级别的团队转移延迟并获得玩家反馈(“控制似乎不错/缓慢/抽搐/糟糕”)。

对于RTS类型的游戏,甚至没有注意到250毫秒的命令传输延迟,在250-500毫秒时,游戏玩法相当不错,而在500毫秒或更高时,刹车变得明显。 有趣的是,玩家习惯了“游戏节奏”,并且对鼠标单击和单位反应之间的延迟抱有心理上的期望。 恒定的延迟响应比命令传输延迟(例如,从80到500 ms)的跳跃要好-在这种情况下,可以将500 ms的恒定延迟视为可玩的,而可变的延迟似乎“抽搐”并使游戏复杂化。

这迫使程序员将精力集中在确保平滑性上-最好选择更长的笔划持续时间,并确保所有内容都将保持平滑和恒定,而不是在面对常规减速情况下尽快执行操作。 速度的所有变化都应是渐进的,并且增长率应尽可能小。

我们还测量了用户对系统的要求-通常,他们大约每隔一秒半到两秒就发出命令(移动,攻击,砍伐树木),有时在激烈的战斗中每秒发出3-4个团队的峰值。 随着我们游戏中活跃行为的不断增长,对数据交换的最高要求出现在游戏的中期和接近尾声。

如果您花时间研究用户的行为,您会注意到他们的游戏方式的其他功能,这将有助于设置网络游戏。 在攻击过程中的AoE中,用户快速单击了鼠标(单击,单击,单击,单击,向前,向前,向前,向前!),这导致发出的命令数量激增。 此外,他们还派出了许多需要铺平道路的设备-在网络上传输数据的需求也达到了顶峰。 一个简单的过滤器可以在某一点上切断重复的命令,从而大大减少了此行为的负面影响。

通常,监视用户将使您能够:

  • 找出用户对游戏延迟的期望
  • 开发初期的多人原型原型
  • 查看对多用户模式的速度有害的行为。

测量是最重要的。 如果您在工作的早期阶段引入指标,那么您将学到有关数据交换系统的惊人知识。 使度量标准对测试人员可读,并使用它们来了解网络引擎内部发生的情况。

经验教训:当Mark太早推导出度量标准,并且在准备最终代码后没有再次检查消息级别(长度和频率)时,就会出现AoE中数据交换的部分问题。 人工智能之间的随机竞争,难以计算的路径以及结构不良的命令包之类的意外事件可能会导致巨大的性能问题,即使系统在其他方面运行正常也是如此。

使系统通知测试人员和开发人员似乎超出边界条件的内容–程序员和测试人员将在开发过程中看到哪些任务会加载系统; 这将在问题出现的早期阶段解决问题。

花时间向测试人员解释数据交换系统如何工作,向他们显示和解释指标-您可能会惊讶于他们会注意到何时不可避免地在网络代码中发生奇怪的故障。

通常,指标应具有以下属性:

  • 测试人员易于理解和理解
  • 指出瓶颈,刹车和问题
  • 对执行的影响很小,并且不断被发布。

开发人员培训。 教那些习惯于创建单用户应用程序的程序员去思考给出,接收和处理命令之间的分离是非常困难的。 很容易忘记,您可以询问未发生的事情,或者发出命令后几秒钟可能发生的事情。 在发送和接收时都必须检查命令的正确性。

在同步模型中,还要求程序员考虑在仿真中,代码不应依赖于任何本地因素(例如空闲时间的可用性,特殊设备或不同的设置)。 所有机器上的代码执行必须匹配。 例如,模拟中随机地形声音的存在会导致不同的游戏行为。

其他课程。 这应该是通常的常识-但如果您依赖第三方网络(在我们的示例中为DirectPlay),则编写一个独立的测试应用程序,以确认当所有者声称“保证送达”时,消息确实得到了“保证的包顺序”实际上,在处理游戏中传输的数据时,产品没有任何隐藏的瓶颈或奇怪的行为。

准备创建模拟应用程序和压力测试模拟器。 最后,我们创建了三个不同的最小测试应用程序,用于研究单个和重要问题:连接泛滥,选择对手时同时发生连接问题以及丢失保证包。

尽早使用调制解调器(以及幸运的是,使用调制解调器模拟器)进行测试; 在整个开发过程中,继续进行调制解调器测试(无论可能有多痛苦)。 ( — , , , , - ?), dialup-, LAN. , LAN.

图片

Age of Empires 2


Age of Empires 2: The Age of Kings , , The Zone. , DirectPlay , , Age of Empires .

, , «» . -. , , . . , , , .

() The Zone Age of Empires . Age of Kings , . , , , . . The Zone , . - The Zone.

图片

RTS3:


RTS3 — Ensemble (. .: Age of Mythology) . RTS3 , Age of Empires, .

  • Age of Empires 1 2 . , , , .
  • 3D: RTS3 — .
  • — .
  • TCP/IP: — - TCP/IP 56 /.
  • — , NAT.

RTS3 , Age of Empires 1 2 — — RTS3 . AOE/AOK DirectPlay, RTS3 , .

, . , , . Genie , — BANG! , .

Age of Kings , , . , , .

RTS3



6. - RTS3.

- . RTS3 - (. 6). -, , , .

. . , (, , -). ( Channels, TimeSync ..) , .

. Genie peer-to-peer, «». RTS3 , .

«» ( 7). . Age 1 2 .


7. «» .

Peer-to-peer:

  • «-» «--».
  • — ( ) , .

Peer-to-peer:

  • ( n=0 k-1 (n)), .
  • NAT.

Net.lib. RTS3 , , , , . , , , , , .


8. .

RTS3 BANG! , , , . , BANG! ( ). , , , OSI, (. 8).

Socks, 1

, Socks, API C. . . Socks .

Link, 2

2, Link, . , Link, Listener, NetworkAddress Packet, , (. 9).

  • Packet (): — , / ( ) .
  • Link (): . , . send receive , , void*.
  • Listener (): . .
  • Data stream ( ): , , , .
  • Net Address ( ): , .
  • Ping: . , .

  • 9. Link.

Multiplayer, 3
— , API net.lib. , RTS3 , / — , .

BANG! , . API , , .

  • Client (): . () ( ). , .
  • Session (): , , , . . host() join(), , , . / , .
  • Channel Ordered Channel: . . TimeSync, .
  • Shared Data: . , , .
  • Time Sync: .

Game Communications, 4

RTS3. , , . , , .

图片


改进的同步系统。 帝国时代开发团队没有人会说我们不需要更好的同步工具。 就像在任何项目中一样,在分析事后分析的开发过程时,事实证明,大部分时间都花在某些领域上,但是如果我们提前解决这些问题,则花费的时间可能更少。 在RTS3的开发之初,此类调试的首要任务就是调试同步。

RTS3同步跟踪系统主要旨在快速识别同步错误。 其他优先事项包括简化使用,处理通过系统传递的大量同步数据的能力,在发行版本中完全编译同步代码的能力以及最后通过更改变量而不是完全重新编译来完全更改测试配置的能力。

RTS3中的同步检查是使用两组宏执行的:

#define syncRandCode(userinfo)
gSync->addCodeSync(cRandSync, userinfo, __FILE__, __LINE__)


#define syncRandData(userinfo,
v) gSync->addDataSync(cRandSync, v, userinfo, __FILE__, __LINE__)


这两个宏都接收字符串参数userinfo,它是特定同步元素的名称或指示。 例如,一个同步调用可能看起来像这样:

syncRandCode("syncing the random seed", seed);

同步控制台命令和配置变量。 任何Quake mod开发人员都可以确认,控制台命令和配置变量对于开发过程非常重要。 控制台命令是使用启动配置文件,游戏内控制台或UI进行的简单函数调用,它们调用任意游戏功能。 配置变量是通过简单的功能get,set,define和toggle提供的命名数据类型,我们将其用于各种测试和设置配置参数。

Paul创建了控制台命令系统和变量配置的多人兼容版本。 在他们的帮助下,我们可以通过将标记添加到配置变量的定义中,方便地将常规配置变量(例如,enableCheating)变成多人配置变量。 如果启用此标志,则配置变量将在多人游戏内部转移,并且同步的游戏内决策(例如,关于自由转移资源的允许性)可以基于其值。 多人游戏机的控制台命令具有相似的原理-对多人游戏机的控制台命令的调用通过网络传输,并在所有客户端计算机上同步执行。

通过使用这两个工具,开发人员无需编写代码即可使用多人游戏系统。 他们可以快速添加新的测试和配置工具,并将其轻松集成到网络环境中。

总结一下


同步模拟和点对点模型已在帝国时代游戏系列中成功使用。 尽管投入大量时间来创建工具和技术来解决这种方法的主要问题(例如同步和网络指标)至关重要,但是这种架构在实时策略类型中的可行性已经得到了经验的证明。 我们对RTS3进行的后续改进导致以下事实:即使在最可怕的网络连接条件下,多人游戏与单用户也几乎没有区别。

Source: https://habr.com/ru/post/zh-CN417703/


All Articles