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.window.title = "Snake - made in Love2D by Deybis Melendez"
  t.window.width = 400
  t.window.height = 400
end


function love.load()

Vamos a comenzar declarando las variables que utilizaremos, cada linea de código tendrá un comentario con su explicación:

  t = 20 -- tamaño de cada celda
  timer = 0 -- Controlaremos el tiempo de ejecución con esta variable
  perder = false -- Para controlar cuando pierde el juego
  ganar = false -- Para controlar cuando gana el juego
  love.graphics.setBackgroundColor(0.6039, 0.7725, 0.0117) -- Colocaos un fondo verde
  area = {} -- Nos ayudará a definir un area de juego
  area.x = t -- La posición del area en la parte superior izquierda
  area.y = t * 2 --La posición del area en la parte inferior izquierda
  area.w = love.graphics.getWidth() - t * 2 -- (Width o Ancho) La posición del area en la parte superior derecha
  area.h = love.graphics.getHeight() - t * 3 -- (Height o Alto) La posición del area en la parte inferior derecha
  snake = {} -- Los datos de la serpiente
  snake.velX = 20 -- Velocidad inicial en X de la serpiente
  snake.velY = 0 -- Velocidad inicial en Y de la serpiente
  snake.cola = {} -- contiene todos los puntos x , y de cada parte de la serpiente
  snake.cola[0] = {x = area.x , y = area.y} -- Cabeza de la serpiente
  comida = {} -- Los datos de la comida
  comida.radio = 8 -- La comida será representada por un circulo, por lo que necesitamos un radio
  comida.offset = t/2 -- Ajuste de la posición del circulo en la celda.
  comida.x = (math.random((area.w - area.x)/t)) * t + area.x --Posición inicial random en el eje X
  comida.y = (math.random((area.h - area.y)/t)) * t + area.y -- Posición inicial random en el eje Y


function love.draw()

