Search code examples
androidlibgdxbox2dviewportscaling

Need help scaling screen in LibGDX/Box2d


I've been creating a simple game for android/desktop using the libGDX framework. It's about done, but I've had a lot of trouble getting it to scale to different screen ratios. I am currently using a FitViewport, which works fine for keeping my desired screen ratio, but I'd like to make it so that the black bars on the top and bottom are colored for the sky and ground respectively. I've also tried not using a viewport at all, then adding the colors using a ShapeRenderer, but it just stretches all of the art to fit the screen, doesn't use my desired screen ratio, and ignores the ShapeRenderer shapes.

Here's my GameRenderer class's constructor code:

public GameRenderer (GameWorld gameWorld) {
    this.gameWorld = gameWorld;

    camera = new OrthographicCamera();
    camera.setToOrtho(false, V_WIDTH / PPM, V_HEIGHT / PPM);

    uiCamera = new OrthographicCamera();
    uiCamera.setToOrtho(false, V_WIDTH, V_HEIGHT);

    batch = new SpriteBatch();
    batch.setProjectionMatrix(camera.combined);

    viewport = new FitViewport(V_WIDTH / PPM, V_HEIGHT / PPM, camera);
    viewport.apply();

    shapeRenderer = new ShapeRenderer();
    shapeRenderer.setProjectionMatrix(camera.combined);

    debugRenderer = new Box2DDebugRenderer();
    frog = gameWorld.getFrog();
    jumpRope = gameWorld.getJumpRope();

    score = gameWorld.getScore() + "";
    highScore = AssetLoader.getHighScore() + "";
}

