遊戲開發中,邏輯世界坐標系統(固定寬高)與物理設備屏幕(千差萬別的分辨率、長寬比)之間存在天然矛盾。LibGDX 的 Viewport 抽象層統一了解決方案:定義虛擬世界尺寸,自動計算投影矩陣與視口矩形,實現跨設備一致的渲染表現。
所有 Viewport 繼承自 Viewport 接口,核心方法 update(screenWidth, screenHeight, centerCamera) 根據屏幕尺寸重新計算世界投影。
| Viewport | 縮放策略 | 黑邊 | 世界尺寸變化 | 典型適用場景 |
|---|---|---|---|---|
| StretchViewport | 非等比拉伸填滿屏幕 | 無 | 固定 | UI 佈局、簡單 2D、原型驗證 |
| FitViewport | 等比縮放,保持長寬比,內容完整顯示 | 有(單側或雙側) | 固定 | 像素風遊戲、嚴格長寬比要求 |
| FillViewport | 等比縮放,填滿屏幕,內容可能被裁剪 | 無 | 固定 | 背景圖鋪滿、允許邊緣裁剪 |
| ExtendViewport | 先 Fit,再向長邊延伸世界尺寸至屏幕邊緣 | 可能(若設 maxWorldSize) | 動態擴展(min~max) | 策略遊戲、大地圖、希望大屏見更多 |
| ScreenViewport | 世界尺寸=屏幕像素尺寸(1:1),無縮放 | 無 | 隨屏幕變化 | 編輯器、工具類、桌面端像素精確操作 |
| ScalingViewport | 通用封裝,接受任意 Scaling 枚舉 | 視 Scaling 而定 | 視 Scaling 而定 | 自定義縮放策略組合 |
public void update(int screenWidth, int screenHeight, boolean centerCamera) {
setScreenBounds(0, 0, screenWidth, screenHeight);
setWorldSize(screenWidth * unitsPerPixel, screenHeight * unitsPerPixel);
apply(centerCamera);
}
unitsPerPixel = 1,即 1 像素 = 1 世界單位。minWorldWidth, minWorldHeight, [maxWorldWidth, maxWorldHeight], camera。Scaling.fit 縮放至最小世界完整顯示,若屏幕更寬/高,則向右/上擴展世界尺寸,直至達到 maxWorldSize 或填滿屏幕。maxWorldSize 過小可能導致黑邊。public class ViewportTest extends ApplicationAdapter {
private static final float MIN_W = 800, MIN_H = 600;
private static final float MAX_W = 1280, MAX_H = 800;
private OrthographicCamera camera;
private SpriteBatch batch;
private Texture bg, sprite;
private BitmapFont font;
private ObjectMap viewports;
private int current = -1;
@Override
public void create() {
camera = new OrthographicCamera();
batch = new SpriteBatch();
bg = new Texture("background.jpg");
sprite = new Texture("badlogic.jpg");
font = new BitmapFont();
font.getData().setScale(2f);
viewports = new ObjectMap<>();
viewports.put("Stretch", new StretchViewport(MIN_W, MIN_H, camera));
viewports.put("Fit", new FitViewport(MIN_W, MIN_H, camera));
viewports.put("Fill", new FillViewport(MIN_W, MIN_H, camera));
viewports.put("Screen", new ScreenViewport(camera));
viewports.put("Extend (no max)", new ExtendViewport(MIN_W, MIN_H, camera));
viewports.put("Extend (max)", new ExtendViewport(MIN_W, MIN_H, MAX_W, MAX_H, camera));
selectNext();
}
private void selectNext() {
current = (current + 1) % viewports.size;
Viewport vp = viewports.values().get(current);
vp.apply(true);
vp.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
log(vp);
}
@Override
public void resize(int w, int h) {
viewports.values().get(current).update(w, h, true);
}
@Override
public void render() {
Gdx.gl.glClearColor(0.39f, 0.58f, 0.92f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Viewport vp = viewports.values().get(current);
batch.setProjectionMatrix(camera.combined);
batch.begin();
batch.draw(bg, 0, 0, vp.getWorldWidth(), vp.getWorldHeight());
batch.draw(sprite, 0, 0);
font.draw(batch, viewports.keys().get(current), 10, 400);
batch.end();
}
@Override
public boolean touchDown(int x, int y, int pointer, int button) {
selectNext();
return true;
}
private void log(Viewport vp) {
Gdx.app.log("VP", "World: " + vp.getWorldWidth() + "x" + vp.getWorldHeight()
+ " Screen: " + vp.getScreenWidth() + "x" + vp.getScreenHeight()
+ " Cam: (" + camera.position.x + "," + camera.position.y + ")");
}
}
| 方法 | 說明 |
|---|---|
| viewport.update(w, h, true) | 屏幕尺寸變化時必調,centerCamera=true 保持居中 |
| viewport.apply(true) | 應用投影矩陣到相機,通常在切換 Viewport 後調用 |
| viewport.getWorldWidth/Height() | 獲取當前邏輯世界尺寸(繪圖座標系依據) |
| viewport.getScreenX/Y/W/H() | 獲取視口在物理屏幕上的矩形區域(含黑邊) |
| camera.unproject(vec.set(x, y, 0)) | 屏幕觸控座標 → 世界座標(必須配合正確 Viewport) |
| viewport.setUnitsPerPixel(f) | ScreenViewport 專用,調整像素密度 |
screenX/screenY 必須經 camera.unproject() 轉世界座標,且相機必須已 viewport.apply()。resize,create() 中手動 update 會導致雙次計算,建議統一在 resize 處理。Scene2D.ui 的 Stage 並傳入 Viewport,自動適配;或手動按 getWorldWidth() 百分比佈局。AndroidManifest.xml 配置 configChanges="orientation|screenSize",在 resize 重新計算 Viewport。Gdx.graphics.getDensity() 獲取密度,ScreenViewport 可配合 setUnitsPerPixel(density) 實現物理尺寸一致。需要像素級精確(編輯器/工具) → ScreenViewport ├─ 允許變形、最省事 → StretchViewport ├─ 必須保持長寬比: │ ├─ 所有內容必須可見(UI 重、棋類、像素風) → FitViewport │ ├─ 背景沉浸、邊緣可犧牲(跑酷、射擊) → FillViewport │ └─ 大屏希望看到更多世界(策略、SLG、大地圖) → ExtendViewport └─ 自定義組合縮放邏輯 → ScalingViewport
掌握 Viewport 本質是「世界座標系 → 屏幕座標系」的可控映射。實戰中建議:遊戲邏輯層只認世界座標,渲染層交給 Viewport,輸入層用 unproject 回世界座標,三層解耦,輕松應對千奇百怪的設備碎片化。