Ahora daremos las instrucciones para dibujar el juego:

  love.graphics.setColor(0,0.1,0) -- El color que utilizaremos para dibujar el juego
  if not perder  and not ganar then -- Si no ha perdido o no ha ganado entonces...
    love.graphics.print("Puntuación: " .. #snake.cola, 16, 16) -- Imprimimos la Puntuación en la posición 16,16
  elseif not perder and ganar then -- Si no ha perdido pero ha ganado entonces...
    love.graphics.print("Ganaste! presiona R para reiniciar :D" , 16 , 16) -- Imprimimos que ganamos en la posición 16,16
  else -- Si no se cumplen las condiciones entonces
    love.graphics.print("Perdiste! Tu Puntuación es: " .. #snake.cola .. ", presiona R para reiniciar", 16 , 16) -- Imprimimos que perdiste en la posición 16,16
  end
  for i = 0, #snake.cola do -- Obtenemos cada contenido de snake.cola, es decir, cada x,y (cada parte de la serpiente)
    -- A continuación por cada Posición en x , y que tiene snake.cola, dibujaremos 4 cuadrados para simular el estilo retro del Nokia.
    love.graphics.rectangle("fill", snake.cola[i].x, snake.cola[i].y,t/2 - 1 , t/2 - 1)
    love.graphics.rectangle("fill", snake.cola[i].x + t/2, snake.cola[i].y, t/2 - 1, t/2 - 1)
    love.graphics.rectangle("fill", snake.cola[i].x, snake.cola[i].y + t/2, t/2 - 1, t/2 - 1)
    love.graphics.rectangle("fill", snake.cola[i].x + t/2, snake.cola[i].y + t/2, t/2 - 1, t/2 - 1)
  end
  love.graphics.rectangle("line", area.x, area.y, area.w , area.h ) --Dibujamos la linea que representará el area del juego
  love.graphics.circle("line", comida.x + comida.offset, comida.y + comida.offset, comida.radio) -- Dibujamos el circulo que representa la comida
end



function love.update(dt)

Primero, para ordenar un poco el código y se entienda, vamos a crear unas funciones, esto fuera del love.update:

function comer()

if snake.cola[0].x == comida.x and snake.cola[0].y == comida.y then -- Si la serpiente está encima de la comida entonces...
    snake.cola[#snake.cola + 1] = {x = snake.cola[#snake.cola].x - snake.velX , y  = snake.cola[#snake.cola].y - snake.velY} -- Añadimos una cola a la serpiente.
    if #snake.cola < 361 then -- Si la serpiente no tiene ocupado todo el area del juego, area es 19x19
      while contains(snake.cola , comida, 0) do -- Repetimos el siguiente codigo si la comida aparece encima de la serpiente
        -- damos una nueva posición random hasta que la comida esté fuera de la serpiente.
        comida.x = (math.random((area.w - area.x)/t)) * t + area.x
        comida.y = (math.random((area.h - area.y)/t)) * t + area.y
      end
    else -- Si la serpiente ocupa todo el area entonces...
      ganar = true -- Has ganado.
    end
  end
end



function mover()

  -- El sguiente for significa: Para i = cantidad de partes de la serpiente , repetimos el codigo hasta llegar que i = 0, restando -1 a i en cada repetición.
  for i = #snake.cola , 0 , -1 do -- Obtenemos cada parte de la serpiente, comenzando desde la cola hasta la cabeza, (en orden descendente, ej: 5,4,3,2,1,0)
    if i == 0 then -- snake.cola[0] es la cabeza de la serpiente, por lo que con ella controlamos el movimiento.
      snake.cola[0].x = snake.cola[0].x + snake.velX
      snake.cola[0].y = snake.cola[0].y + snake.velY
    else -- Para las demás hacemos que cada parte de la cola sea igual a la vecina anterior (i-1)
      snake.cola[i].x = snake.cola[i-1].x
      snake.cola[i].y = snake.cola[i-1].y
    end
  end
end



function contains(tabla,val,num)

-- Debido a que en Lua no existe un has() para saber si alguna variable existe en una tabla,
-- He conseguido esta función en internet que hace lo mismo, con la diferencia que he añadido una variable num
-- Que utilizaremos en 2 variantes en nuestro juego.
function contains(tabla, val, num)
   for i=num,#tabla do
      if tabla[i].x == val.x and tabla[i].y == val.y then
         return true
      end
   end
   return false
end





Ahora sí, añadimos el love.update(dt):

function love.update(dt)

  if timer < 0.1 then -- Con esto controlaremos cada actualización de pantalla a 0.1 segundos
    timer = timer + dt
  elseif not perder and not ganar then -- El juego no continuará si has perdido o ganado
    timer = 0 -- Indica que el contador del tiempo de cada frame comienza
    comer()
    mover()
    if contains(snake.cola , snake.cola[0] , 1) then -- Revisamos si snake.cola tiene un valor igual a la cabeza(snake.cola[0]), añadimos 1 para que no cuente la cabeza.
      perder = true -- Si ha colisionado entonces ha perdido
    end
    -- Aqui controlamos que la serpiente se mantenga dentro del area, reapareciendo en el lado opuesto.
    if snake.cola[0].x > area.w then
      snake.cola[0].x = area.x
    elseif snake.cola[0].x < area.x then
      snake.cola[0].x = area.w
    elseif snake.cola[0].y > area.h + t then
      snake.cola[0].y = area.y
    elseif snake.cola[0].y < area.y then
      snake.cola[0].y = area.h + t
    end
  end
end



function love.keypressed()

Por último, añadimos la función del teclado y como manipulamos el juego:

function love.keypressed(key, scancode, isrepeat)
  if key == "up" and snake.velY ~= t then
    snake.velX = 0
    snake.velY = - t
  elseif key == "down" and snake.velY ~= -t then
    snake.velX = 0
    snake.velY = t
  elseif key == "left" and snake.velX ~= t then
    snake.velX = - t
    snake.velY = 0
  elseif key == "right" and snake.velX ~= -t then
    snake.velX = t
    snake.velY = 0
  elseif key == "r" and (perder or ganar) then -- Solo ejecuta si ha perdido o ganado.
    snake.cola[0] = {x = area.x , y = area.y} -- Colocamos la cabeza de la serpiente en el lugar inicial
    for i = 1 , #snake.cola do -- Borramos todo el contenido de snake.cola menos la cabeza (snake.cola[0])
      snake.cola[i] = nil
    end
    -- Reiniciamos las variables del juego a su valor inicial...
    snake.velX = t
    snake.velY = 0
    comida.x = (math.random((area.w - area.x)/t)) * t + area.x
    comida.y = (math.random((area.h - area.y)/t)) * t + area.y
    perder = false
    ganar = false
  end
end





Y listo, ya tenemos un juego básico de snake al puro estilo de los antiguos celulares Nokias,  a continuación un pequeño gameplay del juego:

 

Tutorial como hacer un Pong en Love2D


Assets:

Primero descargaremos los siguientes assets para este proyecto

https://opengameart.org/content/pong-programmer-art

https://opengameart.org/content/pong-sfx

https://www.dafont.com/es/squarefont.font

Creamos una carpeta que nombraremos "pong" y unas sub-carpetas llamadas "sprites" , "sonidos" , "fuentes" y añadimos cada archivo en su respectiva carpeta.

conf.lua

Crearemos un archivo y lo llamaremos conf.lua, en él añadiremos el siguiente código para configurar el proyecto:

function love.conf(t)
  t.window.title = "Pong - made in Love2D by Deybis Melendez"
  t.window.width = 800
  t.window.height = 600
end

main.lua

love.load()

En el love.load vamos a declarar las variables para la bola, la barra izquierda y barra derecha, la imagen de background y los sonidos que utilizaremos:

function love.load()
  bola = {}
  bola.velocidad = 300
  bola.movX, bola.movY = -bola.velocidad , bola.velocidad
  bola.sprite = love.graphics.newImage("sprites/fancy-ball.png") -- El sprite mide 32x32
  bola.x , bola.y = love.graphics.getWidth()/2 - 16 , love.graphics.getHeight()/2 - 16

  barra1 = {}
  barra1.sprite = love.graphics.newImage("sprites/fancy-paddle-blue.png")
  barra1.x , barra1.y = 32, love.graphics.getHeight()/2 - 64 -- La barra mide 32x128

  barra2 = {}
  barra2.sprite = love.graphics.newImage("sprites/fancy-paddle-green.png")
  barra2.x , barra2.y = love.graphics.getWidth() - 64 , love.graphics.getHeight()/2 - 64

  background = love.graphics.newImage("sprites/fancy-court.png")

  fuente = love.graphics.newFont("fuentes/Square.ttf", 32)

  pop = love.audio.newSource("sonidos/Pop.ogg", "static")
  score = love.audio.newSource("sonidos/Score.ogg", "static")
  velocidadBarra = 300
  puntaje1 , puntaje2 = 0 , 0
end

love.draw()

Ahora dibujaremos todo el juego en pantalla:

function love.draw()
  love.graphics.draw(background, 0, 0)
  love.graphics.draw(bola.sprite, bola.x, bola.y)
  love.graphics.draw(barra1.sprite, barra1.x, barra1.y)
  love.graphics.draw(barra2.sprite, barra2.x, barra2.y)
  love.graphics.setFont(fuente)
  love.graphics.print(puntaje1, love.graphics.getWidth() * 1/3 , 32)
  love.graphics.print(puntaje2, love.graphics.getWidth() * 2/3 , 32)
end


Funciones


Vamos a añadir 2 funciones para no repetir el código:

function clamp(min, val, max) -- Nos retorna un rango de valor, desde un mínimo a un máximo
  return math.max(min, math.min(val, max))
end


function reiniciar()  -- Reiniciamos la partida si la bola sale de pantalla
  bola.x , bola. y = love.graphics.getWidth()/2 - 16 , love.graphics.getHeight()/2 - 16
  bola.movX = - bola.movX
  bola.movY = - bola.movY
  play(score)
end

function play(sonido)  -- Un simple control de sonido.
  if sonido:isPlaying() then
    love.audio.stop()
    love.audio.play(sonido)
  else
    love.audio.play(sonido)
  end


love.update(dt)

En el update, añadiremos el movimiento de la bola:

  --Movimiento de la bola
  bola.x = bola.x + bola.movX * dt
  bola.y = bola.y + bola.movY * dt


Luego añadimos el código para el rebote desde arriba y abajo:

  -- Rebote de la bola desde arriba hacia abajo
  if bola.y < 0 then
    bola.movY = bola.velocidad
    play(pop)
  elseif bola.y > love.graphics.getHeight() - 32 then
    bola.movY = - bola.velocidad
    play(pop)
  end


Ahora el en caso de que la bola salga de la pantalla por los lados:

  -- Si la bola sale de pantalla reinicia su posición y da puntajes
  if bola.x < -16 then
    reiniciar()
    puntaje2 = puntaje2 + 1
  elseif bola.x > love.graphics.getWidth() then
    reiniciar()
    puntaje1 = puntaje1 + 1
  end


Ahora añadimos el movimiento de las barras, el control del juego:

  -- Control del juego
  if love.keyboard.isDown("a") then
    barra1.y = barra1.y - velocidadBarra * dt
  elseif love.keyboard.isDown("z") then
    barra1.y = barra1.y + velocidadBarra * dt
  end
  if love.keyboard.isDown("up") then
    barra2.y = barra2.y - velocidadBarra * dt
  elseif love.keyboard.isDown("down") then
    barra2.y = barra2.y + velocidadBarra * dt
  end


Ahora limitamos el movimiento de las barras para que no salga de la pantalla:

  barra1.y = clamp(0, barra1.y, love.graphics.getHeight() - 128) -- 128 píxeles mide el alto de la barra
  barra2.y = clamp(0, barra2.y, love.graphics.getHeight() - 128)


Ahora añadimos el código que controla el rebote de la bola en las barras:

  --Rebote de la bola cuando golpea las barras
  if bola.x <= barra1.x + 32 and bola.x >= barra1.x and bola.y >= barra1.y and bola.y + 32 <= barra1.y + 128 then
    bola.movX = bola.velocidad
    play(pop)
  elseif bola.x + 32 >= barra2.x and bola.x <= barra2.x and bola.y >= barra2.y and bola.y + 32 <= barra2.y + 128 then
    bola.movX = - bola.velocidad
    play(pop)
  end



Ahora ya puedes jugar.

Puedes encontrar el proyecto en mi Github: https://github.com/DeybisMelendez/Love2DBlog/tree/master/pong
Tambien puedes jugar el juego en Itch.io:  https://damv.itch.io/pong

Exportar para Web

Vamos a exportar nuestro proyecto Flappy Bird para la web usando Emscripten.

Requisitos:

Tener instalado Care, para ellos solo usaremos sudo apt install care

Tener instalado Python 2.7 La mayoría de distribuciones lo traen instalado.

Los pasos son los siguientes:

Nos ubicamos en la terminal en una carpeta donde podamos tener a mano el "programa" que utilizaremos para exportar a web, y utilizaremos el comando:

git clone https://github.com/TannerRogalsky/love.js.git

Luego nos ubicamos en la carpeta love.js y utilizamos el siguiente comando:

git submodule update --init --recursive



Una vez que haya terminado, nos ubicamos en la carpeta release-compatibility y utilizaremos el siguiente comando:

python ../emscripten/tools/file_packager.py game.data --preload [ubicacion/de/tu/juego]@/ --js-output=game.js



Vamos a sustituir [ubicacion/de/tu/juego] por la dirección donde tenemos el proyecto, importante dejar el @/ y no usar caracteres especiales en la dirección.

El conjunto de archivos que nos queda es el juego exportado, para probarlo nos iremos al navegador y escribiremos la ubicacion del archivo index.html, por ejemplo:

/home/damv/Documentos/love.js/release-compatibility/index.html

Ahora nos debería ejecutar el juego:


Nota: He modificado el index.html para ponerle un título, con un poco de conocimiento html5 puedes acomodar a tu gusto la forma como se muestra el juego.

He publicado el juego en Itch puedes probarlo aquí:

https://damv.itch.io/ejemplo-de-juego-flappy-bird-en-love2d

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...