Search code examples
javalibgdxdialog

how to properly implement a Dialog box using libgdx


I'm struggling with implementing a dialog box in my program. The main program does not use stage. However when the users lives are at 0 I would like to popup a dialog that either restarts the game or exits the game.

I have created a separate class for the Dialog using the following code.

public class GameOver {

    //dialog game over
    private Dialog dlgGameOver;
    private Skin skin;
    private Stage stage;
    private TextButton btnMain;
    private TextButton btnTryAgain;
    private GameScreen gameScreen;

    public GameOver(final GameScreen gameScreen){
        //create the game over dialog
        this.gameScreen = gameScreen;
        skin = new Skin(Gdx.files.internal("uiskin.json"));
        dlgGameOver = new Dialog(" ",skin);
        stage = new Stage();
        btnMain = new TextButton("Main", skin);
        btnTryAgain = new TextButton("Try Again", skin);
        dlgGameOver.text("Game over!");
        dlgGameOver.button(btnTryAgain);
        dlgGameOver.button(btnMain);
        dlgGameOver.show(stage);
        btnMain.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                System.out.println("Button Pressed");
            }
        });
        btnTryAgain.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                gameScreen.setIntLives(3);
                gameScreen.setIntScore(0);
                System.out.println("Button Pressed");
            }
        });
        Gdx.input.setInputProcessor(stage);

    }

    public Stage getStage() {
        return stage;
    }
}

The main game screen does not use stage. In the update method I create the GameOver class if livers are at 0

if(intLives == 0) {
    if(!boolGameOver) {
        dlgGameOver = new GameOver(this);
        boolGameOver = true;
    } else if(intLives !=0) {
        boolGameOver = false;
    }
}

In the render method I draw the stage

if(intLives == 0) {
    dlgGameOver.getStage().draw();
} else { 
    //<rest of normal update code follows here>
}

This works perfectly the first time the dialog is created. When hitting restart the game will restart (or actually reset the score and lives). However, the second time you run out of lives the dialog is displayed as expected but the buttons will not work. Somehow it seems the clicklistener is not initialized the second time.

Am I doing something fundamentally wrong here? If my approach is totally wrong I'm happy to hear it as well.


Solution

  • The Dialog class is a little unintuitive without taking a closer look.

    That's why I provide a small working example:

    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Dialog;
    import com.badlogic.gdx.scenes.scene2d.ui.Skin;
    import com.badlogic.gdx.utils.Timer;
    import com.badlogic.gdx.utils.Timer.Task;
    
    public class TestGame extends ApplicationAdapter
    {
        Dialog  endDialog;
    
        Skin    skin;
        Stage   stage;
    
        @Override
        public void create()
        {
            skin = new Skin(Gdx.files.internal("uiskin.json"));
    
            stage = new Stage();
    
            Gdx.input.setInputProcessor(stage);
    
            endDialog = new Dialog("End Game", skin)
            {
                protected void result(Object object)
                {
                    System.out.println("Option: " + object);
                    Timer.schedule(new Task()
                    {
    
                        @Override
                        public void run()
                        {
                            endDialog.show(stage);
                        }
                    }, 1);
                };
            };
    
            endDialog.button("Option 1", 1L);
            endDialog.button("Option 2", 2L);
    
            Timer.schedule(new Task()
            {
    
                @Override
                public void run()
                {
                    endDialog.show(stage);
                }
            }, 1);
    
        }
    
        @Override
        public void render()
        {
            Gdx.gl.glClearColor(1, 0, 0, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    
            stage.act();
            stage.draw();
    
        }
    
        @Override
        public void dispose()
        {
            stage.dispose();
        }
    }
    

    Each time you click on an Dialog option (option 1 or option 2) the result object is printed on the system.out (here 1L or 2L, can be any object) and the dialog is displayed again in 1 second.

    The uiskin is taken from the libgdx tests.

    To adapt it more to your needs you can change the result method to:

    protected void result(Object object)
    {
        if (object.equals(1L))
        {
            gameScreen.setIntLives(3);
            gameScreen.setIntScore(0);
            System.out.println("Button Pressed");
        } else {
            // Goto main menut
        }
    };
    

    And add the buttons like this:

    endDialog.button("Retry", 1L);
    endDialog.button("Main Menu", 2L);
    

    Note that the Dialog class is only instantiated once. (so my comment was not correct)


    Just to give you an idea on what you can do with it:

    Since any object can be used you could use reflection:

    try
    {
        endDialog.button("doX",
                ClassReflection.getDeclaredMethod(this.getClass(), "doX"));
        endDialog.button("doY",
                ClassReflection.getDeclaredMethod(this.getClass(), "doY"));
    } catch (ReflectionException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    

    And the result method would look like:

    protected void result(Object object)
    {
        try
        {
            ((Method) object).invoke(TestGame.this);
        } catch (ReflectionException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    };
    

    Now you just need to implement these methods:

    public void doX()
    {
        System.out.println("doX");
    }
    
    public void doY()
    {
        System.out.println("doY");
    }
    

    Just to give you a few ideas.