Get the FREE Ultimate OpenClaw Setup Guide →

libgdx-camera-viewport

Scanned
npx machina-cli add skill kyu-n/gdx-claude-skills/libgdx-camera-viewport --openclaw
Files (1)
SKILL.md
17.1 KB

libGDX Camera & Viewport

Quick reference for Camera, OrthographicCamera, PerspectiveCamera, the Viewport hierarchy, coordinate conversion, and resize handling.

Camera Base Class

com.badlogic.gdx.graphics.Camera — abstract base for both orthographic and perspective cameras.

Public Fields

FieldTypeDefaultNotes
positionfinal Vector3(0, 0, 0)Mutable object, immutable reference
directionfinal Vector3(0, 0, -1)Points into the screen
upfinal Vector3(0, 1, 0)
nearfloat1Near clipping plane
farfloat100Far clipping plane
viewportWidthfloat0
viewportHeightfloat0
combinedfinal Matrix4Projection × View matrix
projectionfinal Matrix4Projection matrix only
viewfinal Matrix4View matrix only
invProjectionViewfinal Matrix4Inverse of combined
frustumfinal FrustumView frustum for culling

All Vector3 and Matrix4 fields are final — the objects are mutable but the references cannot be reassigned. There is no setPosition(), moveTo(), setDirection(), or setFov() method. Modify fields directly:

camera.position.set(x, y, 0);        // set position
camera.position.add(dx, dy, 0);      // move by delta
camera.direction.set(0, 0, -1);      // reset direction

Key Methods

// MUST call after changing ANY camera property (position, zoom, etc.)
void update()
void update(boolean updateFrustum)     // false = skip frustum update (minor perf gain)

// Orientation
void lookAt(float x, float y, float z)
void lookAt(Vector3 target)
void normalizeUp()

// Transform
void rotate(float angle, float axisX, float axisY, float axisZ)
void rotate(Vector3 axis, float angle)
void rotate(Matrix4 transform)
void rotate(Quaternion quat)
void rotateAround(Vector3 point, Vector3 axis, float angle)
void translate(float x, float y, float z)
void translate(Vector3 vec)
void transform(Matrix4 transform)

// Coordinate conversion — see Coordinate Conversion section
Vector3 unproject(Vector3 screenCoords)
Vector3 project(Vector3 worldCoords)
Ray getPickRay(float screenX, float screenY)

OrthographicCamera

Construction

OrthographicCamera camera = new OrthographicCamera();
// Sets near=0 only. viewportWidth/Height = 0, position = (0,0,0). Does NOT call update().

OrthographicCamera camera = new OrthographicCamera(viewportWidth, viewportHeight);
// Sets viewportWidth, viewportHeight, near=0. Calls update().
// Position remains at (0,0,0) — center of the viewport is at origin.

Zoom

public float zoom = 1;  // default
zoom valueEffect
1.0Normal (default)
0.5Zoomed in — see half as much world
2.0Zoomed out — see twice as much world

Zoom direction is counterintuitive: smaller values = zoom in, larger values = zoom out. The zoom field directly scales the orthographic projection extents.

setToOrtho()

camera.setToOrtho(false);                              // uses Gdx.graphics.getWidth/Height()
camera.setToOrtho(false, viewportWidth, viewportHeight); // explicit size

setToOrtho(false, w, h) does three things:

  1. Sets up = (0,1,0), direction = (0,0,-1) (Y-up orientation)
  2. Moves camera position to (zoom * w/2, zoom * h/2, 0) — so (0,0) is at bottom-left
  3. Calls update()

With yDown = true: flips up to (0,-1,0) and direction to (0,0,1) — Y-down coordinate system.

Additional Convenience Methods

void rotate(float angle)              // rotates around direction vector (z-axis by default)
void translate(float x, float y)      // calls translate(x, y, 0)
void translate(Vector2 vec)           // calls translate(vec.x, vec.y, 0)

PerspectiveCamera

PerspectiveCamera camera = new PerspectiveCamera();
// fieldOfView = 67 (vertical, degrees). near=1, far=100.

PerspectiveCamera camera = new PerspectiveCamera(fieldOfViewY, viewportWidth, viewportHeight);
// Sets fieldOfView, viewportWidth, viewportHeight. Calls update().
// Does NOT set near/far — inherits Camera defaults (near=1, far=100).

fieldOfView is vertical FOV in degrees. Horizontal FOV is derived from the aspect ratio. For 3D, you typically need to set near and far explicitly:

PerspectiveCamera camera = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.near = 0.1f;
camera.far = 1000f;
camera.position.set(10, 10, 10);
camera.lookAt(0, 0, 0);
camera.update();

