SLICK 2D
Slick2D es una librería hecha en JAVA cuyo próposito es facilitar la creación de juegos en 2D. Esta librería envuelve a LWJGL (LightWeight Java Game Library) y nos permite crear estados del juego, manejar eventos, crear y mover partículas.
Slick2D es una librería hecha en JAVA cuyo próposito es facilitar la creación de juegos en 2D. Esta librería envuelve a LWJGL (LightWeight Java Game Library) y nos permite crear estados del juego, manejar eventos, crear y mover partículas.
LWJGL es una solución a los programadores JAVA ya que ofrece acceso a las bibliotecas multiplataforma como OpenGL (Open Graphics Library) y OpenAL (Open Audio Library).
Si necesita más información sobre esto puede dirigirse a sus páginas oficiales:
Instalación en NetBeans.
PASO 1:
Descargar Slick2D desde su página oficial http://slick.ninjacave.com/. Esto nos descargará un archivo .zip que adentro contendrá el slick.jar que necesitamos, es decir, solo la librería con los .class (archivos compilados). Este archivo es el que debemos agregar a nuestro proyecto.
En NetBeans debemos crear un nuevo proyecto y luego hacer clic derecho en libraries y luego clic en Add JAR/Folder.
En NetBeans debemos crear un nuevo proyecto y luego hacer clic derecho en libraries y luego clic en Add JAR/Folder.
PASO 2:
Descargar LWJGL desde http://lwjgl.org/download.php. Una vez descargado, descomprimimos el lwjgl-2.8.5.zip y, buscamos y añadimos el siguiente archivo ...\lwjgl-2.8.5\jar\lwjgl.jar a nuestro proyecto.
PASO 3:
Por último clic derecho en nuestro proyecto, ir a propiedades, ahora ir a la pestaña run y en la caja de texto perteneciente al VM Options escribir lo siguiente si estamos trabajando en Windows:
-Djava.library.path=<ruta del lwjgl>\lwjgl-XXX\native\windows
O en Linux:
-Djava.library.path=<ruta del lwjgl>/lwjgl-XXX/native/linux
-Djava.library.path=<ruta del lwjgl>/lwjgl-XXX/native/linux
Con esto le indicamos a la máquina virtual de Java donde buscar los DLL (Dinamic Link Library). En realidad estamos estableciendo una propiedad, en este caso, la propiedad java.library.path con su valor <URL>\native\windows.
Una manera alternativa de realizar esto es la siguiente, directamente la indicamos en el código como primer línea de nuestro main:
System.setProperty("org.lwjgl.librarypath",new File("<ruta del
lwjgl>\\lwjgl\\native\\windows").getAbsolutePath());
Existen otras propiedades como line.separator, file.separator, etc, las que podemos obtener mediante System.getProperty("line.separator").
Introducción.
Básicamente, un juego siempre consta de 3 partes. Estas partes son:
1) Inicialización.
2) Actualización.
3) Renderizado.
La inicialización se refiere a inicializar todos los datos que utilizaremos en nuestro juego como por ejemplo la inicialización de la posición del personaje.
La actualización actualiza esos datos a través del tiempo según las entradas obtenidas desde el usuario. Como seguro intuyó la actualización se va a ejecutar muchas veces, más precisamente se va a ejecutar repetidamente hasta que el juego finalice. Por ejemplo, actualizar la posición del personaje.
Y por último, tenemos el renderizado, esta parte también va a ejecutarse indefinidamente hasta que el juego finalice pero se va a encargar de dibujar a esos datos, por ejemplo, mostrando la nueva posición del personaje.
Estas 3 partes, se pueden representar con 3 métodos. En general:
// Ejecutarlo una sola vez.
public void init(){
}
// Ejecutarlo muchas veces.
public void update(){
}
// Ejecutarlo muchas veces.
public void render(){
}
Con Slick2D lo anterior se puede realizar de una manera muy sencilla. Creamos una clase que extienda org.newdawn.slick.BasicGame e implemente los 3 métodos. Además, vamos a tener que definir un constructor que llame al constructor de la clase padre pasándole como parámetro un String que indica el título, caso contrario no compilará. Esto es debido a que la clase BasicGame define un sólo constructor y es el siguiente:
public BasicGame(String title) {
this.title = title;
}
Siga leyendo y se aclarará el panorama.
Entonces, hasta el momento, el código para nuestra clase es:
import org.newdawn.slick.BasicGame;
import org.newdawn.slicl.SlickException;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
public class MiJuego extends BasicGame{
public MiJuego() throws SlickException{
super ("Mi primer juego"); // Llamamos a la clase padre pasándole el título.
}
public void init (GameContainer container) throws SlickException{
}
public void update (GameContainer container, int delta) throws SlickException{
}
public void render (GameContainer container, Graphics g) throws SlickException{
}
}
Seguro notó que están los 3 métodos que habíamos hablado antes pero que la firma de ellos es ligeramente distinta a la que habíamos propuesto. De hecho, antes lo habíamos planteado en forma general y resulta que ahora aparecen varios parámetros. A estos los agrega Slick2D para hacernos la programación más fácil.
Por el momento voy a obviar la explicación de estos métodos y de sus parámetros. Llegado el momento los explicaré.
Ahora, vamos a agregar un nuevo concepto: cada juego tiene un contenedor y éste es el encargado, entre muchas otras cosas, de llamar a los 3 métodos. Obviamente Slick2D nos permite crear ese contenedor de manera muy sencilla.
Para crear ese contenedor, Slick2D brinda una clase abstracta denominada org.newdawn.slick.GameContainer. Pero no piense que vamos a tener que hacer su implementación ya que Slick2D nos brinda una clase que hace esa implementación llamada org.newdawn.slick.AppGameContainer.
En fin, recapitulando, nosotros hemos creado una clase que extiende a BasicGame llamada MiJuego, ahora debemos crear nuestro contenedor con la ayuda de la clase AppGameContainer. Entonces lo que vamos a hacer es crear un objeto de la clase AppGameContainer y lo añadiremos como atributo de la clase MiJuego. El siguiente diagrama de clases (obviamente no incluye todos los métodos) puede simplificar la situación:
Como observa de la anterior figura nos falta la relación desde la clase MiJuego hasta la clase AppGameContainer. Esto es, debemos tener un atributo en MiJuego que haga referencia a un objeto de la clase AppGameContainer.
Además, note que la clase AppGameContainer tiene dos constructores, en ambos recibe un objeto de tipo Game. Pero resulta que la clase MiJuego es de tipo Game ya que extiende a BasicGame que a su vez extiende a Game. Por lo tanto, le pasaremos como parámetro al propio objeto de la clase MiJuego. Parece ser un trabalenguas, pero vea el siguiente código y verá que es más fácil de lo que parece:
Nota: los renglones resaltados con gris claro son nuevos con respecto al anterior código.
import org.newdawn.slick.BasicGame;
import org.newdawn.slicl.SlickException;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.AppGameContainer;
public class MiJuego extends BasicGame{
private AppGameContainer contenedor;
public MiJuego() throws SlickException{
super ("Mi primer juego"); // Llamamos a la clase padre pasándole el título.
contenedor = new AppGameContainer(this);
// Establecemos el tamaño de la pantalla.
contenedor.setDisplayMode(800, 600, false);
// Quitamos los FPS de pantalla. Pruebe con true y entenderá.
Contenedor.setShowFPS(false);
// Iniciamos el juego. Luego de esto se ejecuta el método init().
contenedor.start();
// Nunca llega ejecutarse código después de start().
System.out.println("ok");
}
public void init (GameContainer container) throws SlickException{
}
public void update (GameContainer container, int delta) throws SlickException{
}
public void render (GameContainer container, Graphics g) throws SlickException{
}
}
Obviamente, creamos una clase Main tal como:
public class Main{
public static void main(String args[]){
try{
new MiJuego();
}catch(org.newdawn.slick.SlickException e){
}
new MiJuego();
}catch(org.newdawn.slick.SlickException e){
}
}
}
Ejecute este programa y verá una ventana negra con el siguiente título "Mi primer juego".
Tómese un tiempo para comprender lo que hizo y lea el código detenidamente, pues es muy sencillo.
Definición de los 3 métodos.
Init().
Como habíamos dicho el método init() se ejecuta una sola vez y se va a ejecutar luego de llamar al método start() de la clase AppGameContainer.
A este método le ingresa un parámetro y es una instancia de la clase GameContainer que en realidad es nuestro AppGameContainer que tenemos como atributo. Pueden comprobarlo añadiendo el siguiente código en el método init():
if(container == contenedor){
System.out.println("true");
}
O bien, viendo el código de las clases en cuestión. En fin, ya veremos en algún artículo siguiente qué podemos hacer con él.
Por el momento, supongamos que queremos escribir una cadena en nuestra pantalla. Para ello, declararemos dos variables que indicarán las coordenadas en dónde se dibujará dicha cadena. Las coordenada en x y la coordenada en y. Tanto en la librería AWT de JAVA como en Slick2D, la intersección de los ejes es en la esquina superior izquierda. Por lo tanto, el punto (0, 0) se encuentra arriba y a la izquierda de nuestra pantalla. El eje y positivo apunta hacia abajo y el eje x positivo apunta hacia la derecha.
Además de las coordenadas definiremos el String a mostrar. El código es:
import org.newdawn.slick.BasicGame;
import org.newdawn.slicl.SlickException;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.AppGameContainer;
public class MiJuego extends BasicGame{
private AppGameContainer contenedor;
private float x, y;
private String cadenaAMostrar;
public MiJuego() throws SlickException{
super ("Mi primer juego");
contenedor = new AppGameContainer(this);
contenedor.setDisplayMode(800, 600, false);
contenedor.setShowFPS(false);
contenedor.start();
}
public void init (GameContainer container) throws SlickException{
this.x = 30;
this.y = 40;
cadenaAMostrar = "Hola Mundo.";
}
public void update (GameContainer container, int delta) throws SlickException{
}
public void render (GameContainer container, Graphics g) throws SlickException{
}
}
Observe cómo inicializamos las variables en el método init(). Ahora, lo que nos falta es dibujar esa cadena. Para ellos usaremos el método Render().
Render().
A este método le ingresan dos parámetros: el ya comentado GameContainer y un objeto Graphics. Al igual que en el método init(), el objeto de la clase GameContainer es en realidad nuestro AppGameContainer.
Con respecto al objeto de la clase Graphics, como seguro supone, es un objeto para dibujar una figura, una cadena o una imagen en nuestra ventana cuyo color por default es el blanco. Note que la clase que provee JAVA, la java.awt.Graphics no es la misma que provee Slick2D, la cual se cualifica como org.newdawn.slick.Graphics.
Esta última clase provee decenas de métodos para dibujar figuras, entre todos ellos, provee uno que se llama drawString(String cadena, int x, int y) y éste es el que usaremos en nuestro ejemplo.
El código del método render() es:
public void render (GameContainer container, Graphics g) throws SlickException{
g.drawString(this.cadenaAMostrar, this.x, this.y);
}
Bien, si ejecuta ésto verá la cadena escrita. Le podemos cambiar el color a esa cadena llamando al método setColor(Color color) de la clase Graphics. De ésta manera el método nos queda:
public void render (GameContainer container, Graphics g) throws SlickException{
g.setColor(Color.green);
g.drawString(this.cadenaAMostrar, this.x, this.y);
}
De nuevo la clase org.newdawn.slick.Color no es la misma que la clase java.awt.Color aunque su objetivo es el mismo. Una diferencia que puede evitar problemas si no utiliza un IDE, es que la clase org.newdawn.slick.Color no tiene definidas las constantes en mayúsculas tal como Color.GREEN como sí lo tiene la clase del AWT.
Ahora haremos que esa cadena se mueva, es decir, que actualice su posición, para ello usaremos el método update.
Update().
En este método ingresan dos parámetros. Ya explique más arriba el GameContainer. Ahora definiré el parámetro int delta. Éste es un valor que representa tiempo, más precisamente es el tiempo que el contenedor del juego tardó en llamar de nuevo al método update() desde la última vez. El tiempo viene expresado en milisegundos (ms). Olvidemos por un momento este tema del tiempo, luego lo retomaremos.
Si quisiéramos hacer que la cadena se mueva podríamos hacer lo siguiente:
public void update(GameContainer container, int delta) throws SlickException {
x += 5;
if (x > 500) {
x = 30;
}
}
Esto va a hacer que la cadena se mueva hasta máximo 500 px en el eje x y luego vuelva a 30 px. Si ejecutamos ésto, vamos a ver que la cadena se mueve muy rápido. De hecho, lo que está pasando es que el método update() se llama muy rápido, cada 2, 3 ó 4 ms (depende de la computadora e incluso varía de llamada en llamada). Estaría bueno que la cadena se mueva, supongamos, cada 50 ms. Justamente, ésto es lo que vamos a hacer.
Crearemos un atributo tiempo (int) que mantenga la cuenta de cuántos segundos van pasando. Una vez que lleguemos a 50 ms entonces movemos la cadena y volvemos el tiempo a cero. El código completo de la clase es:
import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slicl.SlickException;
public class MiJuego extends BasicGame{
private AppGameContainer contenedor;
private int x, y;
private String cadenaAMostrar;
private int tiempo;
public MiJuego() throws SlickException{
super ("Mi primer juego");
contenedor = new AppGameContainer(this);
contenedor.setDisplayMode(800, 600, false);
contenedor.setShowFPS(false);
contenedor.start();
}
public void init (GameContainer container) throws SlickException{
this.x = 30;
this.y = 40;
cadenaAMostrar = "Hola Mundo.";
}
public void update (GameContainer container, int delta) throws SlickException{
tiempo += delta;
if(tiempo > 50){
tiempo = 0;
x += 5;
if(x > 500){
x = 30;
}
}
}
public void render (GameContainer container, Graphics g) throws SlickException{
g.setColor(Color.green);
g.drawString(cadenaAMostrar, x, y);
}
}
hola hermano, dices que cuando se llama a start() se ejecuta init(), pero en donde llamas a update() y render()?.
ResponderEliminarHola.
ResponderEliminarSe puede decir que es el mismo método start() el que se encarga de llamar al método update() y al método render() repetidamente. O sea, una vez que uno llama al método start(), se ejecuta el init(), y luego en un bucle el update() y render().
Por adentro de la librería, la cuestión es un "poquito" más complicada pero la idea es que cuando uno ejecuta start() entonces se llaman los 3 métodos: init, update y render. El primero una vez y los otros 2 muchas veces.
Espero haberte dejado en claro tu duda.
Suerte