如何在libGDX中处理不同的屏幕比例?

81
我使用libGDX实现了一些屏幕,这些屏幕显然会使用libGDX框架提供的Screen类。然而,这些屏幕的实现仅适用于预定义的屏幕尺寸。例如,如果精灵是针对640 x 480大小的屏幕(4:3宽高比)设计的,则在其他屏幕尺寸上,精灵将超出屏幕边界,并且根本不会按比例缩放到屏幕尺寸。此外,即使libGDX提供了简单的缩放,我面临的问题仍然存在,因为那样会导致游戏屏幕的宽高比发生变化。
在互联网上进行研究后,我找到了一个博客/论坛,讨论了同样的问题。我已经实施了它,到目前为止它运行良好。但我想确认这是否是实现此目标的最佳选项,或者是否有更好的替代方案。下面是代码,展示了我如何处理这个合法的问题。
论坛链接: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);
        }
    }
}
更新: 我最近了解到libGDX有一些自己的功能来维护宽高比,我想在这里讨论一下。在搜索跨越互联网的宽高比问题时,我遇到了几个论坛/开发人员,他们遇到了“如何在不同屏幕尺寸上保持宽高比”的问题?其中一个真正对我有效的解决方案已经发布在上面。

后来,当我继续实现屏幕的touchDown()方法时,我发现由于缩放调整大小,我实现touchDown()的坐标会发生很大变化。通过一些代码来根据屏幕调整坐标,我大大减少了这个量,但我没有成功地以针对精确度来保持它们。例如,如果我在纹理上实现了touchDown(),调整屏幕大小将使触摸监听器在纹理区域上向右或向左移动一些像素,具体取决于调整大小,而这显然是不希望看到的。

后来我知道了舞台类有自己的本地功能来维护纵横比(boolean stretch = false)。现在我已经使用舞台类实现了我的屏幕,它能够很好地维护纵横比。但是,在调整大小或不同的屏幕尺寸时,生成的黑色区域总是出现在屏幕的右侧;也就是说,屏幕没有居中,如果黑色区域相当大,这会使它看起来相当丑陋。
有没有社区成员能帮助我解决这个问题?

