面对浮点非确定性,确定性游戏怎么可能?


52

为了使游戏像RTS网络游戏一样,我在这里看到了许多答案,这些建议建议使游戏完全具有确定性。那么您只需要彼此转移用户的操作,并稍微延迟所显示的内容,以便在呈现下一帧之前“锁定”每个人的输入。这样,诸如单位位置,健康状况之类的事情就不需要通过网络不断更新,因为每个玩家的模拟都是完全相同的。我也听到过建议进行重放的同一件事。

但是,由于浮点计算在机器之间,甚至在同一机器上同一程序的不同编译之间是不确定的,真的可以这样做吗?我们如何防止这一事实在整个游戏中引起波动的玩家(或重播)之间产生微小差异?

我听说有人建议完全避免使用浮点数int来表示小数的商,但这对我来说并不实用-例如,如果我需要取一个角度的余弦值怎么办?我是否真的需要重写整个数学库?

请注意,我主要对C#感兴趣,据我所知,在这方面,C#与C ++存在完全相同的问题。


5
这不是您问题的答案,但是为了解决RTS网络问题,我建议您偶尔同步设备位置和运行状况,以纠正任何漂移。究竟需要同步哪些信息将取决于游戏。您可以通过不费力地同步状态效果之类的东西来优化带宽使用。
2011年

@jhocking但这可能是最好的答案。
乔纳森·康奈尔

《星际争霸II》实际上仅在每次同步时才同步,我与朋友并排观看每台计算机的游戏重播,那里的差异(也很有意义)尤其是高延迟(1秒)。我有一些单位在屏幕上相对于他自己的地方6/7地图方格
相隔

Answers:


15

浮点是确定性的吗?

几年前,当我想使用与您相同的锁步体系结构编写RTS时,我做了很多阅读。

我对硬件浮点的结论是:

  • 如果您谨慎使用浮点标志和编译器设置,则最有可能使用相同的本机汇编代码。
  • 有一个开源RTS项目声称它们使用包装器库在不同的编译器上进行了确定性的C / C ++编译。我没有核实这一说法。(如果我没记错的话,那是关于STREFLOP图书馆的)
  • .net JIT留有很大的余地。特别是允许使用比要求更高的精度。它还在x86和AMD64上使用不同的指令集(我认为在x86上它使用x87,AMD64在它上使用某些SSE指令,它们的行为对于denorms有所不同)。
  • 复杂的指令(包括三角函数,指数,对数)尤其成问题。

我得出结论,无法确定地使用.net中的内置浮点类型。

可能的解决方法

因此,我需要解决方法。我考虑过:

  1. FixedPoint32用C#实现。虽然这不太难(我已经完成了一半),但是很小的值范围使使用起来很烦人。您必须时刻小心,以免溢出或失去太多精度。最后,我发现这并不比直接使用整数容易。
  2. FixedPoint64用C#实现。我发现这很难做到。对于某些操作,128bit的中间整数将很有用。但是.net不提供这种类型。
  3. 将本机代码用于在一个平台上确定的数学运算。在每个数学运算上产生委托调用的开销。失去跨平台运行的能力。
  4. 使用Decimal。但是它很慢,占用大量内存,并且容易引发异常(除以0,溢出)。对于财务用途来说非常好,但不适用于游戏。
  5. 实现自定义的32位浮点。起初听起来相当困难。在实现此功能时,缺少BitScanReverse内部函数会引起一些麻烦。

我的SoftFloat

受到您在StackOverflow上的帖子的启发,我刚刚开始在软件中实现32位浮点类型,结果令人鼓舞。

  • 内存表示形式与IEEE浮点数二进制兼容,因此在将它们输出到图形代码时,我可以重新解释类型转换。
  • 它支持SubNorm,无限和NaN。
  • 确切的结果与IEEE的结果并不完全相同,但是通常对于游戏来说并不重要。在这种代码中,对于所有用户而言结果都是相同的,而对最后一位数字无误才是重要的。
  • 表现不错。一项简单的测试显示,与220-260MFLOPS float进行加/乘运算(2.66GHz i3上的单线程)相比,它可以完成约75MFLOPS的处理。如果有人对.net拥有良好的浮点基准,请发送给我,因为我当前的测试非常初级。
  • 舍入可以改善。当前它会截断,这大致相当于向零舍入。
  • 还很不完整。目前,除法运算,强制转换和复杂的数学运算已丢失。

如果有人想贡献测试或改进代码,请与我联系,或在github上发出拉取请求。https://github.com/CodesInChaos/SoftFloat

其他不确定因素

.net中还有其他不确定性来源。

  • 迭代a Dictionary<TKey,TValue>HashSet<T>以未定义的顺序返回元素。
  • object.GetHashCode() 每次运行都不同。
  • 内置Random类的实现未指定,请使用您自己的。
  • 具有天真锁定的多线程会导致重新排序和不同的结果。要非常小心地正确使用线程。
  • WeakReferences丢失时,它们的目标是不确定的,因为GC可以随时运行。

23

这个问题的答案来自您发布的链接。具体来说,您应该阅读Gas Powered Games的报价:

我在Gas Powered Games工作,我可以直接告诉您浮点数学是确定性的。您只需要相同的指令集和编译器,并且用户的处理器当然符合IEEE754标准,该标准包括我们所有的PC和360客户。运行DemiGod,最高指挥官1和2的引擎依赖于IEEE754标准。更不用说市场上所有其他RTS对等游戏。

然后是下面的一个:

如果将重放存储为控制器输入,则不能在具有不同CPU体系结构,编译器或优化设置的计算机上重放。在MotoGP中,这意味着我们无法在Xbox和PC之间共享保存的重放。

确定性游戏仅在使用相同编译的文件并在符合IEEE标准的系统上运行时才具有确定性。跨平台同步的网络模拟或重放将无法进行。


2
如前所述,我主要对C#感兴趣。由于JIT会进行实际的编译,因此即使在同一台计算机上运行相同的可执行文件,我也不能保证它会“使用相同的优化设置进行编译”
BlueRaja-Danny Pflughoeft 2011年

2
有时,舍入模式在fpu中设置的方式有所不同,在某些体系结构上,fpu具有一个比精度大的内部寄存器用于计算……而IEEE754并未就FP操作的工作方式留有余地,但它没有具体说明应如何实施任何特定的数学函数,这可能会暴露出体系结构上的差异。
斯蒂芬,

4
@ 3nixios通过这种设置,游戏不是确定性的。只有具有固定时间步长的游戏才能确定。您正在争论在单台计算机上运行仿真时已经不确定的事情。
AttackingHobo

4
@ 3nixios,“我当时说过,即使时间步长固定,也无法确保时间步长始终保持不变。” 你是完全错误的。固定时间步长的要点是在更新中每个刻度始终具有相同的增量时间
AttackingHobo

3
@ 3nixios使用固定的时间步长可以确保时间步长是固定的,因为我们的开发人员不允许这样做。您误解了使用固定时间步长应如何补偿滞后。每个游戏更新必须使用相同的更新时间;因此,当对等方的连接出现滞后时,它仍必须计算它错过的每个单独更新。
Keeblebrox

3

