我正在制作Android的2D sidescroller游戏,我正在考虑将Box2D用于物理学。我想知道当使用Box2D时,如果设备的帧率从60 fps(Android的上限)下降到例如30 fps,那么所有的物理速度都会慢下来吗?
详细说明,我将使用实时在线多人游戏,因此帧速率不能作为常量来依赖。因此,如果多人游戏中的一个设备以60 fps运行而另一个设备以30 fps的速度运行并且物体应以10米/秒的速度移动,那么它将以5米/秒的速度在较慢的设备上移动吗?
我想使用Box2D,所以如果遇到这个问题,有没有办法呢?
答案 0 :(得分:3)
我认为,你的问题实际上是两个问题。
1)您是否应该在模拟过程中改变物理时间步长的速率。
不,你不应该。。您可以在游戏循环的一个时间步骤中多次迭代引擎(使用相同的步长值多次调用Step(...)
函数)。
从2.3.0 Box2D手册的第2.4节开始:
可变时间步长产生可变结果,这使得它成为可能 难以调试。因此,不要将时间步长与帧速率联系起来 (除非你真的,真的要)。
2)如何连接两个实时物理模拟并将它们的物理更新周期相互连接。
曾几何时,有一种名为帝国时代的类型改变游戏。它拥有成千上万的人工智能单位,在28.8网络上近乎实时地相互战斗。它工作得很好,有人写了一篇关于他们是如何做到这一点的文章:
我为我的更新循环调整了下面的技术和代码,以便我可以控制在两个不同的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的定点版本,但被另一个项目分散了注意力(Space Spiders Must Die!)。有一天......
对于您的特定情况,这可能不是问题(即您没有按照预期的方式进行独立模拟或执行此操作),如果不是,请不要担心。
You can see lots of other Box2D stuff (but not this game...it is not up there yet) here on my blog.
这有用吗?
答案 1 :(得分:1)
在Box2D手册中找到答案。
" 2.4模拟世界(Box2D)
.....我们也不喜欢改变的时间步骤。可变时间步长会产生可变结果,这使得调试变得困难。因此,不要将时间步长与帧速率相关联(除非你真的,真的必须这样做)......"
所以基本上,物理学不会随着帧速率而改变,但是如果你想让它们这样做,它们就可以了。
答案 2 :(得分:1)
您需要考虑两件事。
帧速率并不会真正影响移动物体的速度。即使你的帧率是@ 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 值来调整事物,从而产生一致的物理特性。这是通过试验来完成的,以查看碰撞开始失败的值,并在此时转动最大步骤。
你还必须根据你的游戏调整位置和速度迭代的数量(步骤函数中提到的数字到正确的值。每个游戏都必须根据它自己的需要调整它。保持这个至少适合你。