Creación de un videojuego. Flappy Bird
En esta lección, aplicaremos todo lo aprendido para crear nuestro primer juego, y nada mejor que empezar con uno de los juegos con más éxito de la última década: Flappy Bird.
La dinámica del juego de Flappy Bird es sencilla: Mover a nuestro personaje verticalmente para esquivar unos obstáculos que aparecen de forma aleatoria en el escenario. A medida que el personaje avance, se irá incrementando su puntuación, y perderá en caso de chocar con algún obstáculo, tal y como podemos ver en la Figura 1:
Figura 1. Juego original Flappy Bird.
En esta lección, desarrollaremos nuestra propia versión del juego, donde trabajaremos los siguientes conceptos:
- Desarrollo de Sprites animados y escenario
- Desarrollo de pantallas del juego
- Generación de obstáculo de forma procedural
- Efectos de sonido y puntuación
- Implementación de la lógica del juego
- Gestión de eventos y controles
DESARROLLO DE SPRITES ANIMADOS Y ESCENARIOS
El primer paso a la hora desarrollar nuestro juego, es disponer de todos los elementos que serán mostrados en la interfaz, es decir, tanto el personaje principal como los obstáculo y escenarios.
Comenzaremos con el fondo de pantalla del juego. En este punto disponemos de dos opciones:
1) UTILIZACIÓN DE LOS FONDOS PREDETERMINADOS DE SCRATCH
Tal y como se ha descrito en lecciones anteriores, Scratch dispone de una amplia galería de imágenes predefinidas que podemos utilizar en nuestros proyectos.
Hay que distinguir entre dos grandes grupos:
Personajes y obstáculos: Se superpone con el fondo de pantalla y son elementos interactuarles directamente por el jugador.
Fondos de pantalla: Son imágenes, generalmente, estática, con las que el jugador no interacciona. No obstante, muchos juegos optan por agregar animaciones y efectos al fondo con el fin de mejorar la experiencia de juego.
Entre esos efectos, podemos destacar el llamado “scroll parallax”, el cual se utiliza para simular el movimiento relativo de los personajes, a partir del desplazamiento del fondo de pantalla. Como se puede observar en la Figura 2, existe una sensación de movimiento de la nave a pesar de que esta en realidad permanece estática en el mismo punto de la pantalla, siendo el fondo el que se mueve.
Figura 2. Efecto scroll parallax.
En este punto, comenzaremos agregando uno de los fondos de pantalla predefinidos en Scratch, tal y como se muestra en la Figura 3.
Figura 3. Fondo de pantalla predeterminado.
Aunque las imágenes proporcionadas por Scratch son predeterminadas, esto no impide que podamos modificarlas para que se ajusten a nuestras necesidades.
Tal y como se muestra en la Figura 4, podemos mover, desplazar y redimensionar cualquier elemento, incluso duplicarlos y cambiar su color.
Figura 4. Edición fondos predeterminados.
2) IMPORTANDO DESDE UN FICHERO EXTERNO
Scratch también permite dibujarlos desde su propia interfaz, pero se limita al dibujo de formas poligonales simples, por lo que no es la mejor opción si queremos crear un juego con un acabado profesional.
En un juego es habitual que existan distintos fondos en función del escenario o nivel en el que se encuentra nuestro personaje, así como fondos específicos para el menú del juego, entre otros.
En este proyecto, vamos a utilizar el mismo fondo para todos los menús y escenarios del juego, pero vamos a personalizarlos un poco.
Como necesitamos definir tanto el fondo de pantalla del juego, como el del menú principal, como uno específico de GAME OVER, duplicaremos el fondo que ya hemos creado y les asignaremos un nombre, tal y como se muestra en la Figura 5.
Figura 5. Duplicar fondos y renombrarlos.
Ahora agregaremos algo de texto para personalizar nuestras pantallas (Figura 6).
Figura 6. Agregar texto a fondos de pantalla.
Ahora que tenemos los fondos preparados, es momento de empezar a agregar el resto de elementos, comenzando con nuestro protagonista. Para ello, vamos a seleccionar uno de los Sprites incluidos, tal y como se muestra en la Figura 7.
Figura 7. Selección del Personaje.
Como hemos visto en lecciones anteriores, el personaje se compone de diferentes imágenes para simular su movimiento. Estas imágenes pueden ser editadas según nuestras necesidades utilizando la propia interfaz de Scratch, tal y como se muestra en la Figura 8, donde podemos, mover, cambiar color y redefinir el contorno de las distintas partes que componen al personaje.
Figura 8. Edición de un Sprite.
Por último, agregaremos el obstáculo del juego: La tubería. Para ello, podemos importar una generarla desde la propia interfaz (Figura 9) o importarla (Figura 10).
Figura 9. Dibujar tubería.
Figura 10. Usar tubería importada.
CONTROLAR A NUESTRO PERSONAJE Y LA LÓGICA DEL JUEGO
Una vez que hemos definido los elementos gráficos que van a componer nuestro juego, es momento de empezar a programarlo.
Como sabemos, el juego se inicia cuando el usuario acciona la bandera verde (Figura 11), pero debemos indicar qué queremos que se ejecute al inicio del juego, dado que, en caso contrario, mostrará en pantalla el último escenario y personaje que hayamos seleccionado.
Figura 11. Ejemplo de inicialización de juegos mediante bandera verde.
En nuestro juego queremos que nada más iniciarse muestre el menú principal que hemos creado antes. Por ahora, nuestro menú no dispondrá de ningún botón, eso lo agregaremos más adelante, por lo que por ahora sólo queremos que comience el juego después de unos segundos.
Debemos recordar que cada elemento de la pantalla (personajes, obstáculos, escenario…) pueden contener su propio código, por lo que en función del elemento que deseamos controlar, deberemos realizar la programación en un bloque u otro.
En este caso, como queremos definir la lógica de las pantallas, pulsaremos sobre uno de los fondos que hemos creado para poder empezar a programarlos.
Para ello, definimos los bloques que se muestran en la Figura 12, donde nada más iniciar, mostramos la pantalla que hemos llamado como “main_menu”, e indicamos que queremos permanecer en dicha pantalla, por un cierto tiempo, por ejemplo 5 segundos.
Una buena práctica a la hora de desarrollar cualquier juego, es controlar el inicio mediante un flat o evento, es decir, mediante una variable de activación cuyo valor cambiará para indicar al resto de elementos del programa que deben iniciarse. Esto se consigue mediante el bloque “broadcast”, al cual agregamos un nombre, que será el nombre del evento de activación. En nuestro caso, vamos a nombrarlo como “Start”.
Figura 12. Bloque de inicio del juego.
Ahora que ya hemos hablado un poco de los eventos, vamos a implementar el evento que se activará cuando perdamos la partida. Para ello, definimos los bloques que se muestran en la Figura 13, donde asignaremos al evento el nombre que deseemos, por ejemplo “Game_over”. Cuando este evento se produzca, queremos que la pantalla del juego cambie y se detenga la ejecución del juego, lo cual hacemos mediante el comando “Stop all”.
Figura 13. Evento Game Over.
Todo este proceso se puede observar en la Figura 14.
Figura 14. Resumen pasos 12 y 13 para programación de lógica de menús.
Ahora vamos a trabajar con nuestro personaje, para ello, pulsaremos sobre su icono. Como el modelo por defecto es muy grande, vamos a cambiar sus dimensiones como se muestra en la Figura 15.
Figura 15. Cambiar escala del personaje.
El personaje debe empezar en unas coordenadas concretas de la pantalla para dar suficiente tiempo al jugador a visualizar los distintos obstáculos. Esto lo podemos hacer, de dos formas: Moviendo el jugador de forma manual (Figura 16A) o mediante código (Figura 16B)
Figura 16. Emplazamiento del personaje. (A) manualmente (B) de forma programática.
Nuestro personaje es un pájaro que debe esquivar los obstáculos a media que vuela, por lo que debemos hacer que aletee sus alas con una cierta velocidad nada más iniciar el juego.
Este efecto se logra intercambiando los sprites del personaje contantemente. Para ello disponemos del componente “next costume”. Dado que aleteo debe ser lo más natural posible, introduciremos un cierto retraso entre cada animación, tal y como se muestra en la Figura 17.
Figura 17. Animación del personaje.
El personaje aleta correctamente, pero por ahora se encuentra flotando en el aire, por lo que vamos a hacer que le afecta la gravedad. Para ello, implementaremos un nuevo bloque (Figura 18) que se ejecutará al momento de recibir el evento “Start” (aunque se puede realizar dentro del bloque que hemos creado en la Figura 16A, pero de esta forma es más intuitivo).
Figura 18. Agregar efecto de la gravedad
El personaje sólo se desplaza verticalmente. Para simular la acción de la gravedad, debemos hacer que nuestro protagonista se desplace verticalmente hacia abajo una cierta cantidad de píxeles, es decir, en el eje y.
Como podemos observar en la Figura 18, para un valor de +10, el personaje sube muy rápido hasta salirse por la ventana. Para solucionarlo, cambiaremos a un valor de -2. Como deseamos que la gravedad actué siempre sobre nuestro personaje, utilizaremos un bucle.
Ya hemos logrado que el personaje caiga por la acción de la gravedad, pero ahora vamos a hacer que cuando el jugador pulse la tecla ESPACIO, ascienda unos ciertos pixeles. Para ello, modificaremos el bloque anterior tal y como se muestra en la Figura 19 para monitorizar constantemente si el jugado pulsa la tecla, en cuyo caso, ascenderemos +5 pixeles, mientras que en caso de no pulsarlo, caeremos por la acción de la gravedad.
Figura 19. Control de vuelo del personaje.
Ya hemos definido los controles básicos del juego, pero no hemos definido qué sucederá si nuestro personaje cae hasta salirse de la pantalla. Al igual que en el juego original, si este suceso ocurre, debemos dar por perdida la partida y detener el juego.
Para implementarlo, definimos un nuevo bloque (aunque podríamos definirlo dentro del bloque anterior), donde comprobaremos constantemente si el personaje seleccionado toca alguno de los bordes de la pantalla y de ser así, haremos desaparecer al personaje y cambiaremos la pantalla por la de Game Over, al tiempo que detendremos al juego, tal y como se muestra en la Figura 20.
Figura 20. Detección de colisión con los límites de la pantalla.
Como podemos comprobar, hemos hecho uso del evento “Game_Over” que creamos antes. Este evento provocará la activación del bloque de código definido en la Figura 13, que se encargará de mostrar la pantalla de Game Over así como detener el juego.
Para hacer las pruebas, es recomendable agregar una pausa de 1 segundo justo entes de la comprobación de si el personaje está tocando o no el obstáculo, dado que en ocasione, el juego sitúa al protagonista fuera de la pantalla haciendo que pierda de forma automática.
GENERACIÓN DE OBSTÁCULOS
Ahora que tenemos definidos todos los elementos básicos del juego, nos queda agregar los obstáculos que el jugador deberá esquivar para poder seguir avanzando. El desafío están en hace que esos obstáculo se generen a diferentes alturas con el fin de introducir mayor dificultada al jugador.
Antes de la generación procedural, debemos programar que los obstáculos aparezcan al inicio del juego en una posición alejada del jugador para evitar que este choque por accidente, pero lo suficientemente cerca como para que aún sea parcialmente visible en la pantalla, con el fin de que el jugador visualice el futuro obstáculo.
Para ello, dentro del Sprite de la tubería, agregamos un bloque que nos permitirá situar nuestro obstáculo en unas coordenadas concretas, el cual queremos que se ejecute desde el mismo momento en el que se inicia el juego.
Figura 21. Generación de obstáculo inicial.
Una única tubería que no se mueve no parece un gran reto para el jugador, por ello, debemos generar una cantidad infinita a distintas alturas, que deberán acercarse al jugador con una cierta velocidad.
Pero las tuberías no sólo nacen de la base del juego, sino que también deben aparecer desde la parte superior con el fin de crear una zona estrecha de paso para el jugador. Al igual que sucede con nuestro personaje, el Sprite del obstáculo posee dos imágenes o disfraces, uno con la tubería arriba y otra con la tubería abajo.
Como cada vez que cree un obstáculo deseo tener ambas, para simplificar la programación, vamos a hacer uso de los Bloque Personalizados. En esencia, es un bloque que implementará funciones más complejas de las que tiene Scratch por defecto, en nuestro caso, creará un obstáculo en la parte inferior y lo clonará pero con el Sprite del obstáculo superior.
En primer lugar vamos a definir el bloque personalizado tal y como se muestra en la Figura 22, donde agregaremos un nombre y definiremos una única variable que llamaremos “distancia” que utilizaremos más adelante.
Figura 22. Creación de bloque personalizado.
Una vez creado el bloque, definimos las instrucciones que se ejecutarán. En primer lugar, mostraremos un obstáculo el cual forzaremos para que se uno de los disfraces, por ejemplo el de la tubería arriba. Tras esto, clonamos el objeto y sobre ese nuevo objeto clonado, asignamos el otro sprite para tener la tubería abajo.
Este bloque debe ejecutarse al momento en el que el jugador pase del menú al juego, por lo que en caso de recibir el evento “Start”, esconderemos el obstáculo y llamaremos al bloque de crear obstáculo que acabamos de crear. Dado que nos interesa que el juego genere constantemente obstáculos, introduciremos este bloque dentro de un bucle infinito, tal y como se muestra en la Figura 23, asignando, provisionalmente, el valor de distancia a 0.
Figura 23. Implementación del bloque personalizado de creación de tubería.
Como podemos observar, al ejecutar el juego vemos que las tuberías están unidad entre sí, lo que impediría que nuestro personaje pasara a través de ellas. Para solucionarlo, modificaremos el código anterior para modificar la altura de cada tubería de forma aleatoria, pero dejando entre ellas un espacio de unos 120pixeles para que nuestro personaje pueda pasar por ellas sin problema.
Para ello, sólo debemos agregar un cambio en la coordenada Y de ambos objetos (el original y el clon), donde el obstáculo que está arriba, pondremos como coordenada Y el valor de distancia aleatoria entre 1 y 120 que pasaremos como parámetro del bloque personalizado. Para asegurar una separación de 120pixeles entre los obstáculo, el segundo bloque presentará una coordenada Y = distancia -120, tal y como se muestra en la Figura 24.
Figura 24. Generación de obstáculos con altura aleatoria.
Antes de probar este cambio, vamos a introducir un retraso de, por ejemplo, 10 segundos en el bucle principal, con el fin de que podamos visualizar el efecto. Si ejecutamos múltiples veces el juego, veremos cómo en cada ejecución, la altura entre los obstáculos es distinta, pero siempre manteniendo esa separación, tal y como se muestra en la Figura 25.
Figura 25. Visualización de cambio en la altura de los obstáculos.
Ahora que los obstáculos están creados y que su altura se genera de forma aleatoria, debemos desplazarlos hasta la posición del jugador con el fin de que este pueda esquivarlos. El problema residen en que los obstáculos son generados de forma procedural, y deberemos gestionar cada uno de ellos de forma individualizada. Por suerte, Scratch se encarga de esta gestión de forma transparente al usuario.
El código a implementar se muestra en la Figura 26, el cual se ejecutará cada vez que se cree una instancia de obstáculo. Como queremos que este se mueva constantemente en el eje X, introducimos el bloque de desplazamiento dentro de un bucle indefinido, en el que moveremos el obstáculo.
Para ajustar la separación entre obstáculos, debemos cambiar el tiempo de pausa entre la creación de cada uno de ellos. Para introducir mayor dificultada, modificaremos este tiempo con un número aleatorio entre dos valores predefinidos.
Figura 26. Desplazamiento de obstáculos.
Pero si ejecutamos el juego, veremos que todos los obstáculos se acumularán en la parte izquierda de la pantalla (Figura 27).
Figura 27. Problema de acumulación de obstáculos.
Para solucionarlo, sólo debemos detectar cuando el obstáculo llegue al margen izquierdo de la pantalla, para hacerlo desaparecer de forma automática. Para ello, implementamos el código de la Figura 28.
Figura 28. Solución a la acumulación de obstáculos en el margen izquierdo de la pantalla.
En este punto, ya podemos definir que en caso de que nuestro personaje colisione con alguno de los obstáculos, se muestre la pantalla de Game Over y se termine la partida. En este punto, es posible que sea necesario ajustar la escala de nuestro personaje o la altura de los obstáculos para evitar que nuestro protagonista choque constantemente.
Para ello, debemos modificar el bloque de colisiones que creamos anteriormente para que también tenga en cuenta el choque con los obstáculos, tal y como se muestra en la Figura 29.
Figura 29. Colisión con obstáculos.
DETALLES FINALES
Ahora ya tenemos un juego completamente operativo, pero podemos agregar nuevas características para mejorar la experiencia del jugador.
Comenzaremos agregando un efecto de sonido cada vez que nuestro personaje supere un obstáculo. Para ello, usaremos uno de los sonidos por defecto que trae Scratch (Figura 30) y los agregaremos a nuestro proyecto.
Figura 30. Selección de un archivo de audio.
Para reproducir el sonido cuando el jugador supera el obstáculo, debemos comprobar si el obstáculo está un poco hacia la izquierda de nuestro personaje, el cual siempre ocupa la misma posición en pantalla, es decir, en las coordenadas X=-110 Y=0. Esto se logra implementando el bloque que se muestra en la Figura 31, donde teniendo en cuenta que el sonido sólo debe reproducirse una vez y que el obstáculo se mueve en saltos de 3pixeles a la izquierda, definiremos un valor sobre el que estemos seguros de que el obstáculo pasará, con el fin de hacer la comparación, por ejemplo un valor de -99.
Figura 31. Reproducir sonido al superar un obstáculo.
Como último detalle, vamos a agregar la puntuación el jugador, la cual se irá incrementando cada vez que vaya superando obstáculos. Para ello, debemos crear una variable que llamaremos “SCORE”, la cual inicializaremos a cero nada más iniciar el juego (Figura 32). Como podemos observar, esta variable se agrega de forma automática en la parte superior izquierda de la pantalla.
Figura 32. Creación e inicialización de variable puntuación.
Esta variable debe incrementarse cada vez que el jugador supere un obstáculo, o dicho de otra manera, cada vez que se reproduzca el sonido definido anteriormente (Figura 33).
Figura 33. Incrementar puntuación al superar obstáculos.
Y con esto hemos terminado nuestro juego al estilo Flappy Birds, donde a media que el jugador avance irá subiendo su puntuación tal y como se muestra en la Figura 34.
Figura 34. Resultado Final del Juego.