Tutorial como hacer un Flappy Bird en Love2D - Parte 1

Empezaremos creando un proyecto nuevo en Atom, haremos una carpeta llama "flappyBird" y en Atom iremos a File - Add Project Folder. Seleccionamos la carpeta de nuestro proyecto.

Ahora crearemos el archivo conf.lua y le añadiremos lo siguiente:

function love.conf(t)
  t.window.title = "Flappy Bird - made in Love2D by Deybis Melendez"
  t.window.width = 512
  t.window.height = 288
end




Guardamos el archivo con Ctrl +S, y ahora creamos un archivo nuevo llamado main.lua.

El main.lua:

function love.load():

Comenzaremos con la funcion love.load y añadiremos las variables que utilizaremos para manipular el pajaro, en este caso, el pajaro será representado por un circulo rojo, no utilizaremos imágenes.

Primero añadimos una tabla pajaro = {} esto nos servirá para mantener toda la información del pajaro en un solo lugar, como la posicion, radio del circulo, color, etc.

Luego añadiremos las variables a continuación:

pajaro.radio = 10
pajaro.x = love.graphics.getWidth() / 2 - pajaro.radio
pajaro.y = love.graphics.getHeight() / 2 - pajaro.radio
pajaro.r , pajaro.g , pajaro.b = 255 , 0 , 0 -- Indicamos los colores RGB
pajaro.grav = 20

pajaro.mov = 0
pajaro.salto = 5
pajaro.maxY = love.graphics.getHeight() - pajaro.radio

pajaro.minY = pajaro.radio
end --Indica que la funcion love.load termina ahí.

Explicación:
radio: indicamos el radio del circulo, 10 pixeles de radio.
x,y: calculamos el centro de la pantalla, restando la mitad del ancho y alto respectivamente de la pantalla y lo restamos con el radio.
r, g, b: representa el numero RGB que coloreará el circulo.
grav: el valor de la gravedad que aplicaremos al pajaro.
mov: representa el movimiento en frames del pajaro.
salto: Fuerza que aplicaremos para que el pajaro vuele.
maxY, minY: con esto limitamos a que el pajaro no salga de la pantalla, aplicando una altura maxima (maxY) y una minima (minY)

Nos debe quedar así:


function love.update(dt):

Ahora aplicaremos la lógica del pajaro escribiendo lo siguiente:

  -- Movimiento del pajaro
  pajaro.y = pajaro.y + pajaro.mov -- Indicamos el movimiento en la posicion
  pajaro.mov = pajaro.mov + pajaro.grav * dt -- Aplicamos gravedad al movimiento
  -- Aplicamos limite para que el pajaro no salga de pantalla
  if pajaro.y > pajaro.maxY then -- Si la posicion del pajaro es mayor al limite maximo entonces...
    pajaro.y = pajaro.maxY -- Coloca el pajaro en el limite maximo
    pajaro.mov = 0 -- Su movimiento es 0
  elseif pajaro.y < pajaro.minY then -- además si la posicion del pajaro es menor que el limite minimo entonces...
    pajaro.y = pajaro.minY -- Coloca el pajaro en el limite minimo
    pajaro.mov = 0
  end -- Siempre hay que indicar que el if termina con end.
end


La multiplicación de pajaro.grav por dt es para que el el movimiento se regule en base a la media del fps del juego, y que no dependa de la velocidad de los frames por segundos, que puede variar según la caracteristica del PC.

Nos debe quedar así:


function love.draw():

Ahora que ya tenemos la lógica básica del pajaro, nos hace falta dibujarlo en pantalla, para ello, vamos a escribir:

  -- Dibujamos el pajaro
  love.graphics.setColor(pajaro.r, pajaro.g, pajaro.b)
  love.graphics.circle("fill", pajaro.x, pajaro.y, pajaro.radio)
end


Primero seleccionamos el color que utilizaremos para dibujar el circulo con  love.graphics.setColor() esta función acepta 4 valores, el codigo RGB y un canal alfa. Es decir, Le podemos indicar por separado cuanto R (rojo), G(verde), B (azul), alfa (transparencia) va a tener lo que vamos a dibujar, el canal alfa puede ser omitido ya que por default vale 1. Los codigos RGB los podemos conseguir facilmente en internet.

Segundo indicamos que dibujaremos un circulo con love.graphics.circle() acepta 4 valores, el modo, que puede ser "fill" (rellenar) o "line" (linea), la posicion x, la posicion y, y el radio del circulo.

