How to optimize a box2d simulation in action game?

I'm working on an action game and i use box2d for physics. The game use a tiled map.

I have different types of body:

• Static ones used for tiles
• Dynamic ones for player and enemies

Actually i tested my game with ~150 bodies and i have a 60fps constantly on my computer but not on my mobile (android). The FPS drop as the number of body increase.

After having profiled the android application, i saw that the World.step took around 8ms in CPU time to execute.

Here are few things to note:

1. Not all the world is visible on screen, i use a scrolling system
2. Enemies are constantly moving toward the player so there is alaways to force applied to their body
3. Enemies need to collide between each others
4. Enemies collide with tiles

I also now that i can active/desactive or sleep/awake bodies.

Considering the fact that only a part of the enemies are possibly displayed on screen, is there any optimizations i can do to reduce the execution time of box2d simulation?

I found a guy trying an optimization based on distance of enemies from the player (link). But i seems like he just desactives far bodies (in my case, i could desactive bodies that are not visible). But my enemies need to move even when they are not visible on screen, and applying forces will not workd on inactive bodies. Should i play with sleeping bodies here?

Also, enemies are composed by two fixtures and are constantly colliding with each others and with tiles but i really never need to get notified about that. Is there anything i can do to optimize this kind of scenario?

Finally, am i wrong to try to run simulation at 60FPS on mobile and should i try to make it run at 30FPS?

• "Also, enemies are composed by two fixtures and are constantly colliding with each others and with tiles but i really never need to get notified about that." -> Do you mean that you don't care if these collisions happen or not? Or do you only care if enemies are colliding with each other/certain tiles when on screen? – NauticalMile May 27 '14 at 1:04
• Also, allowing sleep only saves cpu if your bodies actually go to sleep (i.e. they come to rest). Considering that you are applying forces to all your enemies, and they are always moving towards the player, this will likely not save you anything. – NauticalMile May 27 '14 at 1:11
• I guess i need the collisions to happen even when ennemies are not on screen since i don't want them to fall through tiles or through each other. I just don't need about being notified of thé contacts via a contact listener. – nathan May 27 '14 at 6:07
• Assuming you have a contact listener in place to detect collisions involving the player and maybe a couple of other important bodies, so the only advice I can offer is to look for the fastest out in the callback function; that is, filter collisions you don't care about as early as possible. – NauticalMile May 27 '14 at 16:14
• If you tell us a little more about your gameplay, we may be able to come up with a creative solution – NauticalMile May 27 '14 at 16:15

Now that I think about it some more, there may be a way.

Basic Idea

You say you are using a tile-based map, so you might consider implementing tile-based collision detection far away from the player. Consider the following diagram:

Dashed Lines:

• Green Screen Limits (Camera). Everything intersecting or inside this box is on-screen.
• Red Box2d Limits. Everything intersecting or inside this box is being updated by box2d.
• Blue Gameplay Limits. Everything inside this box is being updated.*

*I will refer to the space inside the blue box, but outside the red box as the tile-region.

Entities:

• Circle The player. The camera follows the player around (could be always centered on the player's position in the simplest case), but the red and green box always share the same center.
• Red Shapes Enemies. Constantly moving towards the player.
• Black Squares Obstacle Tiles.

Filled vs. Unfilled:

• Filled Shapes Box2d Active. These shapes contain active box2d bodies.
• Unfilled Shapes Box2d Inactive.

Updating

The key here is to update your enemies in 2 ways: when they are inside the red box, update them as you normally do.

When they are outside the red box (b2body is inactive), update their position discretely. That is, allow them to move to the next tile after a set amount of time (in accordance with the speed they are allowed to move as a box2d body). If you want info on how to do the path-finding, check out these two answers.

To do this you could maintain a list of tile-enemies, and a list of box2d enemies. Then you need to update the list of tile-enemies each turn.

If you are using a an ECS the implementation becomes much clearer: enemies in the tile region have a TileMove component. Which looks something like this:

TileMove

- timeOnTile //The amount of time the enemy has spent on the current tile
- timeToMove //The amount of time required to spend on the current tile before moving again
- currentDestination //Probably the player
- currentTile //The tile the enemy occupies


The TileMoveSystem::update it, or TileMove::update code might look like this:

timeOnTile += dt
if (timeOnTile >= timeToMove){
//Decide where to move; probably includes some pathfinding and/or steering behaviours
oldTile->occupant = NULL //Tell the old tile it's no longer occupied
newTile->occupant = self //Tell the new tile it's now occupied
timeOnTile = 0.0;
}


Crossing the Tile Region - Box2d Boundary

You also need to be able to check if enemies are crossing the red box boundary and decide what to do with them.

Box2d to Tiled Region - On the box2d side, it's pretty easy. I would reccomend making a sensor body that is updated with the camera's position and orientation. The dimensions of the sensor body are the same as the red box. Whenever the endContact callback is triggered from the sensor, deactivate the b2body belonging to that entity, and create/restore a TileMove component.

Tiled Region to Box2d - Going the other way, you need to do your own area check to see if any of your tiled enemies have entered the red box. This is probably the trickiest bit. My gut feeling is that you should get the AABB of the red box, and query all the tiles (a rectangular sub-region of the global grid) to see if there is an enemy/obstacle that currently occupies each tile. For each entity found with a tileMove component, do a narrow phase test to see if it's actually inside the red box.

Alternatively, just make the red box the AABB that's centered with the green box, like in the figure below

In this case there doesn't need to be a narrow phase; every occupied tile in the AABB region will activate its occupant. The drawback here is that there will need to be a few more active enemies than there would be otherwise.

In any case, once you have determined that an entity needs to move back into the box2d region you need to:

• create/restore the PhysicsBody component
• Set the transform of the box2d body to the current tile position.
• (re)activate the box2d body
• Remove the MoveTile component

Drawbacks

First, this means there is a whole other group of objects you have to maintain (the tile-entities). Also, you say you have ~150 bodies in the simulation. There's a lot of changes I've suggested, and the overhead cpu cost of determining when enemies/obstacles are inside/outside the box2d region (and transforming them appropriately) might not give the net performance gain you desire.

What I can say, is that, the more enemies you have in your game, the better this method will perform vs. the pure box2d implementation.

Finally, although I am somewhat uncertain as to the impact it will have on the overall performance, I am confident that this answers your core question: it will reduce the execution time of the box2d simulation.

• Great answer! Honestly, thanks for it. I like the idea, even if i think i should go with basic steering behavior and approximative collision for tiled entities. I don't know if it will save me overall CPU usage but i'm sure gonna try to implement something like that. If you don't mind, i may come back to you to share the results of the implementation : ). I'm going to accept this answer anyway, since, as you said, it will reduce the box2d simulation execution time. Thanks again. – nathan May 31 '14 at 9:49
• Your welcome. I would love to hear how it goes, keep us posted. – NauticalMile May 31 '14 at 13:10