你能提供一下出现相同问题的博客或论坛链接吗? - Steve Blackwell
@SteveBlackwell 请查看更新的问题并看看您能否在此方面提供帮助。 - Rafay
1
我对舞台不是很了解,但在这里看起来(http://code.google.com/p/libgdx/issues/detail?id=456),似乎应该已经解决了。[文档](http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/scenes/scene2d/Stage.html)对于居中并没有太多帮助。也许您可以将相机稍微移动一下或调整视口。 - Steve Blackwell
我正在查看setViewport()的源代码,看起来应该没问题。当你运行它时,如果显示居中不准确,你的屏幕尺寸、舞台尺寸和居中点是什么? - Steve Blackwell
1
我的问题已经解决了。我将其发布为答案。 - Rafay
显示剩余3条评论
7个回答

53

现如今如何实施:

由于这是libgdx中最著名的问题之一,我会给它一个小的更新

LibGDX v1.0引入了Viewport来解决此问题。它更易于使用,而且缩放策略是可插拔的,这意味着一行代码可以改变其行为,您可以尝试不同的缩放策略,看哪一个最适合您的游戏。

关于它的所有信息都可以在这里找到。


我已经尝试过了,但我的TextureRegion仍然被拉伸了。如果将resize(int width, int height)函数留空,则TextureRegion不会被拉伸。 - Muhammad Babar
即使使用视口,我们仍然需要为不同的纵横比使用单独的纹理,对吧?是否有简化该方法的逻辑?或者应该通过保持与分辨率无关来进行图形设计? - Neerkoli
使用视口的好教程:http://www.gamefromscratch.com/post/2014/12/09/LibGDX-Tutorial-Part-17-Viewports.aspx - ubzack
补充此回答并回答评论中的问题,您可以为16:9绘制世界,并增加纹理的高度以便能够在4:3中加载。然后,在实例化视口时,您可以检查屏幕的实际宽高比,并在保持宽度标准的同时,给视口一个适合您正在运行的宽高比的高度。缺点是,在4:3分辨率上会有很多“空白”空间,但如果您的纹理可以处理,那么所有内容都将显示为绘制而不是拉伸。 - Klitos G.

25
编辑:libGDX已经发展壮大。最好的答案现在是用户noone的这个。一定要查看Viewport文档链接。

当SteveBlack发布了一个在舞台类中报告的问题链接时,我去那里发现这个问题(实际上不是我的问题)已经在最新的夜间版本中得到解决。

在互联网上搜索我遇到的问题,我找不到任何解决方案,所以决定直接联系报告错误的人。之后,他在libgdx论坛上回复了我,我感激他帮助我。 这是链接

只需要一行代码,你需要做的就是:

在resize()方法中:

stage.setViewport(640, 480, false);
stage.getCamera().position.set(640/2, 480/2, 0);

在这里,640 X 480是TextureRegion的分辨率,用于描述预期的宽高比。如果您的TextureRegion大小为320 X 240,则应将两个参数更改为新的分辨率以达到效果。

原始解决我的问题的人的个人资料链接


6
在libGDX 0.9.6中,stage.setViewport(640, 480, false)已经足够了,因为它已经包含了对stage.getCamera().position.set(640/2, 480/2, 0)的调用。 - Alexis Pautrot
1
现在不支持使用三个参数的setViewport函数! - Muhammad Babar
我已将接受的答案更改为由用户noone发布的答案,因为Steve Blackwell指出这似乎是最新和正确的答案。 - Rafay

7
左/右或上/下的黑色条纹比扭曲整个场景来适应屏幕看起来更好。如果你选择一个介于可能范围之间的宽高比(4:3可能是低端,16:9可能是高端),那么对于大多数设备而言,黑色条纹应该会很小。这也可以让你在更大的屏幕上使用大部分的屏幕空间,而且你已经有了那个人的代码,所以非常容易实现。如果这只是 libgdx 中内置的一个选项,那就更好了。
但是,我认为最好的方法是使用整个屏幕。我还没有这样做过,但这将是我下一个项目采取的方法。这个想法是,如果有人拥有更宽的屏幕,那么他们应该在两侧看到更多内容。克里斯·普鲁特在他的其中一次演讲中谈到了如何做到这一点(链接到演讲中的位置 - 实际上,整个演讲都非常好)。这个想法是按高度缩放,然后将您的视口设置得足够宽以适应屏幕。OpenGL 应该负责显示其余部分。他的实现方式在 这里。对于 libgdx,也许可以通过移动相机来轻松地使用 scene2d 来实现这一点,但我从未真正使用过 scene2d。无论如何,将应用程序作为本地可调整大小的窗口运行比创建一堆 AVD 来测试多个屏幕尺寸要容易得多。

无论如何,将应用程序作为本地可调整大小的窗口运行是测试多个屏幕尺寸比创建一堆AVD更容易的方法。说得好。实际上,我正在尝试使用这种现象来确保最大的兼容性,而不是针对不同的屏幕尺寸使用不同大小的纹理。 - Rafay

5

3

ResolutionFileResolver 类可帮助您将文件名解析为最佳分辨率。只要您已针对这些屏幕比例创建了精灵,我相信它也可以与不同的屏幕比例一起使用。在 AssetManagerTest 中有一个示例用法。


0
这段代码在最新的更新中对我有效: * OrthographicCamera 是 cam,这不会裁剪任何内容,只是改变了视口,使得宽度仍然比实际窗口/设备大“那么多”倍。
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);
}

0

我正在使用http://blog.acamara.es/2012/02/05/keep-screen-aspect-ratio-with-different-resolutions-using-libgdx/中的方法。

我编写了这个函数,它返回屏幕上被触摸的点,缩放到你想要的游戏位置。

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);
}

在触摸/抬起/拖动中使用此功能,您可以检查游戏中的触摸。


这似乎与相机的unproject方法相似。它做的事情是一样的吗? - milosmns
@milosmns 这是我一年前的做法,好在我发现了unproject的事情..当你的相机总是处于相同的位置时,这很有用。 - Boldijar Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接