El tutor de nuestro curso online de Desarrollo de Aplicaciones para Android, Víctor Ramírez Las,…
Proyecto con libGDX: creación del primer videojuego
3.
Proyecto con libGDX: creación del primer videojuego
Vamos a desarrollar un proyecto con el framework libGDX, en el que crearemos un sencillo videojuego 2D, para familiarizarnos con esta librería y las posibilidades que nos ofrece.
El juego consistirá en controlar una nave espacial con la pantalla táctil del dispositivo. El usuario deberá evitar colisionar con las naves enemigas, y conseguir chocar con los objetos de puntuación para aumentar su casillero de puntos. Al final del tutorial puedes descargar todo el código del proyecto.
Actualización: abajo de este contenido tenéis unas indicaciones que habréis de tener en cuenta para realizar el proyecto con Android Studio en vez de Eclipse, y la descarga del proyecto en ambas versiones.
Elementos necesarios:
- Recursos de imágenes para la nave del jugador, la nave enemiga y objeto de puntuación.
- Se deben declarar e inicializar los recursos anteriormente nombrados con la clase Texture.
- Se deben declarar e inicializar tres objetos de tipo Rectangle, uno por cada elemento en pantalla.
- Será necesario declarar e inicializar un objeto de la clase SpriteBatch, para asignar los objetos Texture (imágenes) a los objetos Rectangle definidos (al asignarlos se asocia tanto el tamaño como la posición en pantalla). Recordemos que es necesario trabajar con los objetos Rectangle, aunque lo que visualicemos sean imágenes o texturas.
- Recursos sonoros para la ambientación de las colisiones entre los elementos.
En la siguiente imagen se pueden observar los diferentes elementos que intervienen en el videojuego:
Como se puede apreciar en la imagen, las coordenadas de los ejes X e Y serán siempre de valor positivo. Para los objetos de puntuación jugador y nave enemiga, al posicionarse en un valor negativo del eje X, aparecerán de nuevo al comienzo de la pantalla por la parte derecha.
Reglas del juego:
- Por cada colisión con el objeto de puntuación del jugador, se sumarán dos puntos a su casillero correspondiente.
- Por cada colisión con la nave enemiga, se restará una vida en el contador de vidas restantes.
- Por cada 10 puntos, se aumentará en uno el número de vidas restantes, se aumentará el nivel en uno, y también aumentará la velocidad de la nave enemiga y del objeto para puntuación.
- En caso de superar los cinco niveles definidos en el videojuego, la puntuación final se sumará al número de vidas restantes (por cada vida se adjudican 10 puntos).
- En el supuesto de perder todas las vidas, se mostrará sólo la puntuación final obtenida.
Funcionamiento:
El control de la nave del jugador se realizará a través de la pantalla táctil del dispositivo, que en caso de pulsarla, la nave comenzará a subir (coordenada eje +Y), y al dejar de presionar la pantalla la nave descenderá (la nave del jugador no podrá sobrepasar los límites superior e inferior de la pantalla).
Proyecto JuegoslibGDX:
JuegoslibGDX/src/com.academiaandroid.libgdx/MiJuegoGDX.java
Se declaran e inicializan las variables que representarán, tanto los elementos visuales del videojuego, como los parámetros de velocidad de movimiento de los diferentes objetos en pantalla, además de la música para la ambientación del juego , la puntuación inicial, vidas del jugador y nivel de inicio del juego:
1 2 3 4 5 6 7 8 9 10 |
private int velocidad_nave = 200; private int velocidad_nave = 200; private Rectangle rectangulo_jugador, rectangulo_cpu_enemigo, rectangulo_vida; private float velocidad_enemigo = 300.0f; private float velocidad_vida = 200.0f; public Music coger_vida,golpe_enemigo,fondo_escenario; int vidas = 3; int puntos = 0; int nivel = 0; |
Se inicializan los objetos Rectangle en el método create() , que nos permitirán controlar las colisiones entre los objetos que intervienen en el videojuego. Se asigna el mismo ancho y alto que las imágenes o texturas que van a representarse en esa posición. Se establece los rectángulos en coordenadas y aleatorias. También se preparan los sonidos para su reproducción, en modo bucle con el método setLooping:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/*Método que será invocado una sola vez, al principio de lanzar la aplicación.*/ @Override public void create() { /*Se crean los objetos de la clase Rectangle, indicando el ancho y alto de la figura geométrica rectángulo, además de asignar las coordenadas X e Y del objeto.*/ rectangulo_jugador = new Rectangle(); rectangulo_jugador.width = 128; rectangulo_jugador.height = 64; rectangulo_jugador.x = 150; rectangulo_jugador.y = 600; /*Tanto el objeto cpu_enemigo, como el objeto vida_jugador, 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 pantalla, es decir, no superior a 800 px - alto del rectángulo para que no se oculte el objeto.*/ 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); rectangulo_vida = new Rectangle(); rectangulo_vida.width = 128; rectangulo_vida.height = 64; rectangulo_vida.x = 1280; rectangulo_vida.y = MathUtils.random(0,800 – 64); /* Se inicializan e instancian los objetos de la clase Music, estableciendo los recursos añadidos con la extensión .wav.*/ coger_vida = Gdx.audio.newMusic(Gdx.files.internal("data/vida.wav")); golpe_enemigo = Gdx.audio.newMusic(Gdx.files.internal("data/golpe_enemigo.wav")); fondo_escenario = Gdx.audio.newMusic(Gdx.files.internal("data/fondo_escenario.wav")); /*Se debe establecer el método setLooping() en true para que cuando finalice la pista vuelva a reproducirse. Será la música que nos acompañe durante la partida.*/ fondo_escenario.setLooping(true); /*Se invoca el método play() para que comience a reproducirse al iniciar la aplicación.*/ fondo_escenario.play(); } |
Implementaremos un método llamado controlColisionesYPuntuacion(), que permitirá establecer la lógica de puntuaciones y niveles, dependiendo de con que objeto colisionemos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/*Método que será invocado desde render(), encargado de controlar las diferentes colisiones que se produzcan en el videojuego, estableciendo la lógica de puntuaciones, niveles, vidas restantes y velocidad de los elementos.*/ private void controlColisionesYPuntuacion() { /*Si se colisiona con la nave enemiga.*/ if(rectangulo_jugador.overlaps(rectangulo_cpu_enemigo)) { vidas = vidas - 1; golpe_enemigo.play(); rectangulo_cpu_enemigo.x = 1280; rectangulo_cpu_enemigo.y = MathUtils.random(0,800); } /*Si se colisiona con el elemento de puntuación.*/ if(rectangulo_jugador.overlaps(rectangulo_vida)) { /*Por cada 10 puntos, incrementaremos una vida, subiremos un nivel y aumentará la velocidad de la nave enemiga y del objeto de puntuación.*/ if((puntos == 8)) { vidas = vidas + 1; nivel = nivel + 1; velocidad_enemigo = 500.0f; velocidad_vida = 300.0f; }else if((puntos == 18)) { vidas = vidas + 1; nivel = nivel + 1; velocidad_enemigo = 800.0f; velocidad_vida = 400.0f; }else if((puntos == 28)) { vidas = vidas + 1; nivel = nivel + 1; velocidad_enemigo = 1000.0f; velocidad_vida = 500.0f; }else if((puntos == 38)) { vidas = vidas + 1; nivel = nivel + 1; velocidad_enemigo = 1200.0f; velocidad_vida = 600.0f; }else if((puntos == 48)) { vidas = vidas + 1; nivel = nivel + 1; velocidad_enemigo = 1400.0f; velocidad_vida = 700.0f; } puntos = puntos + 2; coger_vida.play(); rectangulo_vida.x = 1280; rectangulo_vida.y = MathUtils.random(0,800); } } |
También será necesario crear un método que controle cuando la nave enemiga o el objeto puntuación desaparezcan por el lado izquierdo de la pantalla (eje de coordenadas -X). A continuación se muestra, como ejemplo de ambos, la creación de un nuevo enemigo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/*Método que permitirá colocar fuera de la pantalla al elemento enemigo, en una coordenada Y aleatoria, cada vez que se destruya (desaparezca por el lado izquierdo de la pantalla o eje de coordenadas -X) , para simular nuevos enemigos.*/ private void crearEnemigos() { float tiempo = Gdx.graphics.getDeltaTime(); rectangulo_cpu_enemigo.x = rectangulo_cpu_enemigo.x - velocidad_enemigo * tiempo; if(rectangulo_cpu_enemigo.x < -128) { rectangulo_cpu_enemigo.x = 1280; rectangulo_cpu_enemigo.y = MathUtils.random(64, 800 - 64); numero_enemigos = TimeUtils.nanoTime(); } if(rectangulo_cpu_enemigo.y > 800 - 64) { rectangulo_cpu_enemigo.y = 800 - 64; } if(rectangulo_cpu_enemigo.y < 0) { rectangulo_cpu_enemigo.y = 0; } } |
Para finalizar, se declara un método encargado de controlar cuando el usuario pulsa la pantalla y cuando deja de hacerlo. Es conveniente aclarar, que se debe controlar las posiciones máxima y mínima de la nave del jugador en el eje de coordenadas Y, para que esta siempre sea visible:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/*Método que permite controlar la nave del jugador con pulsaciones en la pantalla del dispositivo. Además se implementa la lógica de movimiento que no permita salir la nave del jugador fuera de la pantalla.*/ private void controlesJugador() { boolean tocar_pantalla = Gdx.input.isTouched(); float eje_x_nave = rectangulo_jugador.getX(); float eje_y_nave = rectangulo_jugador.getY(); float tiempo = Gdx.graphics.getDeltaTime(); if(tocar_pantalla) { /*Comprobamos que la nave del jugador no salga por la parte superior de la pantalla.*/ if(rectangulo_jugador.y > 800 - 64) { eje_y_nave = 800 - 64; }else { eje_y_nave = eje_y_nave + velocidad_nave * tiempo; } }else { /*Comprobamos que la nave del jugador no salga por la parte inferior de la pantalla.*/ if(rectangulo_jugador.y < 0) { eje_y_nave = 0; }else { eje_y_nave = eje_y_nave - velocidad_nave * tiempo; } } rectangulo_jugador.set(eje_x_nave, eje_y_nave, 256, 64); } |
Descarga del Proyecto (versión para Eclipse)
DownloadVersión para Android Studio
Os indicamos los cambios que debéis tener en cuenta en el proyecto para realizarlo con Android Studio. Como veréis son pequeños, y en cualquier caso, disponéis abajo del código del proyecto en versión Android Studio para descargarlo.
En este proyecto debeis considerar lo siguiente:
1) Uso de la clase GL20 en lugar de GL10
Simplemente cambiamos la sentencia de importación:
1 |
import com.badlogic.gdx.graphics.GL20; |
susttuye a:
1 |
import com.badlogic.gdx.graphics.GL10; |
2) Las instancias de BitmapFont, al invocar al método setScale(), han sido sustituidas por la estructura getData().setScale():
Por ejemplo:
1 2 |
BitmapFont contador_puntos = new BitmapFont(); contador_puntos.setScale(tamanio_fuente_puntuacion); |
es sustituido por
1 2 |
BitmapFont contador_puntos = new BitmapFont(); contador_puntos.getData().setScale(tamanio_fuente_puntuacion); |
1 |
sprite_fondo = new Sprite(textura_fondo,0,0,1280,800); |
es sustituído por
1 |
sprite_fondo = new Sprite(textura_fondo,0,0, 1920,1080); |
(depende del tamaño de la pantalla, sería más eficiente obtenerlo, pero dejamos esta versión para no introducir más cambios).
Descarga del Proyecto (versión para Android Studio)
DownloadEsta entrada tiene 2 comentarios
Los comentarios están cerrados.
Buenas, muchas gracias por los tutoriales pero podríais subir unos tutoriales sobre la cámara isometrica ?
Muchas gracias por todo
Hola Pablo, estamos diseñando una nueva serie de tutoriales sobre libGDX que profundizarán en el uso de esta librería a través del desarrollo de un videojuego completo. Cuando tengamos definidos esos contenidos en detalle con el autor te podremos decir si se trata el tema de la cámara isométrica.
Gracias por tu interés