3
\$\begingroup\$

I'm working on my first smartphone game, a simple 2D platformer built with libgdx. Game maps use the Tiled level format, so a map is just a bunch of blocks. I recently started using box2d to implement character collision and movement.

So this is probably naive, but what I did is for a level consisting of 64x50 (= 3200) blocks, I added a static body for every block to represent the physics for this geometry. I set these blocks to sleep, which I thought would be a hint for box2d to disregard these blocks when stepping the simulation unless they're actually interacting with the player (the only dynamic body), so this shouldn't hinder performance very much? I recall reading in the box2d manual that it employs a spatial algorithm to only even look at the bodies relevant to render the current frame.

Yet having these 3500 sleeping static bodies degrades performance to such a degree that even when running the game on my MacBook Pro 2012 in a Genymotion emulator, I get about 5 FPS.

I suppose I'm doing something terribly wrong. My questions would be:

  • when dealing with bodies in the thousands, am I supposed to remove/add box2d bodies for my level geometry dynamically to improve performance, e.g. based on a fixed box around the player? that sounds painful and I'm wondering why the library wouldn't be able to figure out itself
  • maybe I've just made a mistake mapping map tiles to box2d bodies? I've posted my setup code below

Any help appreciated.

Here's how I attach the static bodies to the level geometry; it just walks the map and creates a static body for every block:

private void addB2Bodies(World b2World) {
    for (int row = 0; row < blocksLayer.getHeight(); row++) {
        for (int col = 0; col < blocksLayer.getWidth(); col++) {
            BodyDef bodyDef = new BodyDef();
            bodyDef.type = BodyDef.BodyType.StaticBody;
            bodyDef.awake = false;
            bodyDef.position.set(col + 0.5F, row + 0.5F);

            final PolygonShape shape = new PolygonShape();
            shape.setAsBox(0.5F, 0.5F);
            FixtureDef fixtureDef = new FixtureDef();
            fixtureDef.shape = shape;
            fixtureDef.friction = 0;

            final Body body = b2World.createBody(bodyDef);
            body.createFixture(fixtureDef);
            blocks[col][row] = body;
        }
    }
}

I only have one dynamic body, the player. As for the simulation, I step it at 1 / 60, with the default/recommended iteration counts of 6 and 2.

\$\endgroup\$
  • 1
    \$\begingroup\$ You're right that Box2D uses spatial partitioning; many static objects shouldn't be much of a problem. Sleeping is meant to improve performance by reducing work per update. You seem to assume that Box2D is the problem, but you also talk about rendering. Have you profiled it? Is it Box2D or your rendering that's slowing stuff down? \$\endgroup\$ – Anko Dec 15 '14 at 19:12
  • \$\begingroup\$ it's definitely box2d. If I do not add the bodies as per the method definition above, framerate is back to a smooth 60fps. \$\endgroup\$ – Matthias Dec 16 '14 at 7:33
5
\$\begingroup\$

Yet having these 3500 sleeping static bodies degrades performance to such a degree that even when running the game on my MacBook Pro 2012 in a Genymotion emulator, I get about 5 FPS.

As Anko said in his comment, you shouldn't be guessing about whether this is Box2D or rendering or something else. Measure what's taking all the frame time. Use a profiler and collect hard data indicating whether it's Box2D, your custom rendering code, or some other game logic that's taking up all that time. This problem may literally have nothing to do with Box2D at all.

maybe I've just made a mistake mapping map tiles to box2d bodies? I've posted my setup code below

You should not create a new body for each tile.

That's not only inefficient but has various implications for the simulation that you'll likely run into before too long. Namely, dynamic bodies can "catch" on the inner edges between adjacent tiles due to how Box2D models its physics integration and collision detection (it's a discrete engine, not a continuous engine).

Look at the Edge and Chain shapes in the Box2D manual. These allow you to generate chunks of static geometry that cover many times with a single body and without inner edges to catch on. Generating correct edge and chain shapes will require a little post-processing of your tile data.

\$\endgroup\$
  • \$\begingroup\$ This thought occurred to me, but I should have mentioned that map geometry is destructible in my case. Since Box2d does not support concave geometry, modeling the map terrain as a single static body would mean I'd have to dynamically break it up into smaller convex bodies if a tile gets destroyed. I'm not sure if there are algorithms that can do that, but I'll research in that direction. I wanted to keep complexity to a minimum in these early stages. I'm still confused as to what the problem actually is, as the other poster said that the number of static sleeping bodies shouldn't really matter \$\endgroup\$ – Matthias Dec 16 '14 at 7:43
  • 1
    \$\begingroup\$ @Matthias: it's pretty trivial. With Edge and Chain shapes the concave thing isn't relevant (really, please read the docs) and you can regenerate the shapes for chunks of terrain that are changed as even a naive approach to an edge generation algorithm is still pretty cheap for any reasonable chunk size. \$\endgroup\$ – Sean Middleditch Dec 16 '14 at 8:08
2
\$\begingroup\$

Okay I feel like an idiot now. The problem was that I had the box2d debug renderer running all along. The only thing it does is render a bunch of lines around boxes which I didn't think would be that costly, and I found it to be quite useful so never turned it off during development.

Thanks for your input though.

PS: Still curious though as to why it would drop my frame rate so much. It really only drew a grid around the tiles in my game. How can it be that expensive?

\$\endgroup\$
  • 1
    \$\begingroup\$ The limiting factor tends not to be geometry count, but draw call count. A debug layer can get away with individual draw calls for each thing they want to draw, with performance characteristics as you outline. Your own code probably batches properly, I reckon. \$\endgroup\$ – Lars Viklund Dec 22 '14 at 16:53
  • 1
    \$\begingroup\$ One more thing: the performance of the debug renderer improves vastly when turning off AABBs. I had set this to true (it's false by default). \$\endgroup\$ – Matthias Aug 23 '15 at 10:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.