Nos debe quedar así:


function keypressed(key):

Esta función se ejecuta cuando una tecla es presionada, escribiremos lo siguiente:

-- Controles
function love.keypressed(key)
  if key == "w" then -- Si "key" es igual a "w" entonces...
    pajaro.mov = - pajaro.salto -- Al movimiento del pajaro le restamos el salto.
  end
end


En total el código nos debe quedar así:


Ahora guardamos todo, y si ejecutamos el proyecto, por ahora deberiamos ver el pajaro (circulo rojo) con gravedad y al presionar W pueda hacer saltos en el aire.


Tutorial como hacer un Flappy Bird en Love2D - Parte 2

Continuamos con el tutorial de Flappy Bird, ahora en el love.load() añadiremos el siguiente código:

tubo = {}
tubo.ancho = 20
tubo.alto = love.graphics.getHeight()
tubo.x = love.graphics.getWidth() - 10
tubo.y = love.math.random(- 100 , - tubo.alto) --Nos dará un valor random de -tubo.alto hasta -100
tubo.r , tubo.g , tubo.b = 0 , 255 , 0 -- equivalente al color verde
tubo.limite = - tubo.ancho
tubo.mov = 200 -- Movimiento del tubo en el eje X


Esto nos debe quedar así:



Si nosotros colocaramos tubo.y = 0 se dibujaría un rectangulo verde en vertical que cubriría toda la pantalla, nosotros queremos dejar un margen de 100 pixeles para que el pajaro pueda pasar en medio, es por eso que obtenemos un valor random desde -100 en el eje y, entonces, a partir de esa posición, podemos subir el tubo hasta que la parte baja del tubo quede en el limite, por eso ponemos que nos dé un valor random desde -tubo.alto.

Aquí una gráfica muy mal hecha posiblemente por falta de presupuesto:

Es por eso que es necesario que la altura maxima en el eje y sea -100, para dejar un espacio minimo. Sin embargo, en el juego original hay dos tubos y no uno, bien, vamos ahorrar un poco de código porque utilizaremos el mismo tubo para dibujar dos, unicamente aumentando 100 pixeles al eje Y.

Nos dirigimos directamente a love.draw() y escribiremos lo siguiente:

  -- Dibujamos los tubos
  love.graphics.setColor(tubo.r, tubo.g, tubo.b) -- Seleccionamos el color
  -- Primer tubo
  love.graphics.rectangle("fill", tubo.x, tubo.y, tubo.ancho, tubo.alto)
  -- Segundo tubo
  love.graphics.rectangle("fill", tubo.x, tubo.y + tubo.alto + 100, tubo.ancho, tubo.alto)


Esto nos debe quedar así:



Como podrás ver, usamos la misma tabla tubo para dibujar dos tubos, la diferencia es que al segundo tubo le sumamos el alto del tubo mas 100, esto es para porque el punto de origen del dibujado del rectangulo es en la esquina superior izquierda.

Por último, hay que darle movimiento al tubo, sí, al tubo, no es buena idea hacer que se mueva el pajaro, lo que haremos será simular que el pajaro está volando y en movimiento. Ahora escribiremos lo siguiente en el love.load():

  --Movimiento de los tubos
  tubo.x = tubo.x - tubo.mov * dt
  if tubo.x < tubo.limite then
    tubo.x = love.graphics.getWidth()
    tubo.y = love.math.random(- 100 , - tubo.alto)
  end


Esto nos debería quedar así:


En la primera línea, aplicamos el movimiento, siempre multiplicando por dt para equilibrar el movimiento, luego en la siguiente línea revisamos si el tubo es menor al limite del tubo, una vez cumplida esa condición, colocamos los tubos en la parte derecha de la pantalla, y luego randomizamos una vez mas la posición en Y, para que no reaparezca en la misma posición.

Bien ahora, el juego debería aparecer los tubos.


Tutorial como hacer un Flappy Bird en Love2D - Parte 3

Para finalizar este tutorial, vamos a detectar que el pajaro "choca" con los rectangulos, y añadiremos la puntuación.

En el love.load() añadiremos lo siguiente:

puntuacion = 0
puntoSumado = false


puntoSumado lo utilizaremos para controlar que puntuación no se suma varias veces...

