Cómo gestionar varias pantallas en LibGDX

A la hora de crear videojuegos, es habitual que nuestra aplicación tenga varias pantallas o escenas. A continuación mostraré una manera de gestionar las distintas escenas en LibGDX.

Para este ejemplo, voy a suponer que el videojuego tiene tres pantallas: un menú principal, una segunda de selección de nivel, y la tercera que será la pantalla del juego en sí. Para no hacer este artículo demasiado largo, no usaré clases auxiliares de constantes, utilidades, etc. El esquema general de clases que usaremos será el siguiente:

LibGDX - Screen Management

Clase principal del juego

Al crear el proyecto, la clase principal (llamada por defecto MyGdxGame) extiende de AplicationListener. En LibGDX tenemos una subclase de AplicationListener, la clase Game, que está preparada para mostrar en cada momento una instancia de la interfaz Screen. Lo que haremos es utilizar Game como clase padre de MyGdxGame, y cada una de nuestras pantallas será una implementación de Screen.

Por tanto, la clase principal del proyecto será tal que así:

54
55
56
public class MyGdxGame extends Game {
...
}

Más tarde volveremos a esta clase para mostrar su implementación.

Pantallas o escenas del juego

Como hemos dicho, cada pantalla de nuestro videojuego implementará la interfaz Screen. Además, normalmente trabajo con la clase Stage. Para combinar estos dos elementos utilizo una clase abstracta, AbstractScreen, que es una extensión de Stage e implementa la interfaz Screen. Esta será la clase padre de cada una de nuestras escenas.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public abstract class AbstractScreen extends Stage implements Screen {
 
    protected AbstractScreen() {
        super( new StretchViewport(320.0f, 240.0f, new OrthographicCamera()) );
    }
 
    // Subclasses must load actors in this method
    public abstract void buildStage();
 
    @Override
    public void render(float delta) {
        // Clear screen
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
        // Calling to Stage methods
        super.act(delta);
        super.draw();
    }
 
    @Override
    public void show() {
        Gdx.input.setInputProcessor(this);
    }
 
    @Override
    public void resize(int width, int height) {
        getViewport().update(width, height, true);
    }
 
    @Override public void hide() {}
    @Override public void pause() {}
    @Override public void resume() {}
}

Aspectos clave:

  • Se utiliza un Viewport de 320 de ancho por 240 de alto.
  • Define un método abstracto, buildStage(), que deberá ser implementado por cada pantalla de nuestro juego. Este método se utiliza para añadir los actores a cada escena (LibGDX Scene2d).

 

Hemos dicho que nuestro juego estará formado por tres pantallas. Éstas tendrán los siguientes constructores:

  • Primera pantalla: Menú principal
    public MainMenuScreen() {...}
  • Segunda pantalla: Selección de nivel
    public LevelSelectScreen() {...}
  • Tercera pantalla: Juego
    Es habitual que la pantalla del juego sea la misma para varios (o todo) niveles. Para saber en qué nivel estamos, utilizaremos un parámetro en el propio constructor de la clase:

    public GameScreen(Integer level) {...}

No voy a mostrar la implementación de cada clase para no hacer el artículo demasiado largo con código no relacionado con el objetivo principal del post. En cualquier caso, al final de este artículo tienes el enlace para descargar todo el código fuente utilizado.

 

ScreenEnum: lista de pantallas disponibles

Para gestionar las escenas, crearemos un tipo Enum de java, llamado ScreenEnum, con la lista de pantallas disponibles. Además, el enumerado tiene un método getScreen() que utilizaremos para obtener cada pantalla a partir de su tipo enumerado.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum ScreenEnum {
 
    MAIN_MENU {
        public AbstractScreen getScreen(Object... params) {
            return new MainMenuScreen();
        }
    },
    LEVEL_SELECT {
        public AbstractScreen getScreen(Object... params) {
            return new LevelSelectScreen();
        }
    },
    GAME {
        public AbstractScreen getScreen(Object... params) {
            return new GameScreen((Integer) params[0]);
        }
    };
 
    publicabstract AbstractScreen getScreen(Object... params);
}

