Box2D blocky map. Body, Fixtures a huge map and performance
- by Solom
Right now I'm still in the planning phase of a my very first game. I'm creating a "Minecraft"-like game in 2D that features blocks that can be destroyed as well as players moving around the map.
For creating the map I chose a 2D-Array of Integers that represent the Block ID. For testing purposes I created a huge map (16348 * 256) and in my prototype that didn't use Box2D everything worked like a charm.
I only rendered those blocks that where within the bounds of my camera and got 60 fps straight. The problem started when I decided to use an existing physics-solution rather than implementing my own one. What I had was basically simple hitboxes around the blocks and then I had to manually check if the player collided with any of those in his neighborhood.
For more advanced physics as well as the collision detection I want to switch over to Box2D.
The problem I have right now is ... how to go about the bodies?
I mean, the blocks are of a static bodytype. They don't move on their own, they just are there to be collided with. But as far as I can see it, every block needs his own body with a rectangular fixture attached to it, so as to be destroyable.
But for a huge map such as mine, this turns out to be a real performance bottle-neck. (In fact even a rather small map [compared to the other] of 1024*256 is unplayable.)
I mean I create thousands of thousands of blocks. Even if I just render those that are in my immediate neighborhood there are hundreds of them and (at least with the debugRenderer) I drop to 1 fps really quickly (on my own "monster machine").
I thought about strategies like creating just one body, attaching multiple fixtures and only if a fixture got hit, separate it from the body, create a new one and destroy it, but this didn't turn out quite as successful as hoped. (In fact the core just dumps. Ah hello C! I really missed you :X)
Here is the code:
public class Box2DGameScreen implements Screen
{
private World world;
private Box2DDebugRenderer debugRenderer;
private OrthographicCamera camera;
private final float TIMESTEP = 1 / 60f; // 1/60 of a second -> 1 frame per second
private final int VELOCITYITERATIONS = 8;
private final int POSITIONITERATIONS = 3;
private Map map;
private BodyDef blockBodyDef;
private FixtureDef blockFixtureDef;
private BodyDef groundDef;
private Body ground;
private PolygonShape rectangleShape;
@Override
public void show()
{
world = new World(new Vector2(0, -9.81f), true);
debugRenderer = new Box2DDebugRenderer();
camera = new OrthographicCamera();
// Pixel:Meter = 16:1
// Body definition
BodyDef ballDef = new BodyDef();
ballDef.type = BodyDef.BodyType.DynamicBody;
ballDef.position.set(0, 1);
// Fixture definition
FixtureDef ballFixtureDef = new FixtureDef();
ballFixtureDef.shape = new CircleShape();
ballFixtureDef.shape.setRadius(.5f); // 0,5 meter
ballFixtureDef.restitution = 0.75f; // between 0 (not jumping up at all) and 1 (jumping up the same amount as it fell down)
ballFixtureDef.density = 2.5f; // kg / m²
ballFixtureDef.friction = 0.25f; // between 0 (sliding like ice) and 1 (not sliding)
// world.createBody(ballDef).createFixture(ballFixtureDef);
groundDef = new BodyDef();
groundDef.type = BodyDef.BodyType.StaticBody;
groundDef.position.set(0, 0);
ground = world.createBody(groundDef);
this.map = new Map(20, 20);
rectangleShape = new PolygonShape();
// rectangleShape.setAsBox(1, 1);
blockFixtureDef = new FixtureDef();
// blockFixtureDef.shape = rectangleShape;
blockFixtureDef.restitution = 0.1f;
blockFixtureDef.density = 10f;
blockFixtureDef.friction = 0.9f;
}
@Override
public void render(float delta)
{
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
debugRenderer.render(world, camera.combined);
drawMap();
world.step(TIMESTEP, VELOCITYITERATIONS, POSITIONITERATIONS);
}
private void drawMap()
{
for(int a = 0; a < map.getHeight(); a++)
{
/*
if(camera.position.y - (camera.viewportHeight/2) > a)
continue;
if(camera.position.y - (camera.viewportHeight/2) < a)
break;
*/
for(int b = 0; b < map.getWidth(); b++)
{
/* if(camera.position.x - (camera.viewportWidth/2) > b)
continue;
if(camera.position.x - (camera.viewportWidth/2) < b)
break;
*/
/*
blockBodyDef = new BodyDef();
blockBodyDef.type = BodyDef.BodyType.StaticBody;
blockBodyDef.position.set(b, a);
world.createBody(blockBodyDef).createFixture(blockFixtureDef);
*/
PolygonShape rectangleShape = new PolygonShape();
rectangleShape.setAsBox(1, 1, new Vector2(b, a), 0);
blockFixtureDef.shape = rectangleShape;
ground.createFixture(blockFixtureDef);
rectangleShape.dispose();
}
}
}
@Override
public void resize(int width, int height)
{
camera.viewportWidth = width / 16;
camera.viewportHeight = height / 16;
camera.update();
}
@Override
public void hide() {
dispose();
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
world.dispose();
debugRenderer.dispose();
}
}
As you can see I'm facing multiple problems here. I'm not quite sure how to check for the bounds but also if the map is bigger than 24*24 like 1024*256 Java just crashes -.-.
And with 24*24 I get like 9 fps. So I'm doing something really terrible here, it seems and I assume that there most be a (much more performant) way, even with Box2D's awesome physics.
Any other ideas?
Thanks in advance!