Ahora en el love.update() añadimos lo siguiente:

  if (pajaro.x > tubo.x) and (pajaro.x < tubo.x + tubo.ancho) then
    if ((pajaro.y - pajaro.radio) < (tubo.y + tubo.alto)) or ((pajaro.y + pajaro.radio) > (tubo.y + tubo.alto + 100)) then
      tubo.r , tubo.g , tubo.b = 0 , 0 , 255 -- Color azul
    elseif not puntoSumado then
      puntuacion = puntuacion + 1
      puntoSumado = true
    end
  else
    tubo.r , tubo.g , tubo.b = 0 , 255 , 0
    puntoSumado = false
  end


En la primera linea detectamos si el pajaro está entre los tubos (chocando o no), significa, si la posición del pajaro es mayor a la del tubo y la posicion del pajaro es menor a la del tubo mas su ancho.

En la segunda linea detectamos si el pajaro está en el area del primer rectangulo o en el area del segundo rectangulo, significa, si la posicion del pajaro menos su radio es menor a la posicion del tubo mas su alto o si la posicion del pajaro mas su radio es mayor a la del tubo mas su alto mas 100 pixeles...

Si estas condiciones se cumplen, cambiaremos el color del tubo a azul.

Ahora analizaremos la línea del elseif:

Esta línea en adelante lo que significa es, además si puntoSumado no es verdadero (not invierte el booleano) entonces sumamos puntuacion mas 1 y cambiamos puntoSumado a verdadero,

En el else:
En else colocamos el codigo que se ejecuta si no se cumple ninguna condición, una vez que el pajaro sale de la zona del tubo (la primera condición) pasaremos el color del tubo a verde otra vez, y puntoSumado a false.

Es importante mantener la identación correcta en el proyecto, ese espacio que dejamos con Tab para indicar a qué pertenece cada linea de código. Te dejo el link de Wikipedia sobre Identación: Identación

Por ultimo en love.draw() colocamos el código para imprimir nuestra puntuación:

  -- Imprimimos en pantalla la puntuacion
  love.graphics.setColor(255 , 255 , 0) -- Color amarillo.
  love.graphics.print("Puntuación: " .. puntuacion, 32 , 32)


Y ya tenemos listo nuestro juego básico de Flappy Bird.

Te dejaré el link del proyecto para que puedas descargarlo y verlo en tu PC.

https://github.com/DeybisMelendez/Love2DBlog/tree/master/flappyBird


Como instalar Lua y Love2D en Linux

Instalando Lua

Instalar Lua es fácil, podemos utilizar un comando: sudo apt install lua5.3

Tu primer Hola Mundo en Lua:


Crea con un editor de texto un archivo con nombre main.lua
Escribe print("Hola mundo!")


Guarda el archivo, abrimos la terminal y nos ubicamos en la carpeta donde guardamos el archivo.



Escribimos el comando lua5.3 main.lua


Instalando Love2D

Para instalar Love2D utilizaremos el siguiente comando: 

sudo add-apt-repository ppa:bartbes/love-stable
sudo apt-get update
sudo apt-get install love

Tu primer Hola mundo con Love2D:

Borra lo que escribimos en main.lua o crea un nuevo archivo.
Escribe el siguiente codigo, recuerda que la identación (el espacio vacío al inicio de love.graphics..) se escribe con Tab:

function love.draw()
    love.graphics.print("Hello World", 400, 300)
end



Ahora ejecutamos con el comando:  love /ubicacion/del/archivo
Nota: no debes incluir el main.lua en la dirección ya que love busca el archivo con ese nombre.

Instalar IDE o editor de código para programar en Lua Love2D

El IDE que utilizaremos será el Atom, el link de descarga es: https://atom.io/

Una vez instalado Atom instalaremos el plugin para trabajar con Love2D, seleccionamos Install Packages, luego Open Installer.



Buscamos love-ide y le damos Install.


Aceptamos instalar todas las dependencias.


Con esto ya estamos listos para trabajar con Love2D.

Preparando un proyecto

Ejecutamos Atom, si ya tenemos la carpeta para trabajar el proyecto damos clic en Open Project y seleccionamos la carpeta donde está ubicado el main.lua, siguiendo el ejemplo de el post de instalación de Lua y Lua2D.


Una vez abierto el proyecto, nos aparecerá el main.lua y podemos borrar las pestañas Welcome y demás que nos estorben.


Ahora podemos ejecutar nuestro proyecto usado el boton Run Love App.


Mas que un simple Hola Mundo

Un juego 2D no solo lleva texto, por lo que en este post haremos un Hola Mundo mas adecuado.

