androidkotlinlibgdxbox2d

The more LibGDX Box2D bodies, the slower the falling speed


I'm trying to write a test game with LibGDX Box2D, bodies can fall down quickly within the amount of 5, but if I change the amount to 10, 12 or more, the falling speed becomes slower and slower. I Cannot figure out why this happen that when the number of bodies increase, the falling speed decreases. As far as i understand, the amount of body should not affect the gravity, even if it is possible, it is only a dozen.

class GameViewActivity : ApplicationAdapter() {
    companion object {
        private const val SCALE = 2.0f
        const val PIXEL_PER_METER = 100f
        private const val TIME_STEP = 1 / 60f
        private const val VELOCITY_ITERATIONS = 6
        private const val POSITION_ITERATIONS = 20
        private const val VELOCITY_Y = -9.85f
        private const val VELOCITY_X = 0f
}

private var orthographicCamera: OrthographicCamera? = null
private var world: World? = null
private var batch: SpriteBatch? = null
private var ground: Ground? = null

private var discList = ArrayList<Disc>()
private var shapeRenderer: ShapeRenderer? = null


override fun create() {
    orthographicCamera = OrthographicCamera()
    orthographicCamera!!.setToOrtho(false, Gdx.graphics.width / SCALE, Gdx.graphics.height / SCALE)

    world = World(Vector2(VELOCITY_X, VELOCITY_Y), false)
    world!!.setContactListener(WorldContactListener())
    batch = SpriteBatch()
    val shape = PolygonShape()
    shape.setAsBox(orthographicCamera!!.viewportWidth, 2f)
    ground = Ground(world!!, shape)
    
    for (i in 1..12) {
        val d = Disc(world!!, 60f, 10f + i.toFloat()*2, 150f, 30f)
        discList.add(d)
    }
}


override fun render() {
    update()
    Gdx.gl.glClearColor(0.5f, 0.8f, 1f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)

    shapeRenderer = ShapeRenderer()
    shapeRenderer!!.begin(ShapeRenderer.ShapeType.Filled);
    shapeRenderer!!.color = Color.YELLOW;
    shapeRenderer!!.rect (
        0f,
        ground!!.body!!.position.y * PIXEL_PER_METER  - 12f / 2 / SCALE,
        orthographicCamera!!.viewportWidth * SCALE,
        2f * PIXEL_PER_METER
    )
    shapeRenderer!!.end();

    for (disc in discList) {
        val discRenderer = ShapeRenderer()
        discRenderer.begin(ShapeRenderer.ShapeType.Filled);
        discRenderer.color = disc.getColor()
        discRenderer.rect(
            300f,
            disc.body!!.position.y * PIXEL_PER_METER ,
            disc.getWidth() * SCALE,
            disc.getHeight() * SCALE
        )
        discRenderer.end()
    }
}

private fun update() {
    world!!.step(TIME_STEP, VELOCITY_ITERATIONS, POSITION_ITERATIONS)
    cameraUpdate()
}

private fun cameraUpdate() {
    orthographicCamera!!.update()
}

}

Solution

  • I am pretty sure that the problem is the update method. You only make one world.step in every render method, without taking the Gdx.graphics.getDeltaTime() into account.
    So the more objects there are, the longer the render will take, the slower the game will become.
    Example: Update + render of 5 objects takes 0.0125s. You call world.step with a fixed TIME_STEP of 1 / 60 (0.0167s), so your updated uses 0.004s more then it should. With a speed of 5 m/s and a pixel / m ratio of 100, an object would go 2 pixels further then it should.

    5m/s * 100px/m -> 500px/s
    500px/s * 0.004s -> 2px
    

    Now if you add more and more objects, the update and render will take a little longer to complete, resulting in bigger delta times. If update and render takes 0.02s now, your objects would go 1.65px less then it should.

    So the correct update should look something like this (Java code):

    float frameTime = Math.min(deltaTime, 0.25f);  // Avoid spiral of death on slow devices
    accumulator += frameTime;  // Increase member variable accumulator
    while (accumulator >= TIME_STEP) {
        world.step(TIME_STEP, VELOCITY_ITERATIONS, POSITION_ITERATIONS);
        accumulator -= TIME_STEP;
    }
    

    So basicaly you check the delta time (time between the last render and this render call). On slow devices, the delta time might get to big, resulting in a lot of world.step calls, making delta time even bigger. This is the so called spiral of death, which we avoid by limiting the delta to 0.25f.
    Now we add this delta time to an accumulator. This allows us to store the rest of the delta time, if it is not a multiple of TIME_STEP.
    Next we call world.step in a loop, as long as our accumulator is bigger then TIME_STEP.
    After all of this, the game logic has been updated correctly and we can render the changes to the screen.

    Other notes:
    You are using a pixel to meter conversion. I wouldn't do that, as LibGDX already offers Camera and Viewport for this.
    Basically thos objects allow you to specify the width in x and y and scale it up to fit the screen (in different ways).
    So if you specify a width of 8 and move by 1, you will move by 1 / 8 of the screen width, independent of the resolution.