En el desarrollo de videojuegos con libGDX, una estructura fundamental para organizar nuestro código es el uso de juegos multipantalla. Esto nos permite dividir el juego en diferentes pantallas, como el menú principal, opciones, juego en sí, pausa, entre otras. Para implementarlo, en lugar de extender directamente ApplicationListener o ApplicationAdapter, extendemos la clase Game, que ya implementa ApplicationListener y añade un método muy útil llamado setScreen. Este método nos permite cambiar entre diferentes pantallas, que son clases que implementan la interfaz Screen.
Cada pantalla tiene métodos similares a los de ApplicationListener, pero con algunas diferencias. Por ejemplo, el método render recibe un parámetro delta que indica el tiempo transcurrido desde el último fotograma, lo que facilita el manejo del tiempo en animaciones y actualizaciones. Además, en lugar de create, tenemos show, que se ejecuta cuando la pantalla se muestra, y hide, que se ejecuta cuando la pantalla se oculta. Esto nos permite cargar recursos o iniciar sonidos en show, y liberar recursos o detener sonidos en hide.
Para mantener el código organizado, es recomendable crear una clase base abstracta para las pantallas que reciba una referencia al juego principal. De esta forma, cada pantalla puede acceder a recursos comunes, como el SpriteBatch, sin necesidad de crear uno nuevo para cada pantalla, lo que mejora la eficiencia.
En cuanto a las animaciones, estas se componen de una serie de fotogramas que se muestran uno tras otro para dar la sensación de movimiento. Por ejemplo, podemos tener una imagen que contiene nueve fotogramas alineados horizontalmente, donde cada fotograma representa una posición diferente de un personaje caminando. Para manejar estas animaciones en libGDX, primero cargamos la imagen completa como una textura.
Luego, convertimos esta textura en una TextureRegion, que representa una porción de la textura. Para dividir la imagen en fotogramas individuales, utilizamos el método split de TextureRegion, que recibe el ancho y alto de cada fotograma y devuelve un array bidimensional de TextureRegion con cada subimagen. En nuestro caso, si la imagen mide 252 píxeles de ancho y 49 de alto, y tiene 9 fotogramas, cada fotograma tendrá un ancho de 252 dividido entre 9, es decir, 28 píxeles, y un alto de 49 píxeles.
Este array bidimensional se aplana en un array unidimensional para crear un objeto Animation. La animación se construye pasando la duración de cada fotograma (por ejemplo, 0.2 segundos) y el array de fotogramas. Para mostrar la animación, mantenemos una variable que acumula el tiempo transcurrido y usamos el método getKeyFrame de la animación, que nos devuelve el fotograma correspondiente al tiempo actual. Podemos indicar si queremos que la animación se repita pasando un parámetro booleano.
El código para dividir la textura y crear la animación sería algo así:
private static final int FRAME_COLS = 9;
private static final int FRAME_ROWS = 1;
Texture walkSheet;
TextureRegion[] walkFrames;
Animation<TextureRegion> walkAnimation;
float stateTime;
public void show() {
walkSheet = new Texture(Gdx.files.internal("do_the_walking.png"));
TextureRegion[][] tmp = TextureRegion.split(walkSheet, walkSheet.getWidth() / FRAME_COLS, walkSheet.getHeight() / FRAME_ROWS);
walkFrames = new TextureRegion[FRAME_COLS * FRAME_ROWS];
int index = 0;
for (int i = 0; i < FRAME_ROWS; i++) {
for (int j = 0; j < FRAME_COLS; j++) {
walkFrames[index++] = tmp[i][j];
}
}
walkAnimation = new Animation<TextureRegion>(0.2f, walkFrames);
stateTime = 0f;
}
public void render(float delta) {
stateTime += delta;
TextureRegion currentFrame = walkAnimation.getKeyFrame(stateTime, true);
batch.begin();
batch.draw(currentFrame, 50, 50);
batch.end();
}
Además de las animaciones básicas, libGDX ofrece la posibilidad de optimizar la gestión de texturas mediante el uso de atlas de texturas. Un atlas es una imagen grande que contiene varias imágenes pequeñas, lo que reduce la cantidad de archivos y mejora el rendimiento al minimizar los cambios de textura en la GPU.
Para crear un atlas, podemos usar la herramienta Texture Packer que viene con libGDX. Esta herramienta toma una carpeta con imágenes individuales y las combina en una sola imagen grande (.png), además de generar un archivo .atlas que contiene la información de las posiciones y tamaños de cada subimagen dentro del atlas.
El proceso para usar Texture Packer es el siguiente: descargamos la versión nightly de libGDX para obtener la herramienta, extraemos los archivos necesarios (gdx.jar y la carpeta extensions/gdx-tools), y organizamos nuestras imágenes en una carpeta de entrada. Luego, desde la terminal, ejecutamos un comando Java que invoca la clase TexturePacker con los parámetros de entrada, salida y nombre del atlas. Esto genera el archivo .png y el .atlas en la carpeta de salida.
Una vez generado el atlas, lo copiamos a la carpeta assets de nuestro proyecto y lo cargamos en libGDX con:
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("atlas.atlas"));
Para obtener una imagen específica del atlas, usamos:
TextureRegion region = atlas.findRegion("nombre_de_la_imagen");
Donde "nombre_de_la_imagen" corresponde al nombre del archivo original sin extensión. Esta región puede usarse igual que cualquier TextureRegion para dibujar o crear animaciones.
Este enfoque nos permite manejar múltiples imágenes de forma eficiente y organizada, facilitando la creación de animaciones y la gestión de recursos gráficos en nuestros juegos con libGDX.