Crearemos 2 carpetas, sprite y musica, (El orden en nuestro proyecto siempre es importante)



Dentro de la carpeta sprite colocamos la imagen de arriba (le ponemos nombre "luaImagen.png"), y en la carpeta musica la canción de este link: https://opengameart.org/content/happy-arcade-tune

Nos debe quedar así:


Ahora escribiremos el siguiente código:


Te obligo a escribirlo para que vayas practicando, verás como funciona el autocompletado de Atom con el plugin, luego para correr el código guardamos los cambios con Ctrl + S, y luego damos clic en el botón Run Love App:


Estructura básica de un proyecto en Love2D

main.lua


Todo proyecto de Love2D debe tener en su raíz un main.lua y éste será lo primero que el programa ejecutará siempre.

Funciones Callbacks:

function love.load()

Esta función es llamada una sola vez, cuando se inicia el juego y es por lo general donde se cargan los recursos, se inician las variables y se establece una configuración específica. Todo esto también se puede hacer en cualquier otro sitio, pero haciéndolo aquí significa que se hace una sola vez, ahorrando una gran cantidad de recursos del sistema.

function love.update(dt)

Esta función es llamada constantemente y probablemente será donde se hagan la mayoría de los cálculos. "dt" significa delta time (tiempo delta) y es la cantidad de segundos desde la última vez que se llamó a esta función (que suele ser un valor pequeño como 0,025714)

function love.draw()

Mediante love.draw() se dibuja cualquier cosa en pantalla y si se llama a love.graphics.draw() fuera de esta función, no tendría ningún efecto. Esta función también es llamada constantemente, así que ten en cuenta que si cambias el tipo de letra/color/modo/etc. al final de la función, tendrá un efecto en las cosas del principio de la función en el siguiente frame.

Analicemos el proyecto de ejemplo:


Dentro de nuestra función love.load cargamos como variable "imagen" y "sonido" y luego ejecutamos el sonido con love.audio.play(), si esto lo colocaramos en el update, se reproduciría en cada frame y no se escucharía correctamente.

Nota: "end" indica que la función que hemos "llamado" acaba ahí.

Luego en el love.draw dibujamos los recursos que hemos cargado en la posición que indicamos.

Añadiendo love.update(dt)

Vamos a testear esta función añadiendo lo siguiente:

Declararemos una variable "y = 0" dentro de la función love.load().
Cambiaremos dentro de love.draw() el número 180 del "Hello world" por "y" que es la variable que declaramos en 0.
Añadimos la función love.update(dt) y dentro escribimos "y = y + 1" recuerda escribir al final "end"

Nos debe quedar así:


Guardamos con Ctrl + S y ejecutamos el código, ahora el texto Hello World debería aparecer en movimiento hacia abajo.

Comentarios en Love2D

Añadir comentarios puede ser importante para explicar cómo funciona o qué hace nuestro código, Lua obviará todo texto que esté como comentario.

En Lua la forma de añadir comentarios es la siguiente:

Comentario en una línea:

Solo añadimos "--" (dos guiones) y el resto de la línea no será tomada en cuenta por Lua, ejemplo:


Comentario en varias líneas:

Para usar varias líneas sin utilizar "--" en cada una debemos escribirlo así:

--[[
primer comentario
segundo comentario
]]

Ejemplo:


Variables

Una variable es un dato que almacenamos bajo un nombre. Por ejemplo:

nombre = "Carlos"
apellido = "Hernandez"

Donde nombre y apellido son variables y "Carlos" y "Hernandez" el dato.

Local, Global, Tabla

En Lua se puede declarar 3 tipos de variables, Locales, Globales y Tablas.

Una variable local se limita a usarse dentro de la función donde se declara.
La variable local se declara como: Local variable = dato

Una variable Global se puede recurrir a ella en cualquier función.
La variable Global se declara simplemente como: variable = dato
Esto significa que la variable nombre y apellido en el ejemplo inicial son variables globales.

Una variable Tabla es una variable especial que puede alojar cualquier cosa incluso funciones excepto variables nulas o sin datos.
La variable Tabla se declara como: variable = {contenido}

Declarar mas de una variable

Se puede declarar mas de una variable en la misma línea separando las variables con "," Ejemplo:

nombre ,  apellido = "Carlos" , "Hernandez"

Donde nombre = "Carlos" y apellido = "Hernandez"

Tipos de valores