For 3D camera controls, see CameraInputController in com.badlogic.gdx.graphics.g3d.utils — extends GestureDetector, provides orbit/pan/zoom via mouse.

Coordinate Conversion

Screen → World (unproject)

Input events (touchDown, Gdx.input.getX/Y()) give screen coordinates: Y-down, (0,0) at top-left. Game logic uses world coordinates: Y-up, origin depends on camera setup. You MUST convert.

With Viewport (preferred — handles letterboxing/pillarboxing correctly):

private final Vector2 touchPos = new Vector2();  // reuse — don't allocate per event

@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
    touchPos.set(screenX, screenY);
    viewport.unproject(touchPos);            // Vector2 — accounts for viewport offset
    spawnEntity(touchPos.x, touchPos.y);
    return true;
}

With Camera directly (no viewport offset handling):

private final Vector3 touchPos = new Vector3();  // reuse

@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
    touchPos.set(screenX, screenY, 0);       // z=0 for 2D
    camera.unproject(touchPos);              // Vector3 — mutates and returns same instance
    spawnEntity(touchPos.x, touchPos.y);
    return true;
}

viewport.unproject(Vector2) vs camera.unproject(Vector3):

  • Viewport version takes Vector2, handles letterbox/pillarbox offset automatically
  • Camera version takes Vector3, assumes viewport fills the entire screen
  • Prefer viewport.unproject() when using Viewports

World → Screen (project)

Vector3 worldPos = new Vector3(entity.x, entity.y, 0);
camera.project(worldPos);  // now contains screen coordinates (Y-up from bottom-left)

viewport.project(Vector2) is also available for the viewport-aware variant.

Viewport

com.badlogic.gdx.utils.viewport.Viewport — manages a Camera and handles screen-to-world mapping on resize.

Viewport Types

ViewportBehaviorBlack bars?Distortion?Cropping?
FitViewportLetterbox/pillarboxYesNoNo
FillViewportFills screen, crops edgesNoNoYes
StretchViewportStretches to fillNoYesNo
ExtendViewportExtends world to fillNoNoNo
ScreenViewport1:1 pixel mappingNoNoNo

Construction

All viewport constructors optionally accept a Camera. If you omit the Camera, the viewport creates a default OrthographicCamera. If you have your own camera, pass it to avoid disconnection:

// GOOD — your camera is used by the viewport
OrthographicCamera camera = new OrthographicCamera();
FitViewport viewport = new FitViewport(800, 480, camera);

// BAD — viewport creates its own internal camera, yours is disconnected
OrthographicCamera camera = new OrthographicCamera();
FitViewport viewport = new FitViewport(800, 480);  // viewport.getCamera() != camera

Constructor signatures:

FitViewport(float worldWidth, float worldHeight)
FitViewport(float worldWidth, float worldHeight, Camera camera)

FillViewport(float worldWidth, float worldHeight)
FillViewport(float worldWidth, float worldHeight, Camera camera)

StretchViewport(float worldWidth, float worldHeight)
StretchViewport(float worldWidth, float worldHeight, Camera camera)

ExtendViewport(float minWorldWidth, float minWorldHeight)
ExtendViewport(float minWorldWidth, float minWorldHeight, Camera camera)
ExtendViewport(float minWorldWidth, float minWorldHeight, float maxWorldWidth, float maxWorldHeight)
ExtendViewport(float minWorldWidth, float minWorldHeight, float maxWorldWidth, float maxWorldHeight, Camera camera)

ScreenViewport()
ScreenViewport(Camera camera)

ScalingViewport(Scaling scaling, float worldWidth, float worldHeight)
ScalingViewport(Scaling scaling, float worldWidth, float worldHeight, Camera camera)

FitViewport, FillViewport, and StretchViewport all extend ScalingViewport with Scaling.fit, Scaling.fill, and Scaling.stretch respectively.

ScreenViewport

Maps 1 world unit = 1 pixel (by default). World dimensions change with window size. Common for UI/Scene2D.

ScreenViewport viewport = new ScreenViewport();
viewport.setUnitsPerPixel(0.5f);  // 1 world unit = 2 pixels (DPI scaling)
float upp = viewport.getUnitsPerPixel();

ExtendViewport

Extends the world in one direction to fill the screen. Guarantees at least minWorldWidth × minWorldHeight is visible. With maxWorldWidth/maxWorldHeight, caps the extension.

update() and apply()

// In resize() — REQUIRED:
viewport.update(width, height);          // centerCamera defaults to false
viewport.update(width, height, true);    // true = center camera at (worldW/2, worldH/2)

