{"id":166,"date":"2014-11-10T21:41:55","date_gmt":"2014-11-10T20:41:55","guid":{"rendered":"http:\/\/www.pixnbgames.com\/blog\/?p=166"},"modified":"2015-08-21T23:53:00","modified_gmt":"2015-08-21T22:53:00","slug":"como-gestionar-varias-pantallas-en-libgdx","status":"publish","type":"post","link":"https:\/\/www.pixnbgames.com\/blog\/libgdx\/como-gestionar-varias-pantallas-en-libgdx\/","title":{"rendered":"C\u00f3mo gestionar varias pantallas en LibGDX"},"content":{"rendered":"<p style=\"text-align: justify;\">A la hora de crear videojuegos, es habitual que nuestra aplicaci\u00f3n tenga varias pantallas o escenas.\u00a0A\u00a0continuaci\u00f3n mostrar\u00e9 una\u00a0manera de gestionar las distintas escenas en LibGDX.<\/p>\n<p style=\"text-align: justify;\">Para este ejemplo, voy a suponer que el videojuego tiene tres\u00a0pantallas: un men\u00fa principal, una segunda de selecci\u00f3n de nivel, y la tercera que ser\u00e1 la pantalla del juego en s\u00ed. Para no hacer este art\u00edculo demasiado largo, no usar\u00e9 clases auxiliares de constantes, utilidades, etc. El esquema general de clases que usaremos ser\u00e1 el siguiente:<\/p>\n<p style=\"text-align: justify;\"><a href=\"http:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/LibGDX-Screen-Management.png\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-medium wp-image-169\" src=\"http:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/LibGDX-Screen-Management-300x225.png\" alt=\"LibGDX - Screen Management\" width=\"300\" height=\"225\" srcset=\"https:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/LibGDX-Screen-Management-300x225.png 300w, https:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/LibGDX-Screen-Management.png 960w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<h2 style=\"text-align: justify;\"><\/h2>\n<h2 style=\"text-align: justify;\">Clase principal del juego<\/h2>\n<p style=\"text-align: justify;\">Al crear el proyecto, la clase principal (llamada por defecto <code>MyGdxGame<\/code>) extiende\u00a0de <code>AplicationListener<\/code>. En LibGDX tenemos una\u00a0subclase de <code>AplicationListener<\/code>, la clase <code>Game<\/code>, que est\u00e1 preparada para\u00a0mostrar\u00a0en cada momento una instancia de la interfaz\u00a0<code>Screen<\/code>. Lo que haremos es utilizar <code>Game<\/code> como clase padre de <code>MyGdxGame<\/code>, y cada una de nuestras pantallas ser\u00e1 una implementaci\u00f3n de <code>Screen<\/code>.<\/p>\n<p style=\"text-align: justify;\">Por tanto, la clase principal del proyecto ser\u00e1 tal que as\u00ed:<\/p>\n<pre lang=\"Java\" line=\"54\">public class MyGdxGame extends Game {\r\n...\r\n}<\/pre>\n<p style=\"text-align: justify;\">M\u00e1s tarde volveremos a\u00a0esta clase para mostrar su implementaci\u00f3n.<\/p>\n<h2 style=\"text-align: justify;\"><\/h2>\n<h2 style=\"text-align: justify;\">Pantallas o escenas del juego<\/h2>\n<p style=\"text-align: justify;\">Como hemos dicho, cada pantalla de nuestro videojuego implementar\u00e1 la interfaz <code>Screen<\/code>. Adem\u00e1s, normalmente trabajo con la clase <code>Stage<\/code>. Para combinar estos dos elementos\u00a0utilizo una clase abstracta, <code>AbstractScreen<\/code>, que es\u00a0una extensi\u00f3n de <code>Stage<\/code> e\u00a0implementa la interfaz <code>Screen<\/code>. Esta ser\u00e1 la clase padre de cada una de nuestras\u00a0escenas.<\/p>\n<pre lang=\"Java\" line=\"10\">public abstract class AbstractScreen extends Stage implements Screen {\r\n \r\n    protected AbstractScreen() {\r\n        super( new StretchViewport(320.0f, 240.0f, new OrthographicCamera()) );\r\n    }\r\n \r\n    \/\/ Subclasses must load actors in this method\r\n    public abstract void buildStage();\r\n\r\n    @Override\r\n    public void render(float delta) {\r\n        \/\/ Clear screen\r\n        Gdx.gl.glClearColor(1, 0, 0, 1);\r\n        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);\r\n \r\n        \/\/ Calling to Stage methods\r\n        super.act(delta);\r\n        super.draw();\r\n    }\r\n\r\n    @Override\r\n    public void show() {\r\n        Gdx.input.setInputProcessor(this);\r\n    }\r\n\r\n    @Override\r\n    public void resize(int width, int height) {\r\n        getViewport().update(width, height, true);\r\n    }\r\n\r\n    @Override public void hide() {}\r\n    @Override public void pause() {}\r\n    @Override public void resume() {}\r\n}<\/pre>\n<h3 style=\"text-align: justify;\">Aspectos clave:<\/h3>\n<ul>\n<li style=\"text-align: justify;\">Se utiliza un <a title=\"LibGDX Viewports\" href=\"https:\/\/github.com\/libgdx\/libgdx\/wiki\/Viewpor\" target=\"_blank\">Viewport <\/a>de 320 de ancho por 240 de alto.<\/li>\n<li style=\"text-align: justify;\">Define un m\u00e9todo abstracto, <code>buildStage()<\/code>, que deber\u00e1 ser implementado por cada pantalla de nuestro juego. Este m\u00e9todo se utiliza para a\u00f1adir los actores a cada\u00a0escena (<a href=\"https:\/\/github.com\/libgdx\/libgdx\/wiki\/Scene2d\" target=\"_blank\">LibGDX Scene2d<\/a>).<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p style=\"text-align: justify;\">Hemos dicho que nuestro juego estar\u00e1 formado por tres pantallas. \u00c9stas tendr\u00e1n los siguientes constructores:<\/p>\n<ul>\n<li>Primera pantalla: Men\u00fa principal\n<pre lang=\"Java\">public MainMenuScreen() {...}<\/pre>\n<\/li>\n<li>Segunda pantalla: Selecci\u00f3n de nivel\n<pre lang=\"Java\">public LevelSelectScreen() {...}<\/pre>\n<\/li>\n<li style=\"text-align: justify;\">Tercera pantalla: Juego<br \/>\nEs habitual que la pantalla del juego sea la misma para varios (o todo) niveles. Para saber en qu\u00e9 nivel estamos, utilizaremos un par\u00e1metro en el propio constructor de la clase:<\/p>\n<pre lang=\"Java\">public GameScreen(Integer level) {...}<\/pre>\n<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">No voy a mostrar la implementaci\u00f3n de cada clase para no hacer el art\u00edculo demasiado largo\u00a0con c\u00f3digo no relacionado con el objetivo principal del post. En cualquier caso, al final de este art\u00edculo tienes el enlace para descargar todo el c\u00f3digo fuente utilizado.<\/p>\n<p>&nbsp;<\/p>\n<h2>ScreenEnum: lista de pantallas disponibles<\/h2>\n<p style=\"text-align: justify;\">Para gestionar las escenas, crearemos un tipo Enum de java, llamado <code>ScreenEnum<\/code>, con la lista de pantallas disponibles. Adem\u00e1s, el enumerado tiene un m\u00e9todo <code>getScreen()<\/code> que utilizaremos para obtener cada pantalla a partir de su tipo enumerado.<\/p>\n<pre lang=\"Java\" line=\"8\">public enum ScreenEnum {\r\n\r\n    MAIN_MENU {\r\n        public AbstractScreen getScreen(Object... params) {\r\n            return new MainMenuScreen();\r\n        }\r\n    },\r\n    LEVEL_SELECT {\r\n        public AbstractScreen getScreen(Object... params) {\r\n            return new LevelSelectScreen();\r\n        }\r\n    },\r\n    GAME {\r\n        public AbstractScreen getScreen(Object... params) {\r\n            return new GameScreen((Integer) params[0]);\r\n        }\r\n    };\r\n\r\n    publicabstract AbstractScreen getScreen(Object... params);\r\n}<\/pre>\n<h3>Aspectos clave<\/h3>\n<ul>\n<li style=\"text-align: justify;\">El enumerado define un m\u00e9todo abstracto (<code>getScreen()<\/code>) para obtener una instancia de cada pantalla a partir de su\u00a0tipo.\u00a0Este m\u00e9todo\u00a0admite un argumento variable (<code>Object... params<\/code>); esto nos permitir\u00e1 pasar par\u00e1metros al constructor de la pantalla cuando sea necesario.<\/li>\n<li style=\"text-align: justify;\">Los constructores de las clases\u00a0<code>MainMenuScreen<\/code> y <code>LevelSelectScreen<\/code> no necesitan par\u00e1metros.<\/li>\n<li style=\"text-align: justify;\"><code>GameScreen<\/code>, en cambio, si necesita un argumento de tipo <code>Integer<\/code>. Por tanto, para instanciar esta pantalla\u00a0hacemos casting a\u00a0<code>Integer<\/code>\u00a0del primer elemento\u00a0recibido en el array <code>Object... params<\/code>.<\/li>\n<\/ul>\n<h3>Forma de uso<\/h3>\n<p>Si quisi\u00e9ramos obtener, por ejemplo, la pantalla del men\u00fa principal har\u00edamos lo siguiente:<\/p>\n<pre>AbstractScreen mainMenuScreen = ScreenEnum.MAIN_MENU.getScreen();<\/pre>\n<p>&nbsp;<\/p>\n<h2>Gestor de Escenas<\/h2>\n<p style=\"text-align: justify;\">Para la gesti\u00f3n de escenas crearemos la clase <code>ScreenManager<\/code>. Esta clase, en combinaci\u00f3n con <code>ScreenEnum<\/code>, nos permitir\u00e1 cambiar de pantalla con suma facilidad:<\/p>\n<pre lang=\"Java\" line=\"7\">public class ScreenManager {\r\n\r\n\u00a0 \u00a0\u00a0\/\/ Singleton: unique instance\r\n\u00a0 \u00a0\u00a0private static ScreenManager instance;\r\n\r\n\u00a0 \u00a0\u00a0\/\/ Reference to game\r\n\u00a0 \u00a0\u00a0private Game game;\r\n\r\n\u00a0 \u00a0\u00a0\/\/ Singleton: private constructor\r\n\u00a0 \u00a0\u00a0private ScreenManager() {\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0super();\r\n\u00a0 \u00a0\u00a0}\r\n\r\n\u00a0 \u00a0\u00a0\/\/ Singleton: retrieve instance\r\n\u00a0 \u00a0\u00a0public static ScreenManager getInstance() {\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0if (instance == null) {\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0instance = new ScreenManager();\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0}\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0return instance;\r\n\u00a0 \u00a0\u00a0}\r\n\r\n\u00a0 \u00a0\u00a0\/\/ Initialization with the game class\r\n\u00a0 \u00a0\u00a0public void initialize(Game game) {\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0this.game = game;\r\n\u00a0 \u00a0\u00a0}\r\n\r\n\u00a0 \u00a0\u00a0\/\/ Show in the game the screen which enum type is received\r\n\u00a0 \u00a0\u00a0public void showScreen(ScreenEnum screenEnum, Object... params) {\r\n\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\/\/ Get current screen to dispose it\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0Screen currentScreen = game.getScreen();\r\n\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\/\/ Show new screen\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0AbstractScreen newScreen = screenEnum.getScreen(params);\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0newScreen.buildStage();\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0game.setScreen(newScreen);\r\n\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\/\/ Dispose previous screen\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0if (currentScreen != null) {\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0currentScreen.dispose();\r\n\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0}\r\n\u00a0 \u00a0\u00a0}\r\n}<\/pre>\n<h3>Aspectos clave<\/h3>\n<ul>\n<li style=\"text-align: justify;\">Se utiliza el patr\u00f3n de instancia \u00fanica, o Singleton, lo que nos permite tener un \u00fanico objeto de esta clase para toda la aplicaci\u00f3n. Adem\u00e1s\u00a0ser\u00e1 accesible desde cualquier parte del programa. La forma de obtener la instancia es:\n<pre lang=\"Java\">ScreenManager.getInstance()<\/pre>\n<\/li>\n<li style=\"text-align: justify;\">La clase necesita una referencia a la clase principal del juego, (subclase de <code>Game<\/code>). Esta clase la pasamos en el m\u00e9todo <code>initialize()<\/code>:\n<pre lang=\"Java\">ScreenManager.getInstance().initialize(gameInstance);<\/pre>\n<\/li>\n<li style=\"text-align: justify;\">El m\u00e9todo <code>showScreen()<\/code> nos permite mostrar cualquier pantalla del juego a partir de su tipo enumerado. Adem\u00e1s, tenemos la posibilidad de pasar argumentos cuando sea necesario. Ejemplos:\n<pre lang=\"Java\">ScreenManager.getInstance().showScreen( ScreenEnum.LEVEL_SELECT );\r\nScreenManager.getInstance().showScreen( ScreenEnum.GAME, level);<\/pre>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>Volviendo a la clase principal del juego<\/h2>\n<p style=\"text-align: justify;\">Una vez expuestas las clases <code>ScreenEnum<\/code> y <code>ScreenManager<\/code>, mostramos la implementaci\u00f3n de la clase principal del juego:<\/p>\n<pre lang=\"Java\" line=\"7\">public class MyGdxGame extends Game {\r\n    @Override\r\n    publicvoid create () {\r\n        ScreenManager.getInstance().initialize(this);\r\n        ScreenManager.getInstance().showScreen( ScreenEnum.MAIN_MENU );\r\n    }\r\n}<\/pre>\n<p style=\"text-align: justify;\">Poco que comentar. Simplemente se inicializa la clase <code>ScreenManager<\/code> y se muestra el men\u00fa principal.<\/p>\n<p>&nbsp;<\/p>\n<h2>Cambios de escenas<\/h2>\n<p style=\"text-align: justify;\">Para cambiar entre pantallas simplemente ser\u00e1 necesario llamar al m\u00e9todo <code>showScreen()<\/code> de la clase <code>ScreenManager<\/code>. Por ejemplo, si desde el men\u00fa principal queremos acceder a la pantalla de selecci\u00f3n de nivel, podemos hacerlo con el siguiente listener:<\/p>\n<pre lang=\"Java\">btnPlay.addListener(\r\n    new InputListener() {\r\n        @Override\r\n        public boolean touchDown(InputEvent event,\r\n                                 float x, float y, int pointer, int button) {\r\n            ScreenManager.getInstance().showScreen( ScreenEnum.LEVEL_SELECT );\r\n            return false;\r\n        }\r\n    });<\/pre>\n<p>&nbsp;<\/p>\n<h2 style=\"text-align: justify;\">C\u00f3digo fuente<\/h2>\n<p style=\"text-align: justify;\">Puedes descargar el c\u00f3digo fuente en mi repositorio de <a title=\"Gdx-Scene-Management en GitHub\" href=\"https:\/\/github.com\/angelnavarro\/Gdx-Scene-Management\" target=\"_blank\">GitHub<\/a>. Al ejecutar el programa, podremos navegar entre las tres pantallas del juego:<\/p>\n<p><a href=\"http:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/Captura-de-pantalla-2014-11-10-a-las-22.21.11.png\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter wp-image-203 size-medium\" src=\"http:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/Captura-de-pantalla-2014-11-10-a-las-22.21.11-300x259.png\" alt=\"Men\u00fa principal\" width=\"300\" height=\"259\" srcset=\"https:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/Captura-de-pantalla-2014-11-10-a-las-22.21.11-300x259.png 300w, https:\/\/www.pixnbgames.com\/blog\/wp-content\/uploads\/2014\/11\/Captura-de-pantalla-2014-11-10-a-las-22.21.11.png 868w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p style=\"text-align: justify;\">Espero que este art\u00edculo te haya servido de ayuda. Suerte en tus proyectos y un saludo.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A la hora de crear videojuegos, es habitual que nuestra aplicaci\u00f3n tenga varias pantallas o escenas.\u00a0A\u00a0continuaci\u00f3n mostrar\u00e9 una\u00a0manera de gestionar las distintas escenas en LibGDX. Para este ejemplo, voy a suponer que el videojuego tiene tres\u00a0pantallas: un men\u00fa principal, una <a class=\"more-link\" href=\"https:\/\/www.pixnbgames.com\/blog\/libgdx\/como-gestionar-varias-pantallas-en-libgdx\/\">Seguir leyendo <span class=\"screen-reader-text\">  C\u00f3mo gestionar varias pantallas en LibGDX<\/span><span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":169,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[14],"tags":[15],"language":[5],"_links":{"self":[{"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/posts\/166"}],"collection":[{"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/comments?post=166"}],"version-history":[{"count":41,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/posts\/166\/revisions"}],"predecessor-version":[{"id":213,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/posts\/166\/revisions\/213"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/media\/169"}],"wp:attachment":[{"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/media?parent=166"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/categories?post=166"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/tags?post=166"},{"taxonomy":"language","embeddable":true,"href":"https:\/\/www.pixnbgames.com\/blog\/wp-json\/wp\/v2\/language?post=166"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}