Lua es un lenguaje de tipado dinámico, eso significa que no tiene tipos de variables, es decir, puedes declarar una variable como un número entero, flotante, cadena de texto, etc. Sin necesidad de declarar el tipo al que pertenece. Sin embargo, sí tiene tipos de valores. Los valores que aceptan las variables en Lua son las siguientes:

nil

Nil expresa que la variable no contiene nada, está vacía, para luego añadirle un valor.

boolean (booleano)

Se utiliza para generar condiciones, devuelve 2 valores, "true" y "false"

número

Acepta cualquier numero flotante, es decir decimales o enteros.

cadena de texto

Se reprensenta dentro de comillas "" y es para almacenar texto.

tabla

Puede almacenar un conjunto de valores, excepto nil.

Operadores

En Lua podemos encontrar 4 tipos de operadores:

Operadores aritméticos.

Operador Descripción
+ Suma 2 valores numéricos
- Resta 2 valores numéricos
* Multiplica 2 valores numéricos
/ Divide 2 valores numéricos
% Devuelve el residuo de una división
^ Eleva exponencialmente un valor numérico
- Si se coloca antes de una variable la multiplica x -1

Operadores Lógicos.

Operator Description Example
and Compara si dos valores devuelven true, de lo contrario devuelve false (A and B)
or Compara si al menos un valor devuelve true, de lo contrario devuelve false. (A or B)
not Invierte el operador lógico, si devuelve true, con not devolverá false. !(A and B)

Operadores de Comparación.

Operador Descripción
==          Compara dos valores, si non iguales devuelve true
~= Compara dos valores, sin son iguales devuelve true
> Es mayor que...
< Es menor que...
>= Es mayor o igual que...
<= Es menor o igual que...

Otros Operadores.


Operator Description Example
.. Concatena o une dos cadenas de texto a..b donde a = "Hello " y b = "World", devolverá "Hello World".
# cuenta la longitud de una cadena de texto (cantidad de letras) o tabla. #"Hello" devolverá 5

Loops

Un loop es un conjunto de código que se ejecuta por una cantidad de tiempo. En Lua hay 4 tipos de Loops:

while

Repite el código dentro de while hasta que la condición sea false. La sintaxis de while es:

while (condición):
do
       código a repetir
end

for

Repite el código la cantidad de veces que le indiquemos. La sintaxis de for es:

for valorInicial , valor max/min , incremento
do
      código a repetir
end

Donde valorInicial es el numero con el que queremos comenzar el loop, valor max/min es el numero maximo o minimo que queremos alcanzar en la repetición, incremento es el numero que incrementaremos valorInicial cada repetición hasta alcanzar el valor max/min.

repeat ... until

A diferencia con while, que revisa la condición al inicio del código, repeat revisa la condición al final, es decir, primero ejecuta el código y luego revisa la condición. La sintaxis de repeat es:

repeat
    código a repetir
until(condición)

Loop aninado

Signfica que Lua acepta usar un loop dentro de otro loop, por ejemplo:

while(condicion)
do
   while(condicion)
   do
      codigo
   end
   codigo
end

Usando conf.lua

conf.lua es un script que se ejecuta antes que main.lua y sirve para indicar la configuración del programa, como el tamaño de la pantalla, el título, indicar la versión de tu programa, ubicación del ícono, etc.

Continuando con el proyecto ejemplo que hemos desarrollado, una vez en nuestro IDE Atom, creamos un archivo nuevo con Ctrl + N o desde File, New File. Luego, daremos Ctrl + S para guardar el proyecto, seleccionamos la carpeta de nuestro proyecto y guardaremos con el nombre conf.lua

Nota: conf.lua siempre debe estar junto al main.lua

Nos debe quedar así:


Ahora escribimos lo siguiente:

function love.conf(t)

end

Así ya estamos listos para configurar nuestro juego.
Con este link puedes ver todos los parámetros que puedes añadir para configurar tu juego: https://love2d.org/wiki/Config_Files

Los más básicos los veremos a continuación:

t.window.title = "Titulo de ventana de tu juego"
t.window.icon = "ubicacion/de/tu/icono.png"
t.window.width = 1280 -- Ancho de la ventana
t.window.height = 720 -- Alto de la ventana

En nuestro proyecto ejemplo quedaría así:

Como hacer un Snake en Love2D

Archivos Comenzamos creando los archivos main.lua y conf.lua, en el conf.lua solo añadimos lo siguiente: function love.conf(t)   t.win...