El tutor de nuestro curso online de Desarrollo de Aplicaciones para Android, Víctor Ramírez Las,…
Framework libGDX para programación de videojuegos (II)
2.
Framework libGDX para programación de videojuegos (II)
Seguimos viendo cómo trabajar con el framework libGDX a través del desarrollo de un videojuego en Android.
Texturas, SpriteBatch y Colisiones con libGDX
A continuación, se describirán los pasos para controlar las colisiones entre objetos, y posteriormente se le asignarán Texturas para mostrar un aspecto visual más acorde a un videojuego (personaje jugador, enemigos, escenario etc.). De no usar las texturas los objetos tendrían un aspecto más plano.
En cualquier videojuego de acción, suelen presentarse numerosos elementos en pantalla, moviéndose todos a la vez. Las interacción entre ellos y los movimientos de los mismos controlados por el usuario determinarán el desarrollo del juego
Los pasos necesarios para implementar colisiones serán los siguientes:
Declaramos un objeto Rectangle por cada elemento que se muestre por pantalla. En este ejemplo realizaremos un objeto para manejar el jugador, y otro un enemigo con el que controlaremos la colisión:
1 2 |
private Rectangle rectangulo_jugador; private Rectangle rectangulo_cpu_enemigo; |
Además se declaran el objeto SpriteBatch (clase que nos permite dibujar rectángulos 2D y referenciarlos con texturas definidas) y el objeto Texture por cada elemento a representar:
1 2 |
private Texture textura_jugador, textura_enemigo; private SpriteBatch batch; |
Posteriormente, dentro del método create() (método que sólo se llama cuando se inicia la aplicación) se inicializan los distintos objetos declarados, con las posiciones y tamaños así como las texturas que usaremos. Destacar que las coordenadas iniciales del enemigo están fuera de la pantalla inicialmente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/*Método que será invocado una sola vez, al principio de lanzar la aplicación.*/ @Override public void create() { /*El objeto rectangulo_cpu_enemigo, se posicionan fuera del ancho de la pantalla (coordenada X) al iniciar la partida, y se establece una posición aleatoria en la coordenada Y, siempre dentro del rango máximo de altura de la plantalla, es decir, no superior a 800 px - alto del rectángulo para que no se oculte el objeto.*/ rectangulo_jugador = new Rectangle(); rectangulo_jugador.width = 128; rectangulo_jugador.height = 64; rectangulo_jugador.x = 150; rectangulo_jugador.y = 600; rectangulo_cpu_enemigo = new Rectangle(); rectangulo_cpu_enemigo.width = 128; rectangulo_cpu_enemigo.height = 64; rectangulo_cpu_enemigo.x = 1280; rectangulo_cpu_enemigo.y = MathUtils.random(0,800 – 64); /*Se cargan las texturas con los recursos añadidos, dentro de la carpeta "assets/data", del proyecto a compilar.*/ textura_jugador = new Texture(Gdx.files.internal("data/nave.png")); textura_enemigo = new Texture(Gdx.files.internal("data/enemigo.png")); } |
Dentro del método render() se establece la lógica de colisiones entre rectángulos, (actualizando la física de nuestro juego) y se realiza el borrado de la anterior escena y el pintado de la nueva usando el objeto batch. Sobre el tema de las colisiones conveniente comprender que aunque visualmente veamos texturas o imágenes, las colisiones se establecen con los objetos de la clase Rectangle definidos, es decir en caso de usar una textura que tenga transparencias, estos puntos que no son visibles pero se incluyen dentro del rectángulo se tomarán como parte del objeto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/*Método que será invocado por cada frame que se dibuje en pantalla, mostrando los elementos definidos. Se encargará de dibujar los elementos en pantalla. Mediante el objeto batch, referenciamos las texturas cargadas con los objetos de la clase Rectangle definidos.*/ @Override public void render() { Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(textura_jugador, rectangulo_jugador.x, rectangulo_jugador.y); batch.draw(textura_enemigo, rectangulo_cpu_enemigo.x, rectangulo_cpu_enemigo.y); if(rectangulo_jugador.overlaps(rectangulo_cpu_enemigo)) { /*En este punto de implementa lo que ocurriría si colisionamos con el objeto enemigo, desde terminar la partida o descontar una vida al jugador*/ } batch.end(); } |
Estructura de libGDX: métodos y estados
En este punto, se establece la lógica de funcionamiento del framework libGDX, describiendo los métodos que sobrescribiremos de la interfaz com.badlogic.gdx.ApplicationListener, y mostrando un diagrama con los diferentes estados de la aplicación.
Los siguientes son los principales métodos que se llaman durante el ciclo de vida de la aplicación:
- create() : se lanza cuando se crea la aplicación, y en él se inicializan los objeto necesarios para nuestro juego.
- resize() : se ejecuta justo después del método create(), al cambiar de tamaño la pantalla sin estar la aplicación en pausa. También se llama si cambiara el tamaño de la pantalla (por ejemplo en un giro del terminal).
- render() : es lanzado por el Game Loop por cada renderización. En el se definen los objetos que deseamos pintar en pantalla.
- pause() : es lanzado justo antes de que se destruya la aplicación. En la plataforma Android, esto ocurre cuando recibes una llamada o se pulsa el botón de Home. Este método es muy útil para guardar datos de nuestra aplicación antes de cerrarse, ya que es más que probable que no pueda reanudarse.
- resume() : este método sólo está disponible para proyectos destinados a dispositivos Android. Se ejecuta cuando la aplicación recibe el foco.
- dispose() : este método es lanzado cuando se destruye la aplicación, anteriormente se ha invocado al método pause().
Estos serían lo métodos que sobreescribiremos en nuestra aplicación, conteniendo cada uno de ellos las distintas tareas que se han de realizar en cada momento (por ejemplo activar la reproducción de la música, re-escalar la visualización, detener la ejecución,…) de un modo similar a como se haría en una aplicación Android.:
Podemos ver a continuación el ciclo de estados de una aplicación libGDX, estableciendo como vamos pasando entre los distintos estados:
Componentes comunes al resto de plataformas con libGDX
Una de las grandes virtudes del framework libGDX es el permitir compartir recursos entre proyectos para varias plataformas.
Todo proyecto libGDX estará formado por un conjunto de componentes que serán comunes a todas las plataformas y una parte propia para cada proyecto en la plataforma correspondiente.
Para ello se define la siguiente estructura, que estaría formada por cuatro módulos, controlados por un marco de Aplicación, encargado de manejar el bucle principal y el ciclo de vida, o lo que es lo mismo, los eventos de creación, destrucción, pausa y cierre de la misma.
- Componente de E/S (archivos): posibilita la gestión de lectura y escritura de ficheros de datos, como pueden ser imágenes, texturas, datos de configuración etc.
- Componente de audio: proporciona el acceso a los sonidos y música de la aplicación.
- Componente de entradas (input): será el encargado de gestionar la entradas, que en el caso de la plataforma Android, serían la pantalla táctil, el teclado y el acelerómetro.
- Componente de gráficos: permitirá gestionar la representación de imágenes y objetos gráficos en la pantalla del dispositivo.
En la siguiente publicación aplicaremos lo visto hasta ahora para crear nuestro videojuego, que consistirá en una nave espacial cuyo movimiento controlaremos a través de pulsaciones en la pantalla táctil, evitando colisionar con las naves enemigas a la vez que tratamos de chocar con objetos que nos permiten aumentar puntuación en nuestro casillero de puntos.