Un juego debe suponer un reto. Si siempre repetimos el mismo ciclo el jugador se aburrirá pronto y dejará el juego.
Para construir un juego divertido es necesario permitir evolucionar y mejorar al personaje o en este caso nuestra nave y avanzar incrementando la dificultad conforme vamos aprendiendo a jugar. Quizá esto último sea lo más difícil de lograr, mantener un equilibrio entre dificultad y jugabilidad. Si el juego es muy fácil aburre y si es muy difícil desespera 😉
En entradas anteriores hemos visto animación, gráficos, sonidos, colisiones… todos elementos que hacen posible la representación del juego.
Una vez sabemos como pintar y animar objetos es tirar de imaginación.
En esta entrada vamos a implementar un aumento de la dificultad conforme avanza el juego y el control de fin de juego, Game Over, bien por éxito al lograr superar todos los niveles o al perder todas las vidas.
Comprueba juego
Empecemos con el código.
Ya tenemos implementada una función xcompruebajuego() que llamamos en love.update() donde comprobamos si hemos abatido a todos los enemigos, si es así, volvemos a crearlos:
function xcompruebajuego()
if(#aliens==0) then
xnewalien(0)
xnewalien(1)
xnewalien(3)
end
end
Recordamos que love.update() es ejecutado automáticamente por el motor de love2d y es donde realizamos los cálculos de procesos y eventos del juego.
Ok, vamos a modificar la función xcompruebajuego() por la siguiente:
function xcompruebajuego()
if(#aliens==0) then
xnewlevel()
end
end
La creación de un nuevo nivel la implementamos en la función xnewlevel():
function xnewlevel()
player1.level = player1.level + 1
print("Player1.level: " .. player1.level)
if player1.level==cmodlastlvl then
xyouwin()
else
xloadbackground(player1.level)
print("bien hecho!")
for i = 1,background.aliens,1
do
xnewalien(i)
end
end
end
Nuevo nivel y ganar la partida
Si el jugador llega al nivel definido por cmodlastlvl ha ganado, Llama a xyouwin() que cambia el valor de Player1.estado a 2.
function xyouwin()
player1.estado = 2 -- 0 inicio, 1 juego, 2 ganado, 3 perdido
end
Mientras no llegue al nivel final, crea un nuevo nivel con fondo, música y número de enemigos distintos
function xloadbackground(lvl)
if lvl==1 then
path="recursos/Backgrounds/blue.png"
background.aliens = 3
else
if lvl % cmodnewlvl == 0 then
slvl = 1 + math.floor(lvl/cmodnewlvl)
if slvl == 2 then
-- niveles del 4 al 7
path="recursos/Backgrounds/darkPurple.png"
elseif slvl == 3 then
-- niveles del 8 al 11
path="recursos/Backgrounds/darkPurple2.png"
elseif slvl == 4 then
-- niveles del 12 al 15
path="recursos/Backgrounds/purple.png"
elseif slvl == 4 then
-- niveles del 16 al 19
path="recursos/Backgrounds/purple2.png"
end
-- un enemigo más cada 4 niveles
background.aliens = background.aliens + 1
else
return -- si no es ninguno de los niveles establecidos, sale de la funcion
end
end
background.image = love.graphics.newImage(path)
-- si sube de nivel cambia el fondo
background.W = background.image:getWidth()
background.H = background.image:getHeight()
background.sound=love.audio.newSource("recursos/musica/space1.mp3")
end
En esta función implementamos los cambios que queramos para cada nivel.
Perder la partida
Si nuestra nave sufre demasiados impactos habremos perdido la partida.

Para implementarlo en código utilizamos la variable player1.damage que contabiliza el daño sufrido por la nave, cuando el daño llega a player1.maxvida perdemos:
p.damage = p.damage + 1 -- incrementa contador de daño
if p.damage == p.maxvida then
xyoulose()
end
Vamos a la función xcolisionesred() que controla los impactos de los laser enemigos en nuestra nave:
function xcolisionesred()
-- comprobamos si alguno de los laserred impacta en player1
-- para ello, tenemos que recorrer los lasersred
p=player1
for ldel, l in ipairs(lasers) do
d=xdistancia(l, p)
if(d-60<(p.R+l.R)) then -- debug cuando estan cerca
if(d<(p.R+l.R)) then -- si la distancia entre objetos es menos que la suma de sus radios... BOOM!
print("impacto!" .. d .. "; RadioA:" .. p.R .. ", RadioL:" ..l.R)
p.damage = p.damage + 1 -- incrementa contador de daño
-- activa explosion
xnewexplosion(l)
-- destruye el laser
table.remove(lasers, ldel)
if p.damage == p.maxvida then
-- fin de la partida
xyoulose()
end
break
end
end
end
end
El flag que indica que hemos perdido es player1.estado=3.
function xyoulose()
player1.estado = 3 -- 0 inicio, 1 juego, 2 ganado, 3 perdido
end
Mensaje fin de partida
Hasta ahora hemos cambiado estados del Player, falta mostrar el mensaje de ganador o perdedor y detener el juego.
Si vamos a detener el juego no tiene sentido seguir realizando calculos y operaciones, por ello incluimos un if en love.update() para que quede así:
function love.update(dt)
if player1.estado == 1 then
... lógica y operaciones del juego
end
end
function love.update(dt)
if player1.estado == 1 then -- solo update de juego si player1.estado == 1
-- aqui ajustamos el tiempo
dtR=dt*1
if pause_down then
return
end
if left_down then
xmoveplayer(-1,0,dtR)
end
if right_down then
xmoveplayer(1,0,dtR)
end
xmovelasers(dtR)
xmovealiens(dtR)
xmovelasersred(dtR)
xexplosiones(dtR)
xcolisiones()
xcolisionesred()
xcompruebajuego()
end
end
Modificamos la función xdrawpngs() donde dibujamos objetos. Si el estado es 1 muestra los elementos del juego en ejecución y si estado es 2 o 3, ganador o perdedor, muestra el mensaje correspondiente:
function xdrawpngs()
if not pngs_visible then
return
end
-- Draw player
love.graphics.draw(player1.image, player1.x, player1.y)
love.graphics.print(player1.kills .. " kills", 30, 10)
love.graphics.print("lvl:" .. player1.level, WINDOWW-160, 10)
love.graphics.print("damage:" .. player1.damage, 30, 30)
-- 0 inicio, 1 juego, 2 ganado, 3 perdido
if player1.estado == 1 then
-- Draw enemies
for _,a in pairs(aliens) do
love.graphics.draw(a.image, a.x, a.y)
end
-- Draw explosiones
for _,e in pairs(explos) do
love.graphics.draw(e.image, e.x, e.y)
end
-- Draw lasers
for _,l in pairs(player1.lasers) do
love.graphics.draw(laserblue01.image, l.x, l.y)
end
for _,l in pairs(lasers) do
love.graphics.draw(laserred01.image, l.x, l.y)
end
elseif player1.estado == 2 then
love.graphics.print("Enhorabuena! HAS GANADO!", 100, WINDOWH/2-40)
elseif player1.estado == 3 then
love.graphics.print("Lo siento! HAS PERDIDO!", 100, WINDOWH/2-40)
end
end

Conclusiones e ideas
Hemos visto las bases suficientes para conocer love2d y crear juegos, a partir de aquí desarrolla y deja libre tu imaginación para hacer tu propio juego.
Es posible hacer ajustes y modificar parámetros para añadir logros/triunfos que nos motiven a seguir. Por ejemplo quitar un punto de daño cada X niveles o enemigos abatidos, incrementar potencia de fuego, evolucionar la nave, añadir nuevos enemigos, añadir objetos como asteroides, mostrar enemigos finales… todo lo que se os ocurra que de riqueza al juego hará que sea más divertido.
Felíz código!!
function xInit()
print("altaruru intro a love2d - Asteroids Clone")
fullscreen=false
love.window.setFullscreen(fullscreen)
print("ESCAPE TO QUIT")
-- recupera dimensiones de la ventana
WINDOWH = love.graphics.getHeight()
WINDOWW = love.graphics.getWidth()
-- inicia aleatorios:
math.randomseed(os.time()) -- random initialize
pause_down = false
pngs_visible = true
circle_visible = false
cmodnewlvl = 4
cmodlastlvl = 18
end
function xloadbackground(lvl)
if lvl==1 then
path="recursos/Backgrounds/blue.png"
background.aliens = 3
else
if lvl % cmodnewlvl == 0 then
slvl = 1 + math.floor(lvl/cmodnewlvl)
if slvl == 2 then
-- niveles del 4 al 7
path="recursos/Backgrounds/darkPurple.png"
elseif slvl == 3 then
-- niveles del 8 al 11
path="recursos/Backgrounds/darkPurple2.png"
elseif slvl == 4 then
-- niveles del 12 al 15
path="recursos/Backgrounds/purple.png"
elseif slvl == 4 then
-- niveles del 16 al 19
path="recursos/Backgrounds/purple2.png"
end
-- un enemigo más cada 4 niveles
background.aliens = background.aliens + 1
else
return -- si no es ninguno de los niveles establecidos, sale de la funcion
end
end
background.image = love.graphics.newImage(path)
-- si sube de nivel cambia el fondo
background.W = background.image:getWidth()
background.H = background.image:getHeight()
background.sound=love.audio.newSource("recursos/musica/space1.mp3")
end
function xloadobjs()
font = love.graphics.newFont("recursos/Bonus/kenvector_future2.ttf", 30)
love.graphics.setFont(font)
background = {}
xloadbackground(1)
laserblue01 = {}
laserblue01.image = love.graphics.newImage("recursos/PNG/Lasers/laserBlue01.png")
laserblue01.W = laserblue01.image:getWidth()
laserblue01.H = laserblue01.image:getHeight()
laserblue01.sound=love.audio.newSource("recursos/Bonus/sfx_laser1.ogg", "static")
laserred01 = {}
laserred01.image = love.graphics.newImage("recursos/PNG/Lasers/laserRed03.png")
laserred01.W = laserred01.image:getWidth()
laserred01.H = laserred01.image:getHeight()
laserred01.sound=love.audio.newSource("recursos/Bonus/sfx_laser2.ogg", "static")
player1 = {}
player1.image = love.graphics.newImage("recursos/PNG/playerShip1_blue.png")
player1.W = player1.image:getWidth() -- ancho de la nave
player1.H = player1.image:getHeight() -- alto de la nave
player1.x=(WINDOWW-player1.W)/2 -- posicion inicial x
player1.y=WINDOWH-player1.H-10 -- posicion inicial y
xpuntocentral(player1)
player1.R = player1.W*0.49 -- radio aprox para pruebas
player1.v = 300 --velocidad de la nave
player1.ctrshoot = -1 -- control de disparos
player1.kills = 0
player1.level = 1
player1.damage = 0
player1.maxvida = 5
player1.estado = 0 -- 0 inicio, 1 juego, 2 ganado, 3 perdido
-- tabla lasers
player1.lasers={}
-- tabla de explosiones
explos={}
--laserimpactred = love.graphics.newImage("recursos/PNG/Lasers/laserRed10.png")
--laserimpactblue = love.graphics.newImage("recursos/PNG/Lasers/laserBlue10.png")
exploimg = love.graphics.newImage("recursos/PNG/Lasers/laserRed10.png")
-- tabla lasers global, los lasers son indpendientes de los aliens, estos solo establecen el punto de partida
lasers={}
-- tabla de aliens
aliens = {}
alienimg = love.graphics.newImage("recursos/PNG/Enemies/enemyBlack1.png")
-- crea aliens por defecto
xnewalien(0)
xnewalien(1)
xnewalien(2)
-- inicia juego
player1.estado = 1
end
function xnewexplosion(obj)
explo1 = {}
explo1.x=obj.x
explo1.y=obj.y
explo1.image = exploimg
explo1.time=18 -- tiempo de "vida" d la explosion
-- inserta en la tabla
table.insert(explos, explo1)
end
function xnewalien(fila)
alien1 = {}
--alien1.image = love.graphics.newImage("recursos/PNG/Enemies/enemyBlack1.png")
alien1.image = alienimg --love.graphics.newImage("recursos/PNG/Enemies/enemyBlack1.png")
alien1.fila=fila;
alien1.W = alien1.image:getWidth() -- ancho de la nave
alien1.H = alien1.image:getHeight() -- alto de la nave
alien1.x = math.random(1,WINDOWW-alien1.H) -- posicion inicial x
alien1.y = 36 + (fila*alien1.H) -- posicion inicial y
xpuntocentral(alien1)
alien1.R = alien1.W*0.5 -- radio aprox para pruebas
alien1.v = 100 + (5*math.random(10,40)) --velocidad de la nave variable, entre 150 y 300 a intervalos de 5
alien1.xdir = 1 -- afecta al sentido del movimiento en el eje X. 1derecha, -1izquierda
alien1.ctrshoot=-1 -- control de disparos
-- inserta en la tabla
table.insert(aliens, alien1)
end
function xdistancia(obj1, obj2)
-- utiliza el punto central de cada objeto
if obj1==nil or obj2==nil then
return 1000
end
if obj1.cx==nil or obj2.cx==nil then
return 1000
end
d=math.sqrt(math.pow(obj2.cx-obj1.cx,2)+math.pow(obj2.cy-obj1.cy,2))
return d
end
function xpuntocentral(obj)
-- debe actualizar siempre que hay movimiento
obj.cx = obj.x + obj.W/2 --punto central X para calculo de colisiones
obj.cy = obj.y + obj.H/2 --punto central Y para calculo de colisiones
end
function xpuntocentralL(obj)
-- debe actualizar siempre que hay movimiento
obj.cx = obj.x + obj.W/2 --punto central X para calculo de colisiones
obj.cy = obj.y + obj.H*0.2 --punto central Y para calculo de colisiones
end
function xcolisiones()
-- comprobamos si alguno de nuestros laser impacta en los aliens
-- para ello, tenemos que recorrer los lasers y los aliens
for ldel, l in ipairs(player1.lasers) do
--love.graphics.draw(laserblue01.image, l.x, l.y)
for adel,a in ipairs(aliens) do
--love.graphics.draw(a.image, a.x, a.y)
d=xdistancia(l, a)
if(d-60<(a.R+l.R)) then -- debug cuando estan cerca
--print(a.fila .. ">" .. d .. " * " .. a.R+l.R)
if(d<(a.R+l.R)) then -- si la distancia entre objetos es menos que la suma de sus radios... BOOM!
print("impacto!" .. d .. "; RadioA:" .. a.R .. ", RadioL:" ..l.R)
player1.kills = player1.kills + 1 -- incrementa contador de muertes
-- explosion
xnewexplosion(l)
-- destruye el laser
table.remove(player1.lasers, ldel)
-- destruye alien
table.remove(aliens, adel)
break
end
end
end
end
end
function xcolisionesred()
-- comprobamos si alguno de los laserred impacta en player1
-- para ello, tenemos que recorrer los lasersred
p=player1
for ldel, l in ipairs(lasers) do
d=xdistancia(l, p)
if(d-60<(p.R+l.R)) then -- debug cuando estan cerca
if(d<(p.R+l.R)) then -- si la distancia entre objetos es menos que la suma de sus radios... BOOM!
print("impacto!" .. d .. "; RadioA:" .. p.R .. ", RadioL:" ..l.R)
p.damage = p.damage + 1 -- incrementa contador de daño
if p.damage == p.maxvida then
-- fin de la partida:
xyoulose()
end
-- activa explosion
xnewexplosion(l)
-- destruye el laser
table.remove(lasers, ldel)
break
end
end
end
end
function xexplosiones(dt)
for edel, e in ipairs(explos) do
-- control del tiempo de vida de la explosión
e.time=e.time-dt*150
if e.time<0 then
-- dstruye explosion
table.remove(explos, edel)
end
end
end
function love.load()
xInit()
xloadobjs()
background.sound:setVolume(0.3) -- 30% del volumen general
background.sound:setLooping(true)
background.sound:play()
end
function xdrawpoints()
if not circle_visible then
return
end
wpoint=6
-- Draw player
love.graphics.setColor(80, 255, 0)
love.graphics.rectangle("fill", player1.cx, player1.cy, wpoint, wpoint)
love.graphics.circle("line", player1.cx, player1.cy, player1.R)
-- Draw enemies
love.graphics.setColor(200, 0, 200)
for _,a in pairs(aliens) do
love.graphics.rectangle("fill", a.cx, a.cy, wpoint, wpoint)
love.graphics.circle("line", a.cx, a.cy, a.R)
end
-- Draw lasers
love.graphics.setColor(190, 170, 0)
for _,l in pairs(player1.lasers) do
love.graphics.circle("line", l.cx, l.cy, l.R)
end
-- Draw lasers red
love.graphics.setColor(240, 20, 20)
for _,l in pairs(lasers) do
love.graphics.circle("line", l.cx, l.cy, l.R)
end
end
function xdrawpngs()
if not pngs_visible then
return
end
-- Draw player
love.graphics.draw(player1.image, player1.x, player1.y)
love.graphics.print(player1.kills .. " kills", 30, 10)
love.graphics.print("lvl:" .. player1.level, WINDOWW-160, 10)
love.graphics.print("damage:" .. player1.damage, 30, 30)
-- 0 inicio, 1 juego, 2 ganado, 3 perdido
if player1.estado == 1 then
-- Draw enemies
for _,a in pairs(aliens) do
love.graphics.draw(a.image, a.x, a.y)
end
-- Draw explosiones
for _,e in pairs(explos) do
love.graphics.draw(e.image, e.x, e.y)
end
-- Draw lasers
for _,l in pairs(player1.lasers) do
love.graphics.draw(laserblue01.image, l.x, l.y)
end
for _,l in pairs(lasers) do
love.graphics.draw(laserred01.image, l.x, l.y)
end
elseif player1.estado == 2 then
love.graphics.print("Enhorabuena! HAS GANADO!", 100, WINDOWH/2-40)
elseif player1.estado == 3 then
love.graphics.print("Lo siento! HAS PERDIDO!", 100, WINDOWH/2-40)
end
end
function love.draw()
-- Draw background
love.graphics.setColor(255, 255, 255)
love.graphics.draw(background.image, 0, 0, 0, WINDOWW/background.W, WINDOWH/background.H)
xdrawpngs()
xdrawpoints()
end
function xmoveplayer(x, y, dt)
player1.x = player1.x + (x * player1.v * dt)
xpuntocentral(player1)
if player1.ctrshoot>0 then
player1.ctrshoot = player1.ctrshoot - 1000 * dt
end
end
-- Move Aliens
function xmovealiens(dt)
for _,a in pairs(aliens) do
a.x = a.x + (a.xdir * a.v * dt)
-- si llega a los bordes de la ventana cambia la dirección
if a.x > WINDOWW-a.W then
a.x = WINDOWW-a.W
a.xdir = -1
elseif a.x<0 then
a.x = 0
a.xdir = 1
end
if a.x > player1.x-20 and a.x<player1.x+20 then
xalienshoot(a)
end
if a.ctrshoot>0 then
a.ctrshoot = a.ctrshoot - 1000 * dt
end
xpuntocentral(a)
end
end
function xmovelasers(dt)
for ldel, laser in ipairs(player1.lasers) do
if laser.y > -5 then
laser.y= laser.y - 500 * dt
xpuntocentralL(laser)
else
-- si llega a la parte superior de la ventana se elimina
table.remove(player1.lasers, ldel)
player1.ctrshoot=-1 -- temporal
end
end
end
function xmovelasersred(dt)
-- estos lasers van d arriba a abajo
for ldel, laser in ipairs(lasers) do
if laser.y < WINDOWH then
laser.y = laser.y + 500 * dt
xpuntocentralL(laser)
else
-- si llega a la parte superior de la ventana se elimina
table.remove(lasers, ldel)
end
end
end
function xplayershoot()
if player1.ctrshoot < 0 then
player1.ctrshoot=10
laser={}
laser.W = laserblue01.W
laser.H = laserblue01.H
laser.x= player1.x + (player1.W/2) - (laserblue01.W/2)
laser.y= player1.y - laserblue01.H
laser.R = laserblue01.W/2
table.insert(player1.lasers, laser)
-- reproduce sonido laser
laserblue01.sound:stop()
laserblue01.sound:play()
end
end
function xalienshoot(a)
-- el alien establece el punto de partida del laser, pero a partir de ese momento
-- el laser es independiente, no se vincula al alien ya que aunque este ultimo sea destruido, el laser seguira existiendo.
if a.ctrshoot < 0 then
a.ctrshoot=200
laser={}
laser.W = laserred01.W
laser.H = laserred01.H
laser.x= a.x + (a.W/2) - (laserred01.W/2)
laser.y= a.y + a.H
laser.R = laserred01.W/2
table.insert(lasers, laser)
-- reproduce sonido laser
laserred01.sound:stop()
laserred01.sound:play()
end
end
function love.update(dt)
if player1.estado == 1 then -- solo update de juego si player1.estado == 1
-- aqui ajustamos el tiempo
dtR=dt*1
if pause_down then
return
end
if left_down then
xmoveplayer(-1,0,dtR)
end
if right_down then
xmoveplayer(1,0,dtR)
end
xmovelasers(dtR)
xmovealiens(dtR)
xmovelasersred(dtR)
xexplosiones(dtR)
xcolisiones()
xcolisionesred()
xcompruebajuego()
end
end
function xyouwin()
player1.estado = 2 -- 0 inicio, 1 juego, 2 ganado, 3 perdido
end
function xyoulose()
player1.estado = 3 -- 0 inicio, 1 juego, 2 ganado, 3 perdido
end
function xnewlevel()
player1.level = player1.level + 1
print("Player1.level: " .. player1.level)
if player1.level==cmodlastlvl then
xyouwin()
else
xloadbackground(player1.level)
print("bien hecho!")
for i = 1,background.aliens,1
do
xnewalien(i)
end
end
end
function xcompruebajuego()
if(#aliens==0) then
xnewlevel()
end
end
function love.keypressed(key)
if key == "p" then
-- cada pulsación de P activa o desactiva la pausa
pause_down = not pause_down
end
if key == "escape" then
love.event.quit()
end
if pause_down then
return
end
if key == 'left' then
left_down = true
elseif key == 'right' then
right_down = true
end
if key == " " then
xplayershoot()
end
if key == "7" then
-- muestra oculta imagenes
pngs_visible = not pngs_visible
end
if key == "8" then
-- muestra oculta circulos detección colisiones
circle_visible = not circle_visible
end
if key == "f" then
fullscreen=not fullscreen
love.window.setFullscreen(fullscreen)
end
end
function love.keyreleased(key, unicode)
if pause_down then
return
end
if key == 'left' then
left_down = false
elseif key == 'right' then
right_down = false
end
if key == " " then
space_down = false
end
end
function love.quit()
print("¡Hasta pronto!")
end