And here is the render() method:

    public void render () {
    Gdx.gl.glClearColor(0, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    score = gameWorld.getScore() + "";
    highScore = AssetLoader.getHighScore() + "";

    batch.begin();
    batch.draw(AssetLoader.background, 0, 0, 236 / PPM, 300 / PPM);

    batch.setProjectionMatrix(uiCamera.combined);
    if (gameWorld.isReady() || gameWorld.isTitleScreen() ) {
        AssetLoader.shadow.getData().setScale(0.4f, 0.4f);
        AssetLoader.shadow.draw(batch, "Tap to start!", 23, V_HEIGHT - 60);

        AssetLoader.font.getData().setScale(0.4f, 0.4f);
        AssetLoader.font.draw(batch, "Tap to start!", 22, V_HEIGHT - 61);
    } else {
        if (gameWorld.isGameOver()) {
            AssetLoader.shadow.getData().setScale(0.4f, 0.4f);
            AssetLoader.shadow.draw(batch, "Game Over", 40, V_HEIGHT - 60);

            AssetLoader.font.getData().setScale(0.4f, 0.4f);
            AssetLoader.font.draw(batch, "Game Over", 39, V_HEIGHT - 61);

            AssetLoader.shadow.draw(batch, "High Score: " + highScore, 15, V_HEIGHT - 90);
            AssetLoader.font.draw(batch, "High Score: " + highScore, 14, V_HEIGHT - 91);
        } else {
            AssetLoader.shadow.getData().setScale(0.55f, 0.55f);
            AssetLoader.shadow.draw(batch, score, (V_WIDTH / 2) - (8 * score.length()), V_HEIGHT / 1.25f);

            AssetLoader.font.getData().setScale(0.55f, 0.55f);
            AssetLoader.font.draw(batch, score, (V_WIDTH / 2) - (8 * score.length()) - 1, V_HEIGHT / 1.25f - 1);
        }
    }

    batch.setProjectionMatrix(camera.combined);

    if (jumpRope.getJumpRopeBehindFrog()) {
        jumpRope.draw(batch);
    }

    if (gameWorld.getFrog().getY() > -60 / PPM) {
        frog.draw(batch);
    }

    if (!jumpRope.getJumpRopeBehindFrog()) {
        jumpRope.draw(batch);
    }

    batch.end();

    debugRenderer.render(gameWorld.getWorld(), camera.combined);

    gameWorld.getWorld().step(1/60f, 6, 2);
}

I use the main OrthographicCamera camera for all of my gameplay and Box2D stuff, and I use the uiCamera for all of the UI, as the name implies. Any and all help is appreciated!

After trying to add a backStage:

    public GameRenderer (GameWorld gameWorld) {
    this.gameWorld = gameWorld;

    camera = new OrthographicCamera();
    camera.setToOrtho(false, V_WIDTH / PPM, V_HEIGHT / PPM);

    uiCamera = new OrthographicCamera();
    uiCamera.setToOrtho(false, V_WIDTH, V_HEIGHT);

    batch = new SpriteBatch();
    batch.setProjectionMatrix(camera.combined);

    // Test code
    backViewport = new ExtendViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

    backStage = new Stage();
    backStage.setViewport(backViewport);
    //

    viewport = new FitViewport(V_WIDTH / PPM, V_HEIGHT / PPM, camera);
    viewport.apply();

    shapeRenderer = new ShapeRenderer();
    shapeRenderer.setProjectionMatrix(camera.combined);

    debugRenderer = new Box2DDebugRenderer();
    frog = gameWorld.getFrog();
    jumpRope = gameWorld.getJumpRope();

    score = gameWorld.getScore() + "";
    highScore = AssetLoader.getHighScore() + "";

    // Test code
    backStage.addActor(new Image(AssetLoader.backgroundColor));
}

public void render () {
    Gdx.gl.glClearColor(0, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    score = gameWorld.getScore() + "";
    highScore = AssetLoader.getHighScore() + "";

    batch.begin();
    batch.draw(AssetLoader.background, 0, 0, 236 / PPM, 300 / PPM);

    batch.setProjectionMatrix(uiCamera.combined);
    if (gameWorld.isReady() || gameWorld.isTitleScreen() ) {
        AssetLoader.shadow.getData().setScale(0.4f, 0.4f);
        AssetLoader.shadow.draw(batch, "Tap to start!", 23, V_HEIGHT - 60);

        AssetLoader.font.getData().setScale(0.4f, 0.4f);
        AssetLoader.font.draw(batch, "Tap to start!", 22, V_HEIGHT - 61);
    } else {
        if (gameWorld.isGameOver()) {
            AssetLoader.shadow.getData().setScale(0.4f, 0.4f);
            AssetLoader.shadow.draw(batch, "Game Over", 40, V_HEIGHT - 60);

            AssetLoader.font.getData().setScale(0.4f, 0.4f);
            AssetLoader.font.draw(batch, "Game Over", 39, V_HEIGHT - 61);

            AssetLoader.shadow.draw(batch, "High Score: " + highScore, 15, V_HEIGHT - 90);
            AssetLoader.font.draw(batch, "High Score: " + highScore, 14, V_HEIGHT - 91);
        } else {
            AssetLoader.shadow.getData().setScale(0.55f, 0.55f);
            AssetLoader.shadow.draw(batch, score, (V_WIDTH / 2) - (8 * score.length()), V_HEIGHT / 1.25f);

            AssetLoader.font.getData().setScale(0.55f, 0.55f);
            AssetLoader.font.draw(batch, score, (V_WIDTH / 2) - (8 * score.length()) - 1, V_HEIGHT / 1.25f - 1);
        }
    }

    batch.setProjectionMatrix(camera.combined);

    if (jumpRope.getJumpRopeBehindFrog()) {
        jumpRope.draw(batch);
    }

    if (gameWorld.getFrog().getY() > -60 / PPM) {
        frog.draw(batch);
    }

    if (!jumpRope.getJumpRopeBehindFrog()) {
        jumpRope.draw(batch);
    }

    batch.end();

    // I put this in front of everything so that I could see what it's doing
    backStage.act();
    backStage.draw();
    //

    debugRenderer.render(gameWorld.getWorld(), camera.combined);

    gameWorld.getWorld().step(1/60f, 6, 2);
}

public void resize (int width, int height) {
    backViewport.update(width, height);
    viewport.update(width, height);
}

Solution

  • Quote from Libgdx wiki:

    Multiple viewports

    When using multiple viewports that have different screen sizes (or you use other code that sets glViewport), you will need to apply the viewport before drawing so the glViewport is set for that viewport.

    viewport1.apply();
    // draw
    viewport2.apply();
    // draw
    

    When using multiple Stages:

    stage1.getViewport().apply();
    stage1.draw();
    stage2.getViewport().apply();
    stage2.draw();
    

    Upd. Couple of notes, unrelated to your question: