Jose Georges
Jose Georges

Reputation: 805

problems with multiple cameras and viewports in libgdx

so I am doing what is suppose to be a simple game but I think I might be complicating things. I have a camera for the GameScreen and a viewPort which follow the position of the player and when it reaches to points on the sides, the camera stops following the player and stays in one point.

This by itself works fine, but then I wanted to add the pause menu and some other features in the game, creating a hud class with its own camera and viewport as well as a Stage and a shapeRenderer.

The problem comes when I create the instance of this hud inside my gameScreen, the camera that I am looking while I am playing looks like is the hudCam, which does not follow the player and basically does not let me see the player when it reaches the edges of the screen.

This is my GameScreen Class:

public class GameScreen implements Screen {

    WowInvasion game;
    ScrollingBackground background;
    private OrthographicCamera gameCam;
    private Viewport gameViewPort;

    /*
    Basically I wanna keep the same sprites running while in the menu, playing and till dead

    therefore, I'll have a switch statement with cases on where the games at, inside the functions needed. That way I'll keep
    the game has a background for the menu and there's no need for running a second screen.
     */
    public static final int MAIN_MENU = 0;
    public static final int GAME = 1;
    private static int state = 1; //current state. starts with MAIN_MENU //DEBUGGING GAME SCREEN

    //STAGES
    private GameStage gameStage; //game ui
    private menuStage mainMenu; //Main menu of the game
    private Hud hud;

    //Resources
    private TextureAtlas atlas; //for the textures most
    private Skin skin; //for the styles and fonts

    //Sprites
    private Player player;

    //Shapes
    private float progressPower; //for the power to build up
    private final float POWER_CHARGED = 1000; //limit to get power
    private final float DECREASING_POWER = 20; //limit to get power

    public GameScreen(WowInvasion game){
        this.game = game;
        gameCam = new OrthographicCamera();
        gameCam.setToOrtho(false, WowInvasion.WIDTH, WowInvasion.HEIGHT);
        gameViewPort = new StretchViewport(WowInvasion.WIDTH, WowInvasion.HEIGHT, gameCam);
        progressPower = 0f;
        game.wowAssetManager.loadTexturesGameScreen(); // tells our asset manger that we want to load the images set in loadImages method
        game.wowAssetManager.loadSkins(); //load the needed skins
        game.wowAssetManager.manager.finishLoading(); // tells the asset manager to load the images and wait until finsihed loading.
        skin = game.wowAssetManager.manager.get("ui/menuSkin.json");
    }

    @Override
    public void show() {
        game.batch.setProjectionMatrix(gameCam.combined);
        background = new ScrollingBackground();
        atlas = game.wowAssetManager.manager.get(WowAssetManager.GAME_ATLAS); //declaring atlas
        mainMenu = new menuStage(gameViewPort, game.batch, skin, game); //pass it so that we only use one batch and one same viewport
        gameStage = new GameStage(gameViewPort, game.batch, skin, game);
        hud = new Hud(game.batch, skin);
        player = new Player(atlas.findRegion("player"), (int) gameCam.viewportWidth / 2, (int) gameCam.viewportHeight / 2);

        switch(state){
            case MAIN_MENU:
                Gdx.input.setInputProcessor(mainMenu);
                break;
            case GAME:
                background.setFixedSpeed(false); //does not work in here
        }
    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        if(state ==  GAME) {
            background.setFixedSpeed(false);
            player.update(delta, gameCam.viewportWidth, gameCam.viewportHeight); //updating player for movement
           //really cheap way to charge power with velocity
            if(progressPower != POWER_CHARGED) {
                progressPower += Math.abs(player.getVelocity().x) + Math.abs(player.getVelocity().y);
                progressPower -= DECREASING_POWER;
            }
            else
                progressPower = POWER_CHARGED / 4;
        }


        mainMenu.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f)); //updating while making sure delta won't be more than 1/30f.
        gameStage.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
        game.batch.begin();
        background.updateAndRender(delta, game.batch); //updating scrolling background
        player.draw(game.batch);
        game.batch.end();
        mainMenu.draw(); //draw the menu stage
        gameStage.draw(); //draw the ui stage for the game
        hud.getStage().draw();
        hud.renderRotateMeter();

        updateCamera(0, WowInvasion.WIDTH);
        System.out.println(player.getPosition().x);
    }

    public void updateCamera(float startX, float endX){
        Vector3 position = gameCam.position;
        //linear interpolation : a + (b - a) * lerp
        //b = player position
        //a = current camera position
        //lerp = interpolation factor
            position.x = gameCam.position.x + (player.getPosition().x - gameCam.position.x) * .1f;

        //making the camera stay when the player gets to close to the sides
        if(position.x < startX) {
            position.x = startX;
        }

        if(position.x > endX){
            position.x = endX;
        }

        gameCam.position.set(position);
        gameCam.update();
    }

    @Override
    public void resize(int width, int height) {
       gameViewPort.update(width, height);
        //hud.getViewport().update(width, height);
    }

    @Override
    public void pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void hide() {
        dispose();
    }

    @Override
    public void dispose() {
        mainMenu.dispose();
        gameStage.dispose();
        game.dispose();
        hud.dispose();
    }

    public static void setState(int state) {
        GameScreen.state = state;
    }

}