Aspectos clave

  • El enumerado define un método abstracto (getScreen()) para obtener una instancia de cada pantalla a partir de su tipo. Este método admite un argumento variable (Object... params); esto nos permitirá pasar parámetros al constructor de la pantalla cuando sea necesario.
  • Los constructores de las clases MainMenuScreen y LevelSelectScreen no necesitan parámetros.
  • GameScreen, en cambio, si necesita un argumento de tipo Integer. Por tanto, para instanciar esta pantalla hacemos casting a Integer del primer elemento recibido en el array Object... params.

Forma de uso

Si quisiéramos obtener, por ejemplo, la pantalla del menú principal haríamos lo siguiente:

AbstractScreen mainMenuScreen = ScreenEnum.MAIN_MENU.getScreen();

 

Gestor de Escenas

Para la gestión de escenas crearemos la clase ScreenManager. Esta clase, en combinación con ScreenEnum, nos permitirá cambiar de pantalla con suma facilidad:

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ScreenManager {
 
    // Singleton: unique instance
    private static ScreenManager instance;
 
    // Reference to game
    private Game game;
 
    // Singleton: private constructor
    private ScreenManager() {
        super();
    }
 
    // Singleton: retrieve instance
    public static ScreenManager getInstance() {
        if (instance == null) {
            instance = new ScreenManager();
        }
        return instance;
    }
 
    // Initialization with the game class
    public void initialize(Game game) {
        this.game = game;
    }
 
    // Show in the game the screen which enum type is received
    public void showScreen(ScreenEnum screenEnum, Object... params) {
 
        // Get current screen to dispose it
        Screen currentScreen = game.getScreen();
 
        // Show new screen
        AbstractScreen newScreen = screenEnum.getScreen(params);
        newScreen.buildStage();
        game.setScreen(newScreen);
 
        // Dispose previous screen
        if (currentScreen != null) {
            currentScreen.dispose();
        }
    }
}

Aspectos clave

  • Se utiliza el patrón de instancia única, o Singleton, lo que nos permite tener un único objeto de esta clase para toda la aplicación. Además será accesible desde cualquier parte del programa. La forma de obtener la instancia es:
    ScreenManager.getInstance()
  • La clase necesita una referencia a la clase principal del juego, (subclase de Game). Esta clase la pasamos en el método initialize():
    ScreenManager.getInstance().initialize(gameInstance);
  • El método showScreen() nos permite mostrar cualquier pantalla del juego a partir de su tipo enumerado. Además, tenemos la posibilidad de pasar argumentos cuando sea necesario. Ejemplos:
    ScreenManager.getInstance().showScreen( ScreenEnum.LEVEL_SELECT );
    ScreenManager.getInstance().showScreen( ScreenEnum.GAME, level);

 

Volviendo a la clase principal del juego

Una vez expuestas las clases ScreenEnum y ScreenManager, mostramos la implementación de la clase principal del juego:

7
8
9
10
11
12
13
public class MyGdxGame extends Game {
    @Override
    publicvoid create () {
        ScreenManager.getInstance().initialize(this);
        ScreenManager.getInstance().showScreen( ScreenEnum.MAIN_MENU );
    }
}

Poco que comentar. Simplemente se inicializa la clase ScreenManager y se muestra el menú principal.

 

Cambios de escenas

Para cambiar entre pantallas simplemente será necesario llamar al método showScreen() de la clase ScreenManager. Por ejemplo, si desde el menú principal queremos acceder a la pantalla de selección de nivel, podemos hacerlo con el siguiente listener:

btnPlay.addListener(
    new InputListener() {
        @Override
        public boolean touchDown(InputEvent event,
                                 float x, float y, int pointer, int button) {
            ScreenManager.getInstance().showScreen( ScreenEnum.LEVEL_SELECT );
            return false;
        }
    });

 

Código fuente

Puedes descargar el código fuente en mi repositorio de GitHub. Al ejecutar el programa, podremos navegar entre las tres pantallas del juego:

Menú principal

Espero que este artículo te haya servido de ayuda. Suerte en tus proyectos y un saludo.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*