🖐1. 概述
手机屏幕多种多样,有着分辨率不同,屏幕宽高比也可能不同。同一张图片在不同手机中显示的位置和大小视觉上的感觉可能都不相同,为此我们需要对不同的手机屏幕进行适配,使相同的程序逻辑在不同的屏幕上显示的视觉效果一致。LibGDX 提供了一个方便的方法处理不同屏幕之间的适配问题,即使用视口(Viewport)。

视口(Viewport)负责管理游戏相机并处理世界(代码/逻辑中所认为的内容显示边界)坐标与实际屏幕坐标之间的映射。视口字面上意思为眼睛视觉上能够看到的口(一般来说就是指手机屏幕的显示),在程序代码中指的是显示内容的逻辑世界边界(宽高),是给不同尺寸手机屏幕统一定义的一个固定的虚拟屏幕尺寸。视口通常结合舞台一起使用,例如给舞台设置一个宽高为 480800 的伸展视口,则在代码/逻辑中舞台/屏幕宽高就可以当做是固定不变的 480800,即舞台/屏幕左下角坐标为 (0, 0),右上角坐标为 (480, 800),最终绘制到不同尺寸的真正手机屏幕上时对 480*800 的绘制结果通过竖直和水平方向的适当缩放使之刚好显示到屏幕上,这样就达到了同样的一套程序逻辑在不同尺寸的屏幕上尽可能地保证了一致的显示效果。

  1. 视口的类型
    视口类的继承关系如下图所示:

LibGDX 中主要以下有 3 种类型视口,它们统一继承自抽象视口类 Viewport:

(1)ExtendViewport(延伸视口): 视口保持和原屏幕相同的宽高比先适配其中一个方向(宽或高),然后另一个方向进行延伸。视口有可能延伸到屏幕外面导致部分内容不能显示。

(2)ScreenViewport(屏幕视口): 屏幕视口的世界尺寸基于原屏幕尺寸(宽高比相同)。默认 1 个世界单位 == 1 个屏幕像素,但这个比例关系可以通过 ScreenViewport 类中的方法(setUnitsPerPixel)进行修改。

(3)ScalingViewport(缩放视口): 缩放视口顾名思义就是将世界尺寸的水平或竖直方向进行相应的缩放(缩放比可能不同)以适配屏幕的宽高。创建缩放视口实例时需要指定缩放方式(枚举类型 Scaling)。ScalingViewport 主要有以下几种缩放方式:

Scaling.fit: 保持宽高比例不变将,世界整个缩放到实际屏幕中(相当于最大限度使之内嵌在屏幕中),如果有剩余没有填满屏幕的空间将出现黑边(世界宽高比和实际屏幕宽高比不一致时出现)。
Scaling.fill: 保持宽高比例不变,将世界进行延伸使之能够填充满整个屏幕,有可能世界的其中一部分在屏幕外面(世界宽高比和实际屏幕宽高比不一致时出现)。
Scaling.stretch: 不保持宽高比例(水平和竖直方向的缩放比例可以不同),将整个世界恰好缩放到屏幕中。
Scaling.none: 保持宽高比例不变,使用固定的世界尺寸,并且不进行任何缩放(世界可能没有填充满屏幕,也可能有一部分在屏幕外面)。
对于 Scaling.fit,Scaling.fill,Scaling.stretch 的 ScalingViewport 缩放类型,LibGDX 中给出了便捷的 ScalingViewport 的子类进行实现,分别对应 FitViewport,FillViewport,StretchViewport。它们三者之间的关系比较如下图所示:

  1. 代码示例
    这里只演示缩放视口(ScalingViewport)中的伸展视口(Scaling.stretch / StretchViewport),其他类型视口如有兴趣可查看 API 文档和源码自行做实验观察效果总结规律。

引用前面章节自定义的演员:

package com.libgdx.test;

import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;

/**
 * 自定义演员
 */
public class MyActor extends Actor {

    private TextureRegion region;

    public MyActor(TextureRegion region) {
        super();
        this.region = region;
        setSize(this.region.getRegionWidth(), this.region.getRegionHeight());
    }

    public TextureRegion getRegion() {
        return region;
    }

    public void setRegion(TextureRegion region) {
        this.region = region;
        setSize(this.region.getRegionWidth(), this.region.getRegionHeight());
    }

    @Override
    public void act(float delta) {
        super.act(delta);
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        super.draw(batch, parentAlpha);
        if (region == null || !isVisible()) {
            return;
        }
        batch.draw(
                region, 
                getX(), getY(), 
                getOriginX(), getOriginY(), 
                getWidth(), getHeight(), 
                getScaleX(), getScaleY(), 
                getRotation()
        );
    }
}

游戏主程序的启动入口类:

package com.libgdx.test;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.StretchViewport;
import com.badlogic.gdx.utils.viewport.Viewport;

/**
 * 游戏主程序的启动入口类
 */
public class MainGame extends ApplicationAdapter {

    private Texture texture;

    private Stage stage;

    private MyActor actor;

    @Override
    public void create() {
        // 创建纹理, badlogic.jpg 图片的宽高为 256 * 256
        texture = new Texture(Gdx.files.internal("badlogic.jpg"));

        // 创建一个伸展视口, 为了方便查看效果, 世界的宽高设置为 256 * 512 (图片刚好在视口下半部分)
        Viewport stretchViewport = new StretchViewport(256, 512);

        // 也可以使用下面方法创建伸展视口
        // Viewport stretchViewport = new ScalingViewport(Scaling.stretch, 256, 512);

        // 使用指定的视口创建舞台, 舞台的宽高为视口世界的宽高
        stage = new Stage(stretchViewport);

        // 创建演员
        actor = new MyActor(new TextureRegion(texture));

        // 添加演员到舞台
        stage.addActor(actor);
    }

    @Override
    public void render() {
        // 红色清屏
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        // 更新舞台逻辑
        stage.act();
        // 绘制舞台
        stage.draw();
    }

    @Override
    public void dispose() {
        // 释放资源
        if (texture != null) {
            texture.dispose();
        }
        if (stage != null) {
            stage.dispose();
        }
    }

}

在不同屏幕中的运行结果如下图所示。视口使用的是伸展视口(StretchViewport),演员默认位置坐标为舞台左下角,视口世界的宽高为 256 512,而演员/图片的宽高为 256 256,所以最终经过伸展缩放适配到实际屏幕上后,演员的水平方向刚好占满视口世界(屏幕),竖直方向占视口世界(屏幕)的一半,在不同大小不同宽高比的屏幕中显示效果均一致。


作者:xietansheng
来源:CSDN
原文:https://blog.csdn.net/xietansheng/article/details/50187331
版权声明:本文为博主原创文章,转载请附上博文链接!

发表评论

邮箱地址不会被公开。 必填项已用*标注