And this is my HUD:

public class Hud implements Disposable{

    private Stage stage;
    private Viewport viewport;
    Button buttonPause, buttonResume;
    private OrthographicCamera hudCam;
    private ShapeRenderer sp; //like a batch for shapes

    public Hud(SpriteBatch sb, Skin skin){
        hudCam = new OrthographicCamera();
        hudCam.setToOrtho(false, WowInvasion.WIDTH, WowInvasion.HEIGHT);
        viewport = new StretchViewport(WowInvasion.WIDTH, WowInvasion.HEIGHT, hudCam);
        stage = new Stage(viewport, sb);
        sp = new ShapeRenderer();

        Table table = new Table();
        table.top();
        //this makes the table the size of the stage
        table.setFillParent(true);

        buttonPause = new Button(skin, "pause");
        buttonPause.setTransform(true);
        buttonPause.addListener(new ClickListener(){ //listener to handle event
            @Override
            public void clicked(InputEvent event, float x, float y) {

            }
        });

        buttonResume = new Button(skin, "resume");
        buttonResume.setTransform(true);
        buttonResume.setScale(0.5f);
        buttonResume.addListener(new ClickListener(){
            @Override
            public void clicked(InputEvent event, float x, float y) {
                buttonResume.setVisible(false);

            }
        });

        table.add(buttonPause);
        table.row();
        table.add(buttonResume);

        stage.addActor(table);
    }

    public void renderRotateMeter(){
        sp.setProjectionMatrix(hudCam.combined);
        sp.begin(ShapeRenderer.ShapeType.Filled);
        sp.setColor(Color.YELLOW);
        sp.rect(hudCam.position.x,hudCam.position.y, WowInvasion.WIDTH / 2, 20);
        sp.end();
    }

    public Viewport getViewport() {
        return viewport;
    }

    public Stage getStage() {
        return stage;
    }

    @Override
    public void dispose() {
        stage.dispose();
        sp.dispose();
    }
}

thanks in advance!

EDIT

so I tried passing the gameCam has a parameter to the hud and instead of making a new OrthographicCamera I used that one has the hudCamara as well and well, the movement with the player is perfect except now the thins from the Hud do not move at all..

Upvotes: 1

Views: 949

Answers (1)

haxpor
haxpor

Reputation: 2601

It looks like you only set projectionMatrix to only HUD camera as seen in

sp.setProjectionMatrix(hudCam.combined);

Try to set it the same to other stuff outside of the HUD class prior to draw call too.

Another thing to keep in mind is that, when you involve using multiple Viewport and Camera in the game as most of the time it will be 1 Viewport matching with 1 Camera and work with another set as in your case. In draw call, you need to call apply() or apply(true) of Viewport class too to tell the system that you will draw based on which viewport thus in turn it will adhere to screen coordinate that was set up by viewport's attaching camera.

So assume you have 2 objects that needed to be called in different viewport consecutively, do it like the following code. The methods call is correct according to libgdx API but variable names are fictional.

// draw objA adhering to viewportA (thus cameraA) <-- assume it's player cam
sb.setProjectionMatrix(cameraA.combined);
viewportA.apply();
objA.draw();

// draw objB adhering to viewportB (thus cameraB) <-- assume it's HUD cam
sb.setProjectionMatrix(cameraB.combined);
viewportB.apply(true);  // send in true as for HUD, we always want to center the screen
objB.draw();

In summary, 2 things to keep in mind when drawing objects that use multiple of camera and viewport in consecutive draw call.

  1. Set projection matrix to either SpriteBatch or ShapeRenderer.
  2. Call apply() or apply(true) of Viewport class to let it know you work with this viewport.

Upvotes: 2

Related Questions