Search code examples
androidcameralibgdxgesture

LibGDX panning is jumping sometimes


I am working on a tile based game which should be scrollable but only inside the boundaries of the world. So I set up the pan() method as several examples/tutorials suggest but it is not always working properly. Sometimes it is jumping back to the starting point of the last gesture or is only moving very slow. Additionally the borders are not working either. Maybe somebody can point out the mistakes I made.

public void pan(float x, float y, float deltaX, float deltaY) { 
    moveCamera(x, y);   
}   

private void moveCamera(float x, float y) {
    Vector3 new_position = getNewCameraPosition((int) x, (int)y);

    if(!cameraOutOfLimit(new_position)) 
        this.getViewport().getCamera().translate(new_position.sub(this.getViewport().getCamera().position));

    lastTouchDown.set(x, y, 0);
}

private Vector3 getNewCameraPosition(int x, int y) { 
    Vector3 newPosition = lastTouchDown;        
    newPosition.sub(x, y, 0);   
    newPosition.y = -newPosition.y;     
    newPosition.add(this.getViewport().getCamera().position);

    return newPosition;
}

private boolean cameraOutOfLimit( Vector3 position ) {
    int x_left_limit = (int) (Global.SCREEN_WIDTH / 2);
    int x_right_limit = (int) (Global.COLS_OF_TILES * Global.TILE_WIDTH - (Global.SCREEN_WIDTH / 2));
    int y_bottom_limit = (int) (Global.SCREEN_HEIGHT / 2);
    int y_top_limit = (int) (Global.ROWS_OF_TILES * Global.TILE_HEIGHT - Global.SCREEN_HEIGHT / 2);

    if( position.x < x_left_limit || position.x > x_right_limit )
        return true;
    else if( position.y < y_bottom_limit || position.y > y_top_limit )
        return true;
    else
        return false; 
}

Solution

  • The code above seems convoluted to me, and I don't really understandwhy you modify a vector called lastTouchPosition in ways that have nothing to do with touch position.

    I would do something like this. These methods clamp your target X and Y positions whenever you want to move your camera.

    float clampCamTargetX(float x) {
        x = Math.max(x, (int) (Global.SCREEN_WIDTH / 2));
        x = Math.min(x, (int) (Global.COLS_OF_TILES * Global.TILE_WIDTH - (Global.SCREEN_WIDTH / 2)));
        return x;
    }
    
    float clampCamTargetY (float y){
        y = Math.max(y,(int)(Global.SCREEN_HEIGHT/2));
        y = Math.min(y,(int)(Global.ROWS_OF_TILES*Global.TILE_HEIGHT-Global.SCREEN_HEIGHT/2));
        return y;
    }
    

    Then if you want to pan it, you would do something like this:

    void panCamera(float deltaX, float deltaY) {
        Camera camera = this.getViewport().getCamera();
        Vector3 camPosition = camera.position;
        camPosition.x = clampCamTargetX(camPosition.x + deltaX);
        camPosition.y = clampCamTargetY(camPosition.y + deltaY);
        camera.update();
    }
    

    Or if you want a complete solution for smoothly moving the camera to the last position touched, try this:

    float startXCam, startYCam, targetXCam, targetYCam;
    float elapsedTimeCam;
    boolean panningCam = false;
    static final float CAM_PAN_DURATION = 0.4f;
    
    public void render (){
        //...
        panCameraToTouchPoint(Gdx.graphics.getDeltaTime());
        //...
    }
    
    void panCameraToTouchPoint (float deltaTime){
        Camera camera = getViewport().getCamera();
        Vector3 camPosition = camera.position;
        if (Gdx.input.justTouched()) {
            startXCam = camPosition.x;
            startYCam = camPosition.y;
            targetXCam = clampCamTargetX(Gdx.input.getX());
            targetYCam = clampCamTargetY(Gdx.input.getY());
            elapsedTimeCam = 0;
            panningCam = true;
        }
    
        if (panningCam){
            elapsedTimeCam += deltaTime;
            float alpha = elapsedTimeCam / CAM_PAN_DURATION;
            if (alpha >= 1){
                alpha = 1;
                panningCam = false;
            }
            camPosition.x = Interpolation.pow2Out.apply(startXCam, targetXCam, alpha);
            camPosition.y = Interpolation.pow2Out.apply(startYCam, targetYCam, alpha);
            camera.update();
        }
    }