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:
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
MainMenuScreenyLevelSelectScreenno necesitan parámetros. GameScreen, en cambio, si necesita un argumento de tipoInteger. Por tanto, para instanciar esta pantalla hacemos casting aIntegerdel primer elemento recibido en el arrayObject... 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étodoinitialize():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:
Espero que este artículo te haya servido de ayuda. Suerte en tus proyectos y un saludo.

