Search code examples
androidbox2dgame-physicsframe-rate

Does Box2D Physics rely on the framerate?


I am making a 2D sidescroller game for Android and am thinking of using Box2D for the physics. I would like to know if when using Box2D, if the framerate of the device drops from 60 fps (Android's cap) to, for example, 30 fps will all the physics slow down with it?

To elaborate, I'm going to be using real-time online multiplayer and so the framerate cannot be relied upon as a constant. So if one of the devices in the multiplayer game is running at 60 fps and another is at 30 fps and an object should be moving at 10 metres/second will it move at 5 metres/second on the slower device?

I would like to use Box2D, so if this problem is the case, is there a way around it?


Solution

  • Your question is, I think, really two questions.

    1) Should you vary the rate of the physics timestep during simulation.

    NO, you should not.. You can iterate the engine multiple times during the a time step of the game loop (call the Step(...) function multiple times with the same step values).

    From section 2.4 of the 2.3.0 Box2D manual:

    A variable time step produces variable results, which makes it difficult to debug. So don't tie the time step to your frame rate (unless you really, really have to).

    2) How can I connect two real-time physics simulations and slave their physics update cycles to each other.

    Once upon a time there was a genre-altering game called Age of Empires. It boasted thousands of AI units fighting each other in near-real-time on a 28.8 network. It worked so well, somebody wrote an article about how they did it:

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

    I adapted the technique and the code below for my update loop so that I could control the frame rate of two games running against each other on two different iPads.

    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;
    }
    

    This piece of code keeps the number of iterations executed balanced between an upper and lower maximum. One key piece to note is that the actual call to this function doesn't happen if the message from the other player has not been received in a timely fashion. This effectively slaves the two physics systems together.

    3) THE PART THAT YOU (MAYBE) REALLY SHOULD KNOW

    If you plan on using Box2D on both devices to independently run the physics, you are going to almost certainly going to see them diverge after a short time. I ran my game on an iPad 2 and iPad 3 and noticed after a few seconds they diverged (collisions occur in one, but not the other). This is because rounding behavior in floating point numbers is different based on multiple factors. For a few quick calculations, no problems. But small discrepancies creep into the lower order bits and accumulate when the values are continually cycled through integrators (as you would see, for example, in a physics simulation). Double precision helps a little, but not in the end.

    A few simple tests of looking at a specific bouncing ball in a field of bouncing balls on multiple CPUs (e.g. iPad 2 vs. iPad 3 for the exact same code) will show this. The errors creep in after a couple seconds of motion and suddenly your velocities/positions are off enough to make a difference.

    Fixed point math is a solution to this, but this way also leads to its own kind of madness. At one point, Box2D had a fixed point version, but this time has passed.

    I even toyed with cooking up a fixed point version of Box2D, but got distracted by another project (Space Spiders Must Die!). Some day...

    This may not be a problem for your particular situation (i.e. you are not doing independent simulation or doing it in a way that it works as expected), and if it is not, no worries.

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

    Was this helpful?