// Before each render pass (when using multiple viewports):
viewport.apply();                        // centerCamera defaults to false
viewport.apply(true);                    // true = center camera

The centerCamera Parameter — Critical

viewport.update(w, h, true) repositions the camera to (worldWidth/2, worldHeight/2, 0), placing (0,0) at the bottom-left corner of the viewport.

viewport.update(w, h) (no third arg / false) does NOT move the camera. It stays at its current position — which defaults to (0,0) from Camera construction. This means the camera is centered at the origin, so the visible area spans from (-halfW, -halfH) to (+halfW, +halfH). Sprites placed at positive coordinates appear in the top-right quadrant only — the classic "everything is offset" bug.

Rule of thumb: Pass true unless you are manually managing camera position (e.g., following a player).

Key Viewport Methods

Camera getCamera()
void setCamera(Camera camera)               // yes, setCamera() DOES exist

float getWorldWidth() / getWorldHeight()     // world-space dimensions
int getScreenX() / getScreenY()              // viewport position on screen (for letterbox offset)
int getScreenWidth() / getScreenHeight()     // viewport size on screen

Vector2 unproject(Vector2 screenCoords)      // screen → world (handles viewport offset)
Vector2 project(Vector2 worldCoords)         // world → screen
Vector3 unproject(Vector3 screenCoords)      // 3D variant
Vector3 project(Vector3 worldCoords)         // 3D variant
Ray getPickRay(float screenX, float screenY) // for 3D picking

// Gutter (black bar) queries
int getLeftGutterWidth() / getRightGutterWidth()
int getBottomGutterHeight() / getTopGutterHeight()

Standard Setup Pattern

public class MyGame extends ApplicationAdapter {
    OrthographicCamera camera;
    FitViewport viewport;
    SpriteBatch batch;

    @Override
    public void create() {
        camera = new OrthographicCamera();
        viewport = new FitViewport(800, 480, camera);
        batch = new SpriteBatch();
    }

    @Override
    public void render() {
        ScreenUtils.clear(0, 0, 0, 1);
        camera.update();                                  // MUST call every frame
        batch.setProjectionMatrix(camera.combined);       // MUST set before begin()
        batch.begin();
        // draw...
        batch.end();
    }

    @Override
    public void resize(int width, int height) {
        viewport.update(width, height, true);             // true = (0,0) at bottom-left
    }
}

Multiple Viewports (Game + UI)

OrthographicCamera gameCamera = new OrthographicCamera();
FitViewport gameViewport = new FitViewport(800, 480, gameCamera);

Stage stage = new Stage(new ScreenViewport());  // Stage creates its own camera

@Override
public void render() {
    ScreenUtils.clear(0, 0, 0, 1);

    // --- Game pass ---
    gameViewport.apply();                          // sets glViewport for game
    gameCamera.update();
    batch.setProjectionMatrix(gameCamera.combined);
    batch.begin();
    // draw game world...
    batch.end();

    // --- UI pass ---
    stage.getViewport().apply(true);               // sets glViewport for UI
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
}

@Override
public void resize(int width, int height) {
    gameViewport.update(width, height, true);
    stage.getViewport().update(width, height, true);
}

You MUST call viewport.apply() before each render pass when using multiple viewports. Each viewport sets the OpenGL viewport (glViewport) to its screen bounds. Without apply(), the second pass renders into the first viewport's bounds.

Camera Follow Pattern

// Snap follow (instant, can feel jerky)
camera.position.set(player.x, player.y, 0);

// Smooth follow (lerp)
float alpha = 0.1f;  // 0 = no movement, 1 = instant snap. Adjust to taste.
camera.position.x += (player.x - camera.position.x) * alpha;
camera.position.y += (player.y - camera.position.y) * alpha;

// Clamped to world bounds (prevent showing outside the map)
float halfW = camera.viewportWidth * camera.zoom / 2f;
float halfH = camera.viewportHeight * camera.zoom / 2f;
camera.position.x = MathUtils.clamp(camera.position.x, halfW, worldWidth - halfW);
camera.position.y = MathUtils.clamp(camera.position.y, halfH, worldHeight - halfH);

camera.update();  // AFTER all position changes

Note on lerp alpha: The pattern above uses a fixed alpha, which is framerate-dependent. For framerate-independent smoothing, use alpha = 1 - (float)Math.pow(1 - speed, delta * 60) or similar.

Cross-References

  • SpriteBatch integration (batch.setProjectionMatrix(camera.combined)) — see libgdx-2d-rendering skill
  • Input coordinate unprojection — see libgdx-input-handling skill
  • Stage Viewport — Stage has its own Viewport; see libgdx-scene2d-ui skill
  • 3D rendering with PerspectiveCamera + ModelBatch — see 3D rendering docs