使用定点算法。或选择一个权威服务器,使其不时地同步游戏状态-MMORTS就是这样做的。(至少,《战争元素》的工作原理是这样的。它也是用C#编写的。)这样,错误就没有机会积累。


是的,我可以使其成为客户端服务器,并处理客户端预测和推断的麻烦。这个问题是关于点对点体系结构的,这应该更容易 ...
BlueRaja-Danny Pflughoeft

好吧,您无需在“所有客户端都运行相同的代码”方案中进行预测。服务器的角色仅是同步方面的权限。
没关系

服务器/客户端只是制作网络游戏的一种方法。另一种流行的方法(尤其是对于例如。RTS游戏,如C&C或星际)是对等网络,其中有没有权威服务器。为了让这成为可能,所有的计算必须在所有客户端完全确定的和一致的-因此我的问题。
BlueRaja-Danny Pflughoeft

好吧,这绝对不像您绝对必须在没有服务器/高客户端的情况下严格按照p2p进行游戏。但是,如果绝对必须-使用定点。
没关系

3

编辑:到定点类的链接(请当心!-我没有用过...)

您总是可以退回定点算法。有人(至少要创建一个rts)已经完成了stackoverflow的相关工作。

您将支付性能损失,但这可能是(或可能不是)问题,因为.net在这里将不会特别有用,因为它不会使用simd指令。基准!

注意:显然intel的某人似乎有一个解决方案,允许您使用c#中的intel性能基元库。这可能有助于对定点代码进行矢量化以补偿性能较慢的情况。


decimal为此也可以工作;但是,这仍然具有与使用相同的问题int-如何进行触发?
BlueRaja-Danny Pflughoeft

1
最简单的方法是构建查找表,并将其与应用程序一起发送。如果精度存在问题,则可以在值之间进行插值。
2011年

另外,您也可以用虚数或四元数(如果是3D数)替换触发调用。是一个很好的解释
路加福音

也可能会有所帮助。
路加福音

在我曾经工作过的公司,我们使用定点数学构建了超声波机器,因此它肯定会为您服务。
路加福音

3

我从事过许多大型游戏。

简短的回答:如果您疯狂地严谨,但可能不值得,这是可能的。除非您使用的是固定体系结构(请参阅:控制台),否则它会很灵活,脆弱并且会带来许多次要问题,例如后期加入。

如果您阅读其中提到的一些文章,则会注意到,虽然可以设置CPU模式,但在某些奇怪的情况下,例如打印驱动程序由于位于相同的地址空间而切换了CPU模式。我遇到了一个应用程序被框架锁定到外部设备的情况,但是一个有故障的组件导致CPU由于热量而节流,并且在早上和下午报告的方式有所不同,从而使午饭后它实际上变成了另一台机器。

这些平台差异在芯片而不是软件中,因此回答您的问题C#也受到影响。诸如融合乘加(FMA)与ADD + MUL之类的指令会更改结果,因为它在内部仅舍入一次而不是两次。C通过排除FMA之类的操作使事情“成为标准”,从而使您具有更多控制权,从而迫使CPU基本完成您想要的工作-但要牺牲速度。内在函数似乎最容易出现差异。在一个项目中,我不得不掏出150年前的acos表,以获取值进行比较,以确定哪个CPU是“正确的”。许多CPU对触发函数使用多项式逼近,但并不总是具有相同的系数。

我的建议:

无论您选择整数锁定还是同步,都应独立于演示文稿处理核心游戏机制。使游戏玩法准确,但不必担心表示层的准确性。还要记住,您不需要以相同的帧速率发送所有联网的世界数据。您可以确定消息的优先级。如果您的模拟匹配率为99.999%,则无需频繁传输即可保持配对。(除了防止作弊。)

有一篇关于Source引擎的不错的文章,解释了一种进行同步的方法:https : //developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

请记住,如果您对后期加入感兴趣,则无论如何都要硬着头皮同步。


1

请注意,我主要对C#感兴趣,据我所知,在这方面,C#与C ++存在完全相同的问题。

是的,C#与C ++存在相同的问题。但是它还有很多。

例如,以肖恩·霍格雷夫斯(Shawn Hawgraves)的说法:

如果将重放存储为控制器输入,则不能在具有不同CPU体系结构,编译器或优化设置的计算机上重放。

确保这在C ++中发生是“足够容易的”。但是,在C#中,这将变得很难处理。这要归功于JIT。

如果解释器将您的代码运行一次,但是第二次JIT将其JIT化,您会怎么办?还是它在别人的机器上解释了两次,但是之后是JIT吗?

JIT不确定,因为您对其几乎没有控制。这是您放弃使用CLR的事情之一。

如果有人使用.NET 4.0来运行您的游戏,而其他人使用Mono CLR(当然是使用.NET库),那么上帝会帮助您。甚至.NET 4.0与.NET 5.0可能也有所不同。您只需要对平台的低级细节进行更多控制即可保证这种情况。

您应该能够摆脱定点数学的困扰。就是这样。


除了数学之外,还有什么不同?据推测,“输入”动作在rts中作为逻辑动作发送。例如,将单元“ a”移动到位置“ b”。我可以看到随机数生成的问题,但是显然也需要将其作为模拟输入发送。
2011年

@LukeN:问题在于Shawn的立场强烈表明编译器的差异会对浮点数学产生实际影响。他会比我知道更多。我只是将他的警告扩展到JIT和C#编译/解释领域。
Nicol Bolas

C#具有运算符重载,还具有用于定点数学的内置类型。但是,这与使用an存在相同的问题int-如何进行触发?
BlueRaja-Danny Pflughoeft

1
>如果解释器运行了您的代码,您认为会发生什么>解释一次,但是第二次将其JIT化呢?还是>在其他人的机器上两次解释它,但是>在那之后是JIT吗?1. CLR代码始终在执行前被清除。2. .net当然将float用作IEEE 754。> JIT不确定,因为您对其几乎没有控制。您的结论是造成虚假陈述的相当微弱的原因。>如果有人使用.NET 4.0运行游戏,而其他人使用Mono CLR(使用课程的.NET库),那么上帝会帮助您。甚至.NET
Romanenkov Andrey

@Romanenkov:“您的结论是虚假陈述的相当微弱的原因。” 请告诉我哪些陈述是错误的以及为什么,以便可以更正它们。
Nicol Bolas

1

在写完这个答案后,我意识到它实际上并没有回答这个问题,特别是关于浮点非确定性的问题。但是,如果他要以这种方式制作网络游戏,无论如何这可能对他有帮助。

除了将要广播给所有玩家的输入共享之外,创建并广播重要游戏状态的校验和(例如玩家位置,健康状况等)也非常有用。在处理输入时,断言针对以下内容的游戏状态校验和所有远程播放器都处于同步状态。您一定可以修复不同步(OOS)的错误,这将使之更容易-您将更早地注意到出现了问题(这将帮助您确定复制步骤),并且应该可以添加更多内容游戏状态记录可疑代码,以便您将引起OOS的所有事件括起来。


0

我认为与该博客相关的想法仍然需要定期同步才能可行-我已经看到网络RTS游戏中有足够多的错误采用了这种方法。

网络有损,缓慢,有延迟,甚至可能污染您的数据。“浮点确定性”(听起来似乎很笼统,让人怀疑)实际上是您最少的担心……尤其是如果您使用固定的时间步长。对于可变的时间步长,您将需要在固定的时间步长之间进行插值,以避免确定性问题。我认为这通常是不确定的“浮点”行为的意思-只是可变的时间步长导致积分发生偏差-与数学库或低级函数无关。

同步是关键。


-2

您使它们具有确定性。有关一个很好的例子,请参阅Dungeon Siege GDC演示文稿,了解它们如何使世界各地实现互联。

还请记住,确定性同样适用于“随机”事件!


1
这就是OP想要知道的使用浮点数的方法。
共产党鸭子
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.