【问题标题】:Does Box2D Physics rely on the framerate?Box2D Physics 是否依赖帧率?
【发布时间】:2014-04-13 01:34:32
【问题描述】:

我正在为 Android 制作一个 2D 横向卷轴游戏,并且正在考虑使用 Box2D 来实现物理。我想知道在使用 Box2D 时,如果设备的帧速率从 60 fps(Android 的上限)下降到例如 30 fps,那么所有的物理速度都会随之变慢吗?

详细说明,我将使用实时在线多人游戏,因此不能将帧率作为一个常数来依赖。因此,如果多人游戏中的一个设备以 60 fps 运行,而另一个设备以 30 fps 运行,并且对象应该以 10 米/秒的速度移动,那么它在较慢的设备上会以 5 米/秒的速度移动吗?

我想用Box2D,如果出现这个问题,有没有办法解决?

【问题讨论】:

标签: android box2d game-physics frame-rate


【解决方案1】:

我认为你的问题实际上是两个问题。

1) 您是否应该在模拟过程中改变物理时间步的速率。

不,你不应该这样做。。您可以在游戏循环的一个时间步长内多次迭代引擎(使用相同的步长值多次调用Step(...) 函数)。

来自 2.3.0 Box2D 手册的第 2.4 节:

可变的时间步长产生可变的结果,这使得它 难以调试。所以不要将时间步与你的帧速率联系起来 (除非你真的,真的必须)。

2) 我如何连接两个实时物理模拟并将它们的物理更新周期相互从属。

曾几何时,有一款名为《帝国时代》的改变类型的游戏。它拥有数千个 AI 单元在 28.8 网络上近乎实时地相互战斗。效果很好,有人写了一篇关于他们是如何做到的:

  1. The article.
  2. The pdf version of the article.

我为我的更新循环调整了以下技术和代码,以便我可以控制在两个不同 iPad 上相互运行的两个游戏的帧速率。

void GameManager::UpdateGame()
{
   const uint32 MAXIMUM_FRAME_RATE = Constants::DEFAULT_OBJECT_CYCLES_PER_SECOND();
   const uint32 MINIMUM_FRAME_RATE = 10;
   const uint32 MAXIMUM_CYCLES_PER_FRAME = (MAXIMUM_FRAME_RATE/MINIMUM_FRAME_RATE);
   const double UPDATE_INTERVAL = (1.0/MAXIMUM_FRAME_RATE);

   static double lastFrameTime = 0.0;
   static double cyclesLeftOver = 0.0;

   double currentTime;
   double updateIterations;

   currentTime = CACurrentMediaTime();
   updateIterations = ((currentTime - lastFrameTime) + cyclesLeftOver);

   if(updateIterations > (MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL))
   {
      updateIterations = MAXIMUM_CYCLES_PER_FRAME*UPDATE_INTERVAL;
   }

   while (updateIterations >= UPDATE_INTERVAL)
   {
      //      DebugLogCPP("Frame Running");
      updateIterations -= UPDATE_INTERVAL;
      // Set the random seed for this cycle.
      RanNumGen::SetSeed(_cycleManager->GetObjectCycle());
      // Dispatch messages.
      _messageManager->SendMessages();
      // Update all entities.
      _entityManager->Update();
      // Update the physics
      _gameWorldManager->Update(Constants::DEFAULT_OBJECT_CYCLE_SECONDS());
      // Advance the cycle clock.
      _cycleManager->Update();
   }

   cyclesLeftOver = updateIterations;
   lastFrameTime = currentTime;
}

这段代码使执行的迭代次数在上限和下限之间保持平衡。需要注意的一个关键点是,如果没有及时收到来自其他玩家的消息,则不会发生对该函数的实际调用。这有效地将两个物理系统从属在一起。

3) 您(可能)真正应该知道的部分

如果您计划在两个设备上使用 Box2D 来独立运行物理,您几乎肯定会在短时间内看到它们出现分歧。我在 iPad 2 和 iPad 3 上运行我的游戏,并在几秒钟后注意到它们出现了分歧(碰撞发生在一个,但不是另一个)。这是因为浮点数的舍入行为因多个因素而异。对于一些快速计算,没有问题。但是,当值在积分器中不断循环时,小的差异会蔓延到低位并累积(例如,在物理模拟中)。双精度有一点帮助,但最终没有帮助。

