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);
}
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:
Using static references on assets (like AssetLoader.background
in your code) is usually considered as a bad practice. For example, read these: https://stackoverflow.com/a/45901466/6836125, https://stackoverflow.com/a/44357810/6836125
SpriteBatches are heavy objects, so it's better to have only one SpriteBatch
instance and to share it:
backStage = new Stage(backViewport, batch);