desarrollo,  lua-love2d invaders

Desarrollo de Videojuegos con LUA+LOVE2D, INVADERS – LECCIÓN XII – Explosiones

Seguimos con el tutorial de invaders. A estas alturas tenemos elementos suficientes para echar un rato jugando, sin embargo, hay detalles y elementos que marcan la diferencia entre un juego para un rato y otro que enganche y nos tenga horas jugando.

Algunos detalles son evidentes como las explosiones que veremos en esta entrada y otros que pueden pasar casi desapercibidos pero enriquecen la partida y la sensación de realismo, las sombras y efectos de luz, el polvo o estela de las naves, elementos visuales que completen la escena aun sin intervenir directamente, como asteroides de fondo…

Explosiones

En un juego como este donde el objetivo es acertar a los enemigos, realzar el impacto es fundamental. Cada impacto es un pequeño triunfo que nos acerca más a ganar la batalla, así que hagamos que se vean.

Las explosiones son objetos que se muestran en pantalla durante un breve espacio de tiempo y se desvanecen. Para crearlas usaremos una tabla donde cada elemento tendrá información de una explosión.

Si queréis echar un vistazo antes de continuar, este es el código completo de la entrada:

function xInit()
	print("altaruru intro a love2d")
	fullscreen=true
--	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
end

function xloadbackground(lvl)
	path="recursos/Backgrounds/blue.png"
	background.image = love.graphics.newImage(path)
	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
	-- 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)
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
				-- 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)	
	-- 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
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)
	-- 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

function xcompruebajuego()
	if(#aliens==0) then
		xnewalien(0)
		xnewalien(1)
		xnewalien(3)
	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

Creando explosiones

Al igual que el resto de elementos del juego, declaramos la tabla inicialmente vacía en la función xloadobjs():

explos={}

La explosión tendrá unas coordenadas donde mostrarse, una imagen, y el tiempo durante el cual se muestra en pantalla:

coordenada x:     explo1.x
coordenada y:     explo1.y
imagen a mostrar: explo1.image
tiempo de vida:   explo1.time=18

Creando la explosión al hacer blanco

Las explosiones no se muestran al azar, debemos crearlas cuando un laser haga blanco. Las coordenadas de la explosión serán las mismas que las del laser en el momento del impacto.

Para iniciar cada nueva explosión vamos a crear una función que establezca los valores de la misma a partir del laser que la provoca, función xnewexplosion(<laser>), pasando como parámetro el laser:

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

Incluimos la llamada para crear la explosión en el momento del impacto, vamos alas funciones xcolisiones() y xcolisionesred() y dentro del if que controla si hay colisión incluimos la linea:

xnewexplosion(l)

Las funciones «colision» quedan así:

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
				-- activa explosion
				xnewexplosion(l)
				-- destruye el laser
				table.remove(lasers, ldel)
				break
			end
		end
	end
end

Ciclo de vida de la explosión

Perfecto!! ya creamos explosiones con cada impacto, ahora implementamos la lógica de su ciclo de vida, en este caso es tan sencillo como restar el tiempo transcurrido a su tiempo total de vida, si este llega a cero la explosión se elimina:

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
			-- destruye explosion
			table.remove(explos, edel)
		end
	end	
end

Incluimos la llamada en love.update() para que se actualice en cada ciclo. Como siempre, el tiempo es variable, debemos contar con la variable DeltaTime, dtR.

function love.update(dt)
	-- 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

Mostrando la explosión

Por último, nada es visible hasta que lo mostramos, así que vamos a nuestra función de dibujo xdrawpngs(), recuerda que llamamos a esta última desde love.draw(), y añadimos las lineas siguientes:

-- Draw explosiones
for _,e in pairs(explos) do
	love.graphics.draw(e.image, e.x, e.y)
end

Simplemente recorremos la tabla explos() y pintamos la imagen en sus coordenadas.

Conclusiones

Con las explosiones hemos visto elementos muy útiles para generar objetos, criaturas, animaciones y efectos autónomos con un ciclo de vida determinado.

Puedes descargar el ejemplo completo aquí:

Un saludo y feliz código!

Siguiente y última entrada de la serie Añadiendo niveles y dificultad al juego

Temario completo del curso

Deja un comentario

Tu dirección de correo electrónico no será publicada.