How to deal with different aspect ratios in libGDX?
I have implemented some screens using libGDX that would obviously use the Screen
class provided by the libGDX framework. However, the implementation for these screens works only with pre-defined screen sizes. For example, if the sprite was meant for a 640 x 480 size screen (4:3 Aspect ratio), it won't work as intended on other screen sizes because the sprites go par the screen boundaries and are not scaled to the screen size at all. Moreover, if simple scaling would have been provided by the libGDX, the issue I am facing would have still been there because that would cause the aspect ratio of the game screen to change.
After researching on internet, I came across a blog/forum that had discussed the same issue. I have implemented it and so far it is working fine. But I want to confirm whether this is the best option to achieve this or whether there are better alternatives. Below is the code to show how I am dealing with this legitimate problem.
FORUM LINK: http://www.java-gaming.org/index.php?topic=25685.new
public class SplashScreen implements Screen {
// Aspect Ratio maintenance
private static final int VIRTUAL_WIDTH = 640;
private static final int VIRTUAL_HEIGHT = 480;
private static final float ASPECT_RATIO = (float) VIRTUAL_WIDTH / (float) VIRTUAL_HEIGHT;
private Camera camera;
private Rectangle viewport;
// ------end------
MainGame TempMainGame;
public Texture splashScreen;
public TextureRegion splashScreenRegion;
public SpriteBatch splashScreenSprite;
public SplashScreen(MainGame maingame) {
TempMainGame = maingame;
}
@Override
public void dispose() {
splashScreenSprite.dispose();
splashScreen.dispose();
}
@Override
public void render(float arg0) {
//----Aspect Ratio maintenance
// update camera
camera.update();
camera.apply(Gdx.gl10);
// set viewport
Gdx.gl.glViewport((int) viewport.x, (int) viewport.y,
(int) viewport.width, (int) viewport.height);
// clear previous frame
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// DRAW EVERYTHING
//--maintenance end--
splashScreenSprite.begin();
splashScreenSprite.disableBlending();
splashScreenSprite.draw(splashScreenRegion, 0, 0);
splashScreenSprite.end();
}
@Override
public void resize(int width, int height) {
//--Aspect Ratio Maintenance--
// calculate new viewport
float aspectRatio = (float)width/(float)height;
float scale = 1f;
Vector2 crop = new Vector2(0f, 0f);
if(aspectRatio > ASPECT_RATIO) {
scale = (float) height / (float) VIRTUAL_HEIGHT;
crop.x = (width - VIRTUAL_WIDTH * scale) / 2f;
} else if(aspectRatio < ASPECT_RATIO) {
scale = (float) width / (float) VIRTUAL_WIDTH;
crop.y = (height - VIRTUAL_HEIGHT * scale) / 2f;
} else {
scale = (float) width / (float) VIRTUAL_WIDTH;
}
float w = (float) VIRTUAL_WIDTH * scale;
float h = (float) VIRTUAL_HEIGHT * scale;
viewport = new Rectangle(crop.x, crop.y, w, h);
//Maintenance ends here--
}
@Override
public void show() {
camera = new OrthographicCamera(VIRTUAL_WIDTH, VIRTUAL_HEIGHT); //Aspect Ratio Maintenance
splashScreen = new Texture(Gdx.files.internal("images/splashScreen.png"));
splashScreenRegion = new TextureRegion(splashScreen, 0, 0, 640, 480);
splashScreenSprite = new SpriteBatch();
if(Assets.load()) {
this.dispose();
TempMainGame.setScreen(TempMainGame.mainmenu);
}
}
}
UPDATE: I recently came to know that libGDX has some of its own functionality to maintain aspect ratios which I would like to discuss here. While searching the aspect ratio issue across the internet, I came across several forums/developers who had this problem of "How to maintain the aspect ratio on different screen sizes?" One of the solutions that really worked for me was posted above.
Later on when I proceeded with implementing the touchDown()
methods for the screen, I found that due to scaling on resize, the co-ordinates on which I had implemented touchDown()
would change by a great amount. After working with some code to translate the co-ordinates in accordance with the screen resize, I reduced this amount to a great extent but I wasn't successful to maintain them with pin point accuracy. For example, if I had implemented touchDown()
on a texture, resizing the screen would shift the touchListener on the texture region some pixels to the right or left, depending on the resize and this was obviously undesired.
Later on I came to know that the stage class has its own native functionality to maintain the aspect ratio (boolean stretch = false
). Now that I have implemented my screen by using the stage class, the aspect ratio is maintained well by it. However on resize or different screen sizes, the black area that is generated always appears on the right side of the screen; that is the screen is not centered which makes it quite ugly if the black area is substantially large.
Can any community member help me out to resolve this problem?
How to do it nowadays:
Since this is one of the most famous questions on libgdx here, I'll give it a little update:
LibGDX v1.0 introduced Viewport
to handle this problem. It is a lot easier to use and the scaling strategy is pluggable, which means a single line can change the behaviour and you can play with it and see which one fits your game the best.
Everything you need to know about it can be found here.
EDIT: libGDX has evolved. Best answer is now this one by user noone. Be sure to check the link to the Viewport
documentation..
As SteveBlack posted the link of the issue that was reported in the stage class, I went there just to discover that the issue (that was not actually my issue) has been resolved in the latest nightlies.
After researching here and there on the internet for the issue I was having, I couldn't find any solutions so decided to contact the person who reported the bug directly. After that, he replied me on libgdx forums and I am indebted to him for his helping me out. Here is the link
It was a single line of code and all you have to do is:
In the resize() methond:
stage.setViewport(640, 480, false);
stage.getCamera().position.set(640/2, 480/2, 0);
Where 640 X 480 is the resolution of your TextureRegion that describes the intended aspect ratio. If your TextureRegion size was 320 X 240, then both the arguments should be changed to the new resolution to do the trick.
Profile link of the original person who actually resolved my issue
Black bars on the left/right or top/bottom look better than just distorting your whole scene to fit the screen. If you target an aspect ratio that's in the middle of the possible ranges (4:3 is probably low end, 16:9 is probably high end), then the bars should stay small for most devices. This also let's you use most of the screen even on bigger screens, and you already have that guy's code so it's pretty easy. It would be even nicer if this was just an option built into libgdx.
But, I think the best approach is to use the whole screen. I haven't done this yet, but it will be the approach I take for my next project. The idea is that if someone has a wider screen, then they should see more on the sides. Chris Pruett talks about how to do this in one of his talks (link to spot in talk--actually, the whole thing is actually pretty good). The idea is to scale for height, and then set your viewport wide enough to fit the screen. OpenGL should take care of displaying the rest. The way he does it is here.
For libgdx, maybe there's an easy way to do this with scene2d by moving the camera over the stage, but I've never actually worked with scene2d. In any case, running the app as a native resizable window is a much easier way to test multiple screen sizes than creating a bunch of AVDs.