在多个 CPU 上的弹跳球场中查看特定弹跳球的一些简单测试(例如,完全相同的代码的 iPad 2 与 iPad 3)将显示这一点。几秒钟的运动后,错误就会蔓延,突然间你的速度/位置就足够了,足以产生影响。

定点数学是解决此问题的方法,但这种方式也会导致其自身的疯狂。 Box2D曾经有一个定点版本,但这一次已经过去了。

我什至玩弄了一个 Box2D 的定点版本,但被另一个项目(太空蜘蛛必须死!)分心。总有一天……

这对于您的特定情况可能不是问题(即您没有进行独立模拟或以预期的方式进行模拟),如果不是,请不要担心。

You can see lots of other Box2D stuff (but not this game...it is not up there yet) here on my blog.

这有帮助吗?

【讨论】:

  • 我最初的问题要简单得多,我在回复该问题时发布的引文已经回答了这个问题。然而,这是一个非常有用的答案,解决了我对创建我的第一个实时多人游戏的一些担忧。我知道在相同的起始条件下,不同设备上的物理模拟会有所不同,但我没有意识到它会在几秒钟内发生!所以感谢您的提醒。您在答案的第 2 部分中发布的文章非常有用。我会尝试将其中的想法融入我的游戏中。
  • (2) 的技术来自 Michael Daley 的一本书,现在有点过时了,但最初的技术(根据本书)是基于这篇文章:sacredsoftware.net/tutorials/Animation/TimeBasedAnimation.xhtml。在某些时候,我可能发布两个“链接”模拟的代码(质量不高),但如果你好奇,我可能会分享它。
【解决方案2】:

在 Box2D 手册中找到了答案。

《2.4 模拟世界(Box2D)

.....我们也不喜欢时间步改变太多。可变的时间步长会产生可变的结果,这使得调试变得困难。所以不要将时间步与你的帧速率联系起来(除非你真的,真的必须)......”

所以基本上,物理不会随着帧速率而改变,但如果你想让它们这样做,它们可以。

【讨论】:

  • 试图根据帧速率改变物理效果最终会烧毁你的大脑并破坏游戏机制。尝试这样做会导致古怪的碰撞和无效的物理学。绝不能这样做。
  • 谢谢,我不打算让它依赖帧率 - 事实上我不希望它这样做,正是出于这些原因。我对 box2d 知之甚少,在开始使用它编写游戏之前,我想知道它是否如此,这样我就可以避免它。
【解决方案3】:

您需要考虑两件事。

帧速率并不真正影响移动物体的速度。即使你的帧速率是@10fps,你的身体仍然会以 10 米/秒的速度移动。

真正改变的是您可以在该帧速率下有效执行的位置和速度迭代次数以及碰撞检测的精度。

在较慢的帧速率下,我们经常看到物体穿过其他物体,并且碰撞以一种或另一种方式失败。

解决此问题的方法是使用固定的时间步长,即使在帧速率不连贯的情况下也是如此。我们为此使用的代码如下

float maximumStep = 1.0f/60.0f;
float progress = 0.0;

// TODO: Tune it further to prevent DEATH!
while (progress < dt)
{
    float step = min((dt-progress), maximumStep);
    // Since we do not have any post collision dynamics therefore we can get away with a one step process for velocity / position iterations.
    GameState::sharedInstance()->box2dWorld->Step(step, 8, 8);
    progress += step;
}

注意此处的 TODO 以防止死亡。虽然这种方法保证物理处理以固定的帧速率工作并且绝对没有碰撞失败,但是你必须确保你最终不会卡在上面提到的时间步循环中.

我们通过将最大步长值设置为 LARGEST 值来调整事物,从而产生一致的物理效果。这是通过试验来完成的,以查看碰撞开始失败的值并在该点旋转最大步长。

您还必须根据您的游戏调整位置和速度迭代次数(Step Function 中提到的数字为适当的值。每个游戏都必须根据自己的需要进行调整。将其保持在最低限度适合你。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-10
    • 1970-01-01
    • 2018-07-22
    • 2021-09-14
    相关资源
    最近更新 更多