Bug with states in LIBGDX

Hi, people!

I’m new at game development. I just wrote some games to learn the basic structure and now I’m trying to go with a real game.

Well, I’m using libgdx. I created my own Game State Manager instead of using the Game/Screen, because they don’t save state. That way, I’m using an abstract class called GameState:

public abstract class GameState {
    
    protected GameStateManager gsm;
    
    public abstract void update();
    public abstract void render();
    public abstract void dispose();
    
}

The GameStateManager class is just a Stack:

public class GameStateManager {
    
    public Stack<GameState> states;
    
    public GameStateManager(){
        System.out.println("Constructor do "+this.toString()+" criado");
        states = new Stack<GameState>();
        
    }
    
    public void addState(GameState state){
        states.push(state);
    }
    
    public void setState(GameState state){        
        
        if(!states.empty()){            
            states.pop();
            states.push(state);
        } else {
            states.push(state);
        }
        
    }
    
    public void removeState(){
        if(!states.empty()) {states.pop();}
    }
    
    public GameState getState(){
        return states.peek();
    }
    
    public void update(){
        states.peek().update();
    }
    
    public void render(){
        states.peek().render();
    }
    
    public void dispose(){
        states.peek().dispose();
    }
    
}

And the main game class is pretty simple:

public class MainGame extends ApplicationAdapter {
    
        private static final String TAG = Chemeng.class.getName();
        
        private GameStateManager gsm;
	
	@Override
	public void create () {
            
            gsm = new GameStateManager();
            gsm.setState(new SplashState(gsm));            
            
	}

	@Override
	public void render () {
            
            input();
            
            gsm.update();
            gsm.render();
            
            System.out.printf(" GSM %s: %d%n", gsm.states.peek().toString(), this.gsm.states.size());
	}
	
	@Override
	public void dispose () {
            gsm.dispose();
	}
        
        private void input(){
            if(Gdx.input.isKeyJustPressed(Keys.ESCAPE)){
                Gdx.app.exit();
            }
        }
        
}

It’s working fine, besides a crazy loop for me. I have the MainMenuState where the player will choose their options (play campaing, tutorials, load, save, settings, etc, etc). One of them take to the LevelState, what also works fine.

The problem is at the LevelState. Somehow, if we click at the same area from the button in the MainMenuState it works! But the LevelState there is no Stage and no listeners! I checked the GameStateManager and etc and nothing makes sense to me. Anyone knows how it’s happening?

MainMenuState:


public class MainMenuState extends GameState {
    
    private Stage stage;
    
    private Skin skin;
    private TextureAtlas uiAtlas;
    
    private Image imgBackground, imgCampanha, imgTutorial, imgCenario;
    private Image imgSandbox, imgConfig, imgCreditos;
        
    
    public MainMenuState(GameStateManager gsm){
        System.out.println("Constructor do "+this.toString()+" criado");
        
        this.gsm = gsm;
        
        stage = new Stage(new ScreenViewport());
        Gdx.input.setInputProcessor(stage);
        
        uiAtlas = new TextureAtlas(
                Gdx.files.internal("skin/uiskin.atlas"));
        
        skin = new Skin(
                Gdx.files.internal("skin/uiskin.json"),
                uiAtlas);
        
        Texture background = new Texture("img/menu/chemical_plant_menu_background.jpg");
        imgBackground = new Image(background);
        imgBackground.addAction(Actions.alpha(0.15f));        
        
        imgCampanha = new Image(new Texture("img/menu/executive_menu.png"));
        imgCampanha.setPosition(750, 500);
        imgCampanha.addListener(new InputListener(){
            @Override
            public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){
                System.out.println("Imagem da campanha clicada...");                
                teste();
                return true;
            }
        });
                
        imgTutorial = new Image(new Texture("img/menu/estudante_menu.png"));
        imgTutorial.setPosition(710, 250);
        
        imgCenario = new Image(new Texture("img/menu/engineer_menu.png"));
        imgCenario.setPosition(480, 230, 10);
        
        imgSandbox = new Image(new Texture("img/menu/crazy_scientist_menu.png"));
        imgSandbox.setPosition(50, 520);
        
        imgConfig = new Image(new Texture("img/menu/quimica_menu.png"));
        imgConfig.setPosition(50, 50);
       

        stage.addActor(imgBackground);
        stage.addActor(imgCampanha);
        stage.addActor(imgTutorial);
        stage.addActor(imgCenario);
        stage.addActor(imgSandbox);
        stage.addActor(imgConfig);
        
        stage.addAction(Actions.alpha(0));
        stage.addAction(Actions.fadeIn(2));
        
        
    }
    
    private void teste(){
        System.out.println("Método teste() chamado;");
        System.out.println("Removendo Actor");
        stage.addAction(Actions.removeActor(imgCampanha));
        imgCampanha = null;
        System.out.println("Atualizando GSM para o novo LevelState");
        gsm.setState(new LevelState(gsm));
    }

    @Override
    public void update() {

    }

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

        stage.act();
        stage.draw();
    }

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

LevelState:


public class LevelState extends GameState {

    private OrthographicCamera camera;
    private SpriteBatch batch;
    
    private ShapeRenderer shaperenderer;
    
    private Texture map;
    
    public LevelState(GameStateManager gsm){
        System.out.println("Constructor do "+this.toString()+" criado");
        this.gsm = gsm;

        batch = new SpriteBatch();
        camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        
        camera.translate(camera.viewportWidth/2, camera.viewportHeight/2);
        camera.update();
        
        map = new Texture(Gdx.files.internal("img/map_test_camera.png"));        
        
        shaperenderer = new ShapeRenderer();
        
    }
    
    private void drawUI(){
        
        shaperenderer.begin(ShapeRenderer.ShapeType.Filled);
        
        shaperenderer.setColor(Color.FIREBRICK);
        
        shaperenderer.rect(Gdx.graphics.getWidth()-120, 0, 120, Gdx.graphics.getWidth());        
        shaperenderer.rect(0, 0, Gdx.graphics.getWidth(), 100);
        
        shaperenderer.end();        
        
    }
    
    @Override
    public void update() {
        
    }

    @Override
    public void render() {
        
        Gdx.gl.glClearColor(0, 0.1f, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
         
        camera.update();
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
        batch.draw(map, 0, 0);
        batch.end();
        
        drawUI();
        
        camera.translate(1f, 1f);
        
    }

    @Override
    public void dispose() {
        batch.dispose();
        map.dispose();
        shaperenderer.dispose();
    }
    
}

I even tried to remove the Actor that calls the listener as you can see at teste() method, but even this doesn’t work.

I’m completly lost =[

Ok, I just found the problem, lol.

I need to call dispose() before change to the new State.

OK to that, but still crazy for me, the LevelState does not has any information about the MainMenuState. Whatever, I’m studying this libgdx for about a month or two…

You seem to be way over-thinking things…

Its not clear if you mean states like when the window has focus or not, or is resized, etc, or if you mean states like in the menu, playing or paused… I’m guessing the second

What you can do to create the same effect is to create a series of screens…

So, you would have :

  • program starts, create the screens, spritebatch, and all the resources that you will need across the program this is where you create the screens, each screen will deal with a “state” as you seem to describe it
  • then as states change you will simply setscreen to the desired screen

each screen can have its own UI elements, and since each screen can have its own stage or whatever you need.

I’d elaborate further, but not sure if that addresses your question

So, just going back, because i found the real problem; it was in the Gdx.input.setProcessor();

Each time I change a state, I must call this method again to the new Stage;

My game is now much more advanced and Im actually using the Multiplex, but thats the same idea.