schteppe / p2.js

JavaScript 2D physics library
Other
2.64k stars 330 forks source link

Ideal way to represent 2D level geometry? #252

Closed danneu closed 7 years ago

danneu commented 8 years ago

I pitched this question to the HTML5 Game Dev forum with some more context and screenshots: http://www.html5gamedevs.com/topic/24183-ideal-way-to-reduce-a-2d-tile-level-into-fewer-p2bodies/

I'm using p2 on the server and client for a naive multiplayer game (live demo: http://p2-space-arena.herokuapp.com/). Currently, for each 32x32 tile, it uses a static p2.Body that contains a p2.Box

When I quadrupled the tile-map resolution of my game (going from 32x32 Body/Box pairs to 512+ 16x16 Body/Polygon pairs), the game performed poorly and had trouble hitting 60fps. The profiler told me that the CPU was spending a lot of time in the .getCollisionPairs() bit of the broadphase.

In that thread, someone pitched me the solution they use for Box2d, using a single Body that represents the level in as few, complex, concave shapes as possible. I implemented it (https://github.com/danneu/p2-space-arena/commit/91be74230232dc4a876fb48a9ab796909d98faa2) but it performed significantly worse than the naive 32x32 solution. This time the profiler told me that a lot of time was being spent in the narrowphase.

What's a good way to represent level geometry in p2 for this sort of 2D top-down arena game?

My next approach will be to subdivide the level into a few dozen contiguous bodies as an obvious middleground.

schteppe commented 8 years ago

A quick fix that you can do is to merge boxes that are next each other into one box. So instead of creating two boxes like new p2.Box(1,1); and then new p2.Box(1,1);, you could just do new p2.Box(2,1); if they are next to each other. Even better if three boxes are in a row, then you could use one p2.Box(3,1); for the three of them. You can save a lot of shapes/bodies this way.

Another approach is to remove bodies that aren't close by. You don't need to collide with them anyway?

var distanceToPlayer = p2.vec2.distance(playerBody.position, wall.position);
var isAddedToWorld = wall.world !== null;
if(distanceToPlayer < 20 && !isAddedToWorld){
    // close to the player
    world.addBody(wall);
} else if(distanceToPlayer > 20 && isAddedToWorld){
    // far away from the player
    world.removeBody(wall);
}

The best and most scalable solution to this problem would probably be a custom tile shape, that would keep all the tile data and do super quick lookups in the data when the player AABB is within one of the tiles. Like the Heightfield shape, but with 2D data.

Looking at the profiler, I too see that .getCollisionPairs() takes the most time. I also see that all the AABBs have to be updated each step since the static objects are being integrated. This should not have to be done! I will look further into this asap :)

danneu commented 7 years ago

Thanks for the feedback, @schteppe!

I wonder what impact https://github.com/schteppe/p2.js/commit/b1387e1d4ef3d907b662c840d70ba8bf401e899e will have on my game's as-is performance.

I'll give your world.{addBody,removeBody} proposal a shot, which sets the stage for render culling as well.