Cuando desarrollamos juegos con Java y Slick 2D, una de las primeras decisiones importantes que debemos tomar es cómo estructurar nuestro código para manejar las diferentes pantallas o menús que un juego suele tener. En lugar de usar la clase básica BasicGame, que está pensada para juegos muy sencillos y lineales, es mucho más recomendable utilizar un sistema basado en estados. Este enfoque nos permite organizar el juego en diferentes pantallas o estados, cada uno con su propia lógica y apariencia, facilitando así la gestión y el mantenimiento del código.
Un estado en Slick 2D representa una pantalla o modo específico del juego, como el menú principal, la pantalla de juego, la pausa o las opciones. Cada uno de estos estados es independiente y tiene sus propios métodos para inicializar, actualizar y dibujar su contenido. Esto evita que todo el código se acumule en un único método render, que se volvería caótico y difícil de mantener si intentáramos manejar todas las pantallas desde ahí.
Para implementar un juego basado en estados, debemos cambiar el contenedor de juego que usamos en nuestro proyecto. En lugar de usar un contenedor para BasicGame, Slick 2D nos ofrece la clase StateBasedGame, que está diseñada para gestionar múltiples estados. En esta clase principal, tenemos un método llamado initStateList donde registramos todos los estados que formarán parte del juego. Cada estado es una instancia de una clase que extiende BasicGameState, una implementación que simplifica la creación de estados al proporcionarnos métodos básicos como init, render, update y getID.
El método getID es fundamental porque cada estado debe tener un identificador único, un número entero que Slick utiliza para distinguirlos y para saber cuál debe mostrar en cada momento. Por convención, el estado que arranca el juego suele tener el ID 0.
Veamos un ejemplo básico de cómo definir un estado que dibuja algunos elementos en pantalla. Creamos una clase que extienda BasicGameState y sobreescribimos los métodos necesarios:
import org.newdawn.slick.*;
import org.newdawn.slick.state.*;
public class Juego extends BasicGameState {
private int stateID;
public Juego(int id) {
this.stateID = id;
}
@Override
public void init(GameContainer container, StateBasedGame game) throws SlickException {
// Inicialización del estado, cargar recursos, etc.
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
// Dibujar en pantalla, por ejemplo:
g.drawString("Hola Slick 2D", 100, 100);
g.drawRect(50, 50, 200, 100);
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
// Actualizar lógica del juego
}
@Override
public int getID() {
return stateID;
}
}
Luego, en nuestra clase principal que extiende StateBasedGame, registramos este estado en el método initStateList:
import org.newdawn.slick.*;
import org.newdawn.slick.state.*;
public class Principal extends StateBasedGame {
public Principal() {
super("Juego basado en estados");
}
@Override
public void initStatesList(GameContainer container) throws SlickException {
this.addState(new Juego(0));
// Aquí podríamos añadir más estados, como menú, pausa, etc.
}
public static void main(String[] args) {
try {
AppGameContainer app = new AppGameContainer(new Principal());
app.setDisplayMode(640, 360, false);
app.setShowFPS(false);
app.start();
} catch (SlickException e) {
e.printStackTrace();
}
}
}
Con esta estructura, cuando ejecutemos el juego, Slick iniciará el estado con ID 0, que en este caso es nuestra clase Juego. Si más adelante queremos añadir un menú o una pantalla de pausa, simplemente creamos nuevas clases que extiendan BasicGameState con IDs diferentes y las registramos en initStatesList. Cambiar entre estados es sencillo gracias al método enterState(int id) que nos permite pasar de un estado a otro sin complicaciones.
Otro aspecto fundamental para hacer nuestros juegos más atractivos es el uso de imágenes en lugar de solo figuras geométricas o texto. Slick 2D facilita la carga y el dibujo de imágenes mediante la clase Image. Para usarla, primero importamos la clase y declaramos un atributo para almacenar la imagen:
import org.newdawn.slick.Image;
public class Juego extends BasicGameState {
private Image sprite;
// resto del código...
}
Luego, en el método init, cargamos la imagen desde una carpeta de recursos que debemos incluir dentro de nuestro proyecto, normalmente llamada res:
@Override
public void init(GameContainer container, StateBasedGame game) throws SlickException {
sprite = new Image("res/apple.logo.gif");
}
Es importante que la carpeta res esté dentro del proyecto para que, al exportar el juego como un archivo JAR, las imágenes se incluyan correctamente y puedan ser cargadas en tiempo de ejecución.
Para dibujar la imagen en pantalla, simplemente llamamos al método draw de la instancia de Image dentro del método render:
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
sprite.draw(300, 250);
}
Este método dibuja la imagen en la posición (300, 250) de la ventana del juego. Slick 2D ofrece varias sobrecargas del método draw que nos permiten dibujar la imagen escalada, recortada o con filtros, lo que nos da mucha flexibilidad para manipular gráficos sin complicarnos demasiado.
Aunque también podríamos usar el método drawImage de la clase Graphics, es preferible usar el método draw de la propia imagen, ya que es más completo y nos permite acceder a funcionalidades específicas de las imágenes, como aplicar filtros o escalados, que no están disponibles a través de Graphics.
Con estos conceptos claros, podemos crear juegos más organizados y visualmente atractivos, manteniendo el código modular y fácil de ampliar. Además, Slick 2D cuenta con una documentación muy completa que nos ayudará a explorar más funcionalidades avanzadas tanto de los estados como del manejo de imágenes y otros recursos gráficos.
En próximas sesiones, podremos profundizar en cómo manejar la entrada del usuario, capturando eventos de teclado, ratón o incluso joystick, para hacer nuestros juegos interactivos y dinámicos.