Recrea Flappy Bird con Lua y Love2D
En este artículo crearemos Flat Bird, que es un clon de Flappy Bird, pero con gráficos más simplificados que el original, ya que la atención se centrará en la mecánica del juego.
Para ello, utilizaremos el lenguaje de programación Lua y el framework Love2D (también conocido como LÖVE).
Lua
Lua es un pequeño e interesante lenguaje de programación desarrollado en la universidad brasileña PUC-Rio y cuya razón de existencia es básicamente simplificar la vida de los programadores que dependen de los lenguajes C o C++. Por lo tanto, fue diseñado para integrarse fácilmente en el lenguaje C (también C++).
Si nunca has oído hablar de él, no te preocupes porque la sintaxis es muy sencilla, especialmente para aquellos que ya han programado en Python o JavaScript. ¡Y no necesitas saber C para usarlo!
Pero ten en cuenta que varios juegos famosos como Angry Birds y World of Warcraft lo utilizaron en partes de su desarrollo. Puedes encontrar una lista más completa de dichos títulos aquí.
Love2D
Es un framework para crear juegos 2D usando Lua.
La instalación es muy sencilla. Simplemente descarga el archivo correspondiente a tu plataforma en el site:
Game loop
En el corazón de prácticamente todos los juegos se encuentra el llamado game loop. Es básicamente un loop infinito, donde el juego procesa la entrada (por ejemplo, presionar teclas y tocar la pantalla), actualiza el estado de las entidades del juego (ya sea respondiendo a la entrada o continuando una simulación física) y, finalmente, muestra en la pantalla el resultado.
Y esto se repite varias veces por segundo. Normalmente hay una velocidad constante con unos 30 o 60 fotogramas por segundo.
Con LÖVE no es diferente. Pero, en este caso, usted no es responsable de escribir el loop del juego, sino sólo ciertas devoluciones de llamada que son funciones especiales, que el framework llama en el momento adecuado.
Por ejemplo, un “Hello, world!” consiste simplemente en implementar el callback love.draw:
Considera que no fue necesario escribir código para crear la ventana ni trabajar directamente con API de gráficos como OpenGL o Vulkan, lo que hace la vida mucho más fácil para aquellos que recién comienzan en el campo del desarrollo de juegos.
Sin embargo, todavía es posible tener un control más refinado sobre ciertos aspectos de la aplicación, como veremos más adelante.
Para ejecutar el código anterior, guárdalo en un archivo main.lua, abre una terminal, ve al directorio donde guardaste el archivo y ejecuta love main.lua.
Esto supone que se puede acceder al programa Love en la variable de entorno PATH. Si usaste el instalador, no tendrás ningún problema. Si eliges la versión .zip, deberás agregar manualmente el directorio al PATH.
Flat Bird
Si llevas varios años viviendo en una cueva y no conoces al adictivo y molesto Flappy Bird, puedes jugarlo aquí.
Hechas las presentaciones, pasemos a nuestro juego.
Los elementos centrales son el pájaro, las tuberías y el suelo. El jugador anota al pasar a través de la abertura entre los dos tubos verticales y muere al tocar los tubos o el suelo.
El control consiste simplemente en la acción de hacer que el pájaro se eleve una determinada distancia, luchando contra la gravedad. Se puede implementar con solo presionar una tecla, hacer clic con el mouse o tocar la pantalla, en el caso de plataformas móviles.
Antes de pasar al código del juego, definamos el tamaño de la ventana y su título. Para hacer esto, cree un archivo conf.lua y agregue el siguiente código:
Para obtener más información sobre las configuraciones disponibles, consulta este link.
El código del juego estará en un único archivo main.lua
para evitar tener que trastear con módulos o paquetes.
Para empezar elegimos una resolución para el lienzo, que es donde dibujaremos el juego. Ten en cuenta que no es necesario que esta resolución sea la misma que la ventana o pantalla donde se ejecutará el juego. En este caso, asumo que la ventana y el lienzo tienen la misma resolución para simplificar el código.
Piense en la palabra clave "local" como "let" de JavaScript.
Aquí tienes algunos colores para pintar los elementos del juego. LÖVE supone que los componentes RGBA de un color están en el rango [0, 1].
Una constante que simula la aceleración inducida por la gravedad. Podemos pensar en la unidad como píxeles/segundo.
En LÖVE, el origen del sistema de coordenadas, el punto (0, 0), es el más cercano a la esquina superior izquierda de la pantalla. Y el eje y se invierte en el sentido de que cuando aumentas la coordenada y, el objeto se mueve hacia abajo en la pantalla.
Puede parecer extraño, pero es una convención muy común en bibliotecas y frameworks de desarrollo de juegos.
Pájaro
A continuación tenemos el código responsable del pájaro. Coloca al pájaro en el centro de la pantalla verticalmente y un poco a la izquierda del centro horizontalmente. También se definen velocidades mínimas y máximas, para evitar ser demasiado lento o demasiado rápido. El bump_height
es la altura a la que el pájaro se mueve verticalmente cada vez que el jugador activa el control.
En la función birdUpdate cambiamos la velocidad según la constante gravitacional que definimos arriba y también cambiamos la posición vertical del pájaro según su velocidad. ¿Qué es dt? Bueno, es sólo la forma abreviada de "tiempo delta", que es el tiempo que ha pasado entre los dos últimos fotogramas.
Esto es necesario porque esta función se llamará aproximadamente 60 veces por segundo y como cada cuadro puede tardar un poco más o un poco menos de lo esperado, usar dt
ayuda a suavizar el movimiento independientemente de la máquina del jugador.
Tubos
En Lua sólo existe una estructura de datos, que es la tabla (hash). Es similar al objeto de JavaScript.
Para trabajar con tuberías, usaremos una tabla de tuberías que contiene dos campos (.clock y .gen_rate) que funcionan como un reloj para determinar cuándo se debe crear la siguiente tubería. También usaremos los campos numéricos de la tabla pipes
como estructura de cola.
Puntuación
Aquí tenemos el código del sistema de puntuación. En él almacenamos la puntuación actual y la puntuación máxima (que sólo persiste mientras el juego está abierto). El criterio para aumentar la puntuación es: si el centro del pájaro pasó por el centro de una tubería, cuyo campo behind_bird sigue siendo falso, para evitar contar el mismo punto varias veces.
Piso
Aquí definimos el suelo, o suelo, que es simplemente un rectángulo marrón en la parte inferior de la pantalla.
Funciones auxiliares
Aquí definimos un reloj similar al usado para generar las tuberías, pero con el objetivo de contar el tiempo hasta que el juego se reinicie cuando se acabe el juego (game over).
La función gameReset transforma el estado del juego al estado inicial, modificando sólo los campos necesarios. Nota que scoreboardReset no restablece la puntuación máxima.
La función hasIntersection detecta si hay una intersección entre dos rectángulos, donde xj, yj representa la esquina superior izquierda del rectángulo j y wj, hj representa su ancho y alto, respectivamente.
LÖVE callbacks
Este callback es llamado apenas una vez, durante el inicio del juego, por lo que es un buen lugar para escribir código que cargue recursos como música del juego e imágenes de fondo.
Aquí definimos una semilla para el sistema generador de números aleatorios, inicializamos las entidades del juego y definimos la fuente que se utilizará para todos los textos que se muestran en la pantalla. Elegí utilizar una sola fuente, en este caso la misma que la del marcador.
Aquí movemos el pájaro, las tuberías, comprobamos si hay colisión (lo que determina el final del juego) y, si no hay colisión, actualizamos la puntuación, si corresponde. Tenga en cuenta que si el pájaro está "muerto", no actualizamos el estado de las entidades del juego, sino que mostramos un mensaje de "juego terminado", que dura unos segundos antes de que se reinicie el juego.
Ese es el callback responsable de diseñar en la pantalla (cualquier código como love.graphics.rect solo funciona como se esperaba si se llama dentro de esa función).
Ese callback se llama cuando hay un evento de “tecla presionada”. En este caso, debemos hacer que el pájaro se eleve un poco y vuelva a su velocidad inicial al pulsar la tecla espacio, flecha arriba o W. Pero sólo si el pájaro sigue vivo, ya que el modo zombie no se ha implementado.
Resultado
Al final, el juego queda así:
Para tu conveniencia, el código también está aquí.
Si no te gustó el aspecto minimalista del juego, hay algunos paquetes de arte que sus creadores pusieron a disposición de forma gratuita, como los siguientes:
- https://megacrash.itch.io/flappy-bird-assets (más próximo al juego original).
- https://ansimuz.itch.io/flappy-chicken (Tiene música genial y sprites para crear variaciones del juego, ¡como hacer que el pájaro suelte huevos que explotan!).
En lugar de dibujar rectángulos, ahora es necesario cargar las imágenes del disco a la memoria con la función love.graphics.newImage y para diseñar en la pantalla se usa la función love.graphics.draw.
Las imágenes de estos paquetes tienen una resolución muy baja, algo así como 16x16 y 32x32. Para hacer el juego en una resolución más alta, debes cambiar el tipo de filtro utilizado por LÖVE (el algoritmo utilizado para ampliar o reducir imágenes), usando la siguiente función https://love2d.org/wiki/love.graphics.setDefaultFilter.
Los modos disponibles son “linear” y “nearest”. El patrón es “linear”, entonces, para preservar el aspecto "pixelado" de las imágenes, sin introducir desenfoque, querrás utilizar el modo "nearest".
Para más información sobre Lua y LÖVE, recomiendo los siguientes enlaces:
- https://www.lua.org/manual/5.1/pt/
- https://love2d.org/wiki/love
- https://sheepolution.com/learn/book/contents
¡Gracias y disfruta el juego!
Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.