How to deal with different aspect ratios in libGDX?
Asked Answered
G

7

81

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?

Gluconeogenesis answered 8/2, 2012 at 18:4 Comment(8)
Could you link to the blog or forum that had the same issue?Appendant
@SteveBlackwell here is the link: java-gaming.org/index.php?topic=25685.newGluconeogenesis
@SteveBlackwell Please see the updated question and see if you can help on this.Gluconeogenesis
I don't know very much about the Stage, but looking here, it seems like this should have been fixed. The docs aren't too much help for centering. Maybe you could move the camera over a little bit or adjust the viewport.Appendant
Also, the current question is the same as https://mcmap.net/q/260600/-how-to-get-stage-in-the-center-in-libgdx/324625, which was very recent, but unanswered. :(Appendant
@SteveBlackwell thanks for the link. But I think the bug is not related in my case. I have got the latest nightlies, yet the issue is still there.Gluconeogenesis
I'm looking at the source of setViewport(), and it looks like it should be ok. When you run it and it's off center, what's your screen size, stage size, and what center point do you get for that?Appendant
Well my issue actually got resolved. I am posting it as the answer.Gluconeogenesis
T
53

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.

Trager answered 18/4, 2014 at 6:31 Comment(4)
I have tried it but still my TextureRegion Streches. If leave the reszie(int width, int height) function empty. THe TextureRegion doesn't strectch.Forestall
But even when using Viewports we have to use separate textures for different aspect ratios, don't we? Is there a logic to simplify that method? Or should the graphic designing be done by keeping it independent of resolutions?Safar
Good tutorial on using Viewports: gamefromscratch.com/post/2014/12/09/…Cither
Adding to this answer and answering to the questions in comments, you could draw your world for 16:9 and give more height to your texture in order to be able to load in 4:3. Then, when instantiating your viewport you can check the actual aspect ratio of the screen and while keeping your width standard you can give your viewport a height that will fit the aspect ratio you are running. A drawback is that you will have much "empty" space on a 4:3 resolution, but if your texture can handle that, everything will show as drawn and not stretchedUnstudied
G
25

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

Gluconeogenesis answered 24/2, 2012 at 10:49 Comment(3)
In libGDX 0.9.6, stage.setViewport ( 640, 480, false ) is enough as it already includes a call to stage.getCamera ().position.set ( 640 / 2, 480 / 2, 0 );Syneresis
setViewport with 3 parameters in not available now!Forestall
I have changed the accepted answer to the one posted by user noone as Steve Blackwell pointed out because it seems to be the latest and correct one.Gluconeogenesis
A
7

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.

Appendant answered 9/2, 2012 at 18:1 Comment(1)
"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." Well said. actually I am trying work on this phenomenon to ensure maximum compatibility instead of targeting different screen sizes with different sizes of textures.Gluconeogenesis
F
5

See the last part of the Viewport section in the official docs: https://code.google.com/p/libgdx/wiki/scene2d#Viewport

Ftc answered 18/5, 2013 at 3:15 Comment(0)
L
3

The ResolutionFileResolver class allows you to resolve file names to the best resolution. I believe that will work with different aspect ratios as well, provided you have created sprites for those aspect ratios. There is an example use in the AssetManagerTest.

Luminary answered 15/2, 2012 at 18:54 Comment(0)
P
0

I'm using that method from http://blog.acamara.es/2012/02/05/keep-screen-aspect-ratio-with-different-resolutions-using-libgdx/

I made this function that returns the point on the screen touched, scaled down to the game position you want.

public Vector2 getScaledPos(float x, float y) {
    float yR = viewport.height / (y - viewport.y);
    y = CAMERA_HEIGHT / yR;

    float xR = viewport.width / (x - viewport.x);
    x = CAMERA_WIDTH / xR;

    return new Vector2(x, CAMERA_HEIGHT - y);
}

Use this in the touchdown / up/ drag and you can check for touch in your game.

Pesce answered 16/3, 2014 at 19:15 Comment(2)
This seems similar to the unproject method of the camera. Does it do the same thing?Flanders
@Flanders this is the way i was doing like 1 year ago, good i found out the unproject thing.. is good when your camera is always in the same position..Pesce
F
0

This code works for me with the latest update: * OrthographicCamera is cam, this makes no cropping, just changes the viewport so the width is still "that many" times larger than the actual window/device

public void resize(int width, int height) {
    int newW = width, newH = height;
    if (cam.viewportWidth > width) {
        float scale = (float) cam.viewportWidth / (float) width;
        newW *= scale;
        newH *= scale;
    }

    // true here to flip the Y-axis
    cam.setToOrtho(true, newW, newH);
}
Flanders answered 11/8, 2014 at 0:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.