如何处理 libGDX 中的不同纵横比?

Posted

技术标签:

【中文标题】如何处理 libGDX 中的不同纵横比?【英文标题】:How to deal with different aspect ratios in libGDX? 【发布时间】:2012-03-01 05:16:01 【问题描述】:

我已经使用 libGDX 实现了一些屏幕,这些屏幕显然会使用 libGDX 框架提供的Screen 类。但是,这些屏幕的实现仅适用于预定义的屏幕尺寸。例如,如果 sprite 用于 640 x 480 尺寸的屏幕(4:3 宽高比),它不会在其他屏幕尺寸上按预期工作,因为 sprite 符合屏幕边界并且未缩放到屏幕尺寸一点也不。此外,如果 libGDX 提供了简单的缩放,那么我面临的问题仍然存在,因为这会导致游戏屏幕的纵横比发生变化。

在互联网上进行研究后,我遇到了一个讨论相同问题的blog/forum。我已经实现了它,到目前为止它运行良好。但我想确认这是否是实现这一目标的最佳选择,或者是否有更好的选择。下面是显示我如何处理这个合法问题的代码。

论坛链接: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() 方法时,我发现由于resize 的缩放,我实现touchDown() 的坐标会发生很大变化。在使用一些代码根据屏幕调整大小转换坐标之后,我在很大程度上减少了这个数量,但我没有成功地保持它们的精确度。例如,如果我在纹理上实现了touchDown(),调整屏幕大小会使纹理区域上的 touchListener 向右或向左移动一些像素,具体取决于调整大小,这显然是不希望的。

后来我才知道舞台类有自己的原生功能来保持纵横比(boolean stretch = false)。现在我已经使用舞台类实现了我的屏幕,它很好地保持了纵横比。但是在调整大小或不同的屏幕尺寸时,生成的黑色区域总是出现在屏幕的右侧;那就是屏幕没有居中,如果黑色区域很大,这会很丑。

任何社区成员都可以帮我解决这个问题吗?

【问题讨论】:

你能链接到有同样问题的博客或论坛吗? @SteveBlackwell 这里是链接:java-gaming.org/index.php?topic=25685.new @SteveBlackwell 请查看更新后的问题,看看您是否可以提供帮助。 我对Stage不太了解,但是看here,好像应该已经修复了。 docs 对居中没有太大帮助。也许您可以将相机移动一点或调整视口。 其实我的问题已经解决了。我将其发布为答案。 【参考方案1】:

左侧/右侧或顶部/底部的黑条看起来比仅仅扭曲整个场景以适应屏幕更好。如果您的目标纵横比在可能范围的中间(4:3 可能是低端,16:9 可能是高端),那么对于大多数设备来说,条应该保持较小。即使在更大的屏幕上,这也可以让您使用大部分屏幕,并且您已经拥有该人的代码,因此非常容易。如果这只是 libgdx 中内置的一个选项,那就更好了。

但是,我认为最好的方法是使用整个屏幕。我还没有这样做,但这将是我下一个项目所采用的方法。这个想法是,如果有人有更宽的屏幕,那么他们应该在侧面看到更多。 Chris Pruett 在他的一次演讲中谈到了如何做到这一点(link to spot in talk--实际上,整个事情实际上非常好)。这个想法是缩放高度,然后将视口设置为足够宽以适合屏幕。 OpenGL 应该负责显示其余部分。他这样做的方式是here。

对于 libgdx,也许有一种简单的方法可以通过将相机移动到舞台上来使用 scene2d,但我从未真正使用过 scene2d。无论如何,将应用程序作为原生可调整大小的窗口运行比创建一堆 AVD 更容易测试多种屏幕尺寸。

【讨论】:

“无论如何,将应用程序作为原生可调整大小的窗口运行比创建一堆 AVD 更容易测试多种屏幕尺寸。”说得好。实际上,我正在尝试解决这种现象,以确保最大的兼容性,而不是针对具有不同尺寸纹理的不同屏幕尺寸。【参考方案2】:

ResolutionFileResolver 类允许您将文件名解析为最佳分辨率。我相信这也适用于不同的纵横比,前提是您已经为这些纵横比创建了精灵。 AssetManagerTest中有一个使用示例。

【讨论】:

【参考方案3】:

编辑: libGDX 已经进化。最佳答案现在是用户 noone 的 this one。请务必查看Viewport documentation. 的链接。

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

在互联网上到处搜索我遇到的问题后,我找不到任何解决方案,因此决定直接联系报告错误的人。之后,他在 libgdx 论坛上回复了我,我很感谢他帮助我。 Here is the link

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

在 resize() 方法中:

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

其中 640 X 480 是 TextureRegion 的分辨率,它描述了预期的纵横比。如果您的 TextureRegion 大小为 320 X 240,则应将两个参数都更改为新的分辨率以达到目的。

Profile link of the original person who actually resolved my issue

【讨论】:

在 libGDX 0.9.6 中, stage.setViewport ( 640, 480, false ) 就足够了,因为它已经包含了对 stage.getCamera ().position.set ( 640 / 2, 480 / 2 的调用, 0 ); setViewport 有 3 个参数现在不可用! 我已将接受的答案更改为用户 noone 发布的答案,正如 Steve Blackwell 指出的那样,因为它似乎是最新且正确的答案。【参考方案4】:

查看官方文档中 Viewport 部分的最后一部分: https://code.google.com/p/libgdx/wiki/scene2d#Viewport

【讨论】:

【参考方案5】:

我正在使用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 这是我一年前的做法,很好,我发现了 unproject 的事情。当你的相机总是在同一个位置时很好。。【参考方案6】:

现在怎么做:

由于这是 libgdx 上最著名的问题之一,我将对其进行一点更新

LibGDX v1.0 引入了Viewport 来处理这个问题。它更易于使用,并且扩展策略是可插拔的,这意味着单行可以改变行为,您可以使用它来查看哪一个最适合您的游戏。

你需要知道的一切都可以在here找到。

【讨论】:

我已经尝试过了,但仍然是我的TextureRegion Strechs。如果将reszie(int width, int height) 函数留空。 TextureRegion 不会拉伸。 但即使在使用视口时,我们也必须为不同的纵横比使用单独的纹理,不是吗?是否有简化该方法的逻辑?还是应该通过保持独立于分辨率来完成图形设计? 使用视口的好教程:gamefromscratch.com/post/2014/12/09/… 添加到这个答案并回答 cmets 中的问题,您可以绘制 16:9 的世界,并为您的纹理提供更多高度,以便能够以 4:3 加载。然后,在实例化您的视口时,您可以检查屏幕的实际纵横比,同时保持您的宽度标准,您可以为您的视口提供一个适合您正在运行的纵横比的高度。一个缺点是在 4:3 分辨率下您将有很多“空白”空间,但如果您的纹理可以处理,所有内容都将显示为绘制而不是拉伸【参考方案7】:

此代码适用于我的最新更新: * OrthographicCamera 是凸轮,这不会进行裁剪,只是更改视口,因此宽度仍然比实际窗口/设备大“那么多”倍

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

【讨论】:

以上是关于如何处理 libGDX 中的不同纵横比?的主要内容,如果未能解决你的问题,请参考以下文章

处理不同尺寸的延迟加载图片的纵横比,避免内容跳转

如何在平方 SurfaceView(如 Instagram)中将相机预览大小设置为平方纵横比

如何在不改变原始纵横比的情况下组合两个不同大小的 UIImage?

iMessage App如何调整图像大小并保持纵横比

如何让 Flexslider 在 iOS 上显示不同纵横比的图片?

如何处理批次内不同实例中的不确定句子数量?