Common Mistakes

  1. Forgetting camera.update() after changing position/zoom — The combined matrix is not recalculated until update() is called. Nothing moves or zooms on screen.
  2. Not passing true to viewport.update(w, h, true) in resize() — Camera stays centered at (0,0). Sprites at positive coordinates render in the top-right quadrant only.
  3. Using screen coordinates for game logic without unprojectingtouchDown gives screen pixels (Y-down, top-left origin). Always unproject through camera or viewport. See input handling skill.
  4. Passing Vector2 to camera.unproject()camera.unproject() takes Vector3, not Vector2. Use viewport.unproject(Vector2) for the 2D convenience variant.
  5. Inventing camera.setPosition() or camera.moveTo() — These methods do not exist. position is a public final Vector3 field — modify it directly: camera.position.set(x, y, 0).
  6. Creating a Viewport without passing your Camera — The viewport creates a default OrthographicCamera internally. Your separate camera is disconnected from the viewport, and batch.setProjectionMatrix(camera.combined) won't match the viewport's transform.
  7. Not calling viewport.apply() with multiple viewports — Each viewport must apply() before its render pass to set the correct glViewport. Without it, the second pass renders into the wrong screen region.
  8. Confusing zoom directionzoom < 1 zooms in (less world visible), zoom > 1 zooms out (more world visible). This is the opposite of what most developers expect.
  9. Using Gdx.graphics.getWidth()/getHeight() for world dimensions — These return screen pixel dimensions, not world units. Use viewport.getWorldWidth()/getWorldHeight() or camera.viewportWidth/viewportHeight for world-space dimensions.
  10. Forgetting batch.setProjectionMatrix(camera.combined) before batch.begin() — SpriteBatch uses a default pixel-coordinate projection. Without this call, your camera's position, zoom, and viewport are ignored. See 2D rendering skill.
  11. Not calling viewport.update() in resize() — The viewport doesn't adapt to window size changes. Graphics appear stretched or clipped after resizing.
  12. Forgetting to multiply by camera.zoom when clamping position — The effective visible area is viewportWidth * zoom × viewportHeight * zoom. Clamping without the zoom factor lets the camera show outside world bounds when zoomed out.

Source

git clone https://github.com/kyu-n/gdx-claude-skills/blob/master/skills/libgdx-camera-viewport/SKILL.mdView on GitHub

Overview

This skill covers the Camera base class, OrthographicCamera, PerspectiveCamera, the Viewport hierarchy, coordinate conversion, and resize handling. It explains how to modify camera fields directly, how the projection and view matrices work, and how to manage multiple cameras and viewports in LibGDX.

How This Skill Works

Cameras expose public fields like position, direction, up, near, far, and matrix references. There are no setter methods; you modify fields directly and call update() to refresh matrices. The library also provides unproject/project for coordinate conversion and supports various Viewport types; setToOrtho initializes the camera and can position (0,0) at bottom-left, with an optional Y-down orientation by enabling yDown.

When to Use It

  • Debugging incorrect click positions or offset rendering
  • Working with multiple cameras and viewport setups (Fit/Fill/Stretch/Extend/ScreenViewport)
  • Coordinate conversion needs (unproject/project) and picking rays
  • Handling resize events and viewport adjustments
  • Troubleshooting stretched graphics or non-(0,0) bottom-left origins

Quick Start

  1. Step 1: Create an OrthographicCamera (and optional Viewport such as FitViewport) and initialize it
  2. Step 2: Move/rotate/zoom by assigning to camera.position, camera.direction, etc., then call camera.update()
  3. Step 3: On input or rendering, use camera.unproject(screenCoords) or camera.project(worldCoords); call viewport.update(width, height) on resize

Best Practices

  • Always call update() after changing any camera property (position, zoom, etc.)
  • Modify fields directly; there are no setters for core camera properties
  • Choose the right viewport type for your aspect ratio and scaling needs (FitViewport, FillViewport, StretchViewport, ExtendViewport, ScreenViewport)
  • Use unproject() to map screen coordinates to world coordinates before handling input
  • Understand origin/orientation with setToOrtho(false) and yDown to ensure (0,0) placement matches your needs

Example Use Cases

  • Position a 2D world so (0,0) aligns with the bottom-left using setToOrtho(false) and a given viewport
  • Debug misaligned clicks by unprojecting screen coords with the active camera/viewport
  • Implement split-screen or layered views with multiple cameras and corresponding viewports
  • Handle window resizes by updating viewport dimensions and camera accordingly
  • Avoid sprite stretching by using a proper viewport (e.g., FitViewport) for non-square aspect ratios

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers