Tutorial Parte 2 Double Dragon

Continuando , vamos a crear a nuestro héroe. Añadiento eventos de teclado de Love2d, colisiones para que no se salga del nivel, y animación para realizar el caminado.

Héroe

Creamos en scripts un archivo llamado hero.lua, y definiremos su estructura.

local hero = {}

function hero.load()
end

function hero.update(dt)
end

function hero.draw()
end

return hero

Y lo añadimos a main.lua.

local game = require 'assets/scripts/game'
local level = require 'assets/scripts/level' 
local hero = require 'assets/scripts/hero' 

-- Cargar
function love.load()
	game.load()
	level.load()
	hero.load()
end

-- Actualizar
function love.update(dt)
	-- Por ahora no tenemos nada que actualizar
end

-- Pintar
function love.draw()
	level.draw()
	hero.draw()
end

-- Eventos
function love.keypressed(key, scancode, isrepeat)
	-- Eventos de teclado: apretando una tecla
end

function love.keyreleased(key, scancode)
	-- Eventos de teclado: Soltando una tecla
end

En hero.lua cargamos y dibujamos el sprite.

local hero = {}

function hero.load()
	hero.img = love.graphics.newImage('assets/sprites/hero/hero.png') 
	hero.x = 100
	hero.y = 400
end

function hero.update(dt)
end

function hero.draw()
	love.graphics.draw(hero.img, hero.x, hero.y) 
end

return hero

Nos encontramos con un traspié, se muestran las 4 imágenes de la animación. Lo gestionaremos, pero antes debemos tener claro para que sirve cada una.

Para facilitarnos la vida vamos a utilizar un plugin ya realizado y compartido por un bondadoso usuario:

Descargaremos anim8.lua y lo dejaremos en vendor. Editamos hero.lua.

local anim8 = require 'assets/scripts/vendor/anim8' 

local hero = {}

function hero.load()
	hero.img = love.graphics.newImage('assets/sprites/hero/hero.png') 
	hero.x = 100
	hero.y = 400
  	hero.num_frames = 4 -- Tenemos 4 posiciones a animar
  	hero.width = hero.img:getWidth() / hero.num_frames -- Debemos indicarle cuanto mide un cuadro de ancho. En nuestro caso es sencillo: la anchura de la imagen dividida por 4, o el número de frames
  	hero.height = hero.img:getHeight() -- También con la altura. Al ser horizontal nos vale la altura de la imagen.
	g = anim8.newGrid(hero.width, hero.height, hero.img:getWidth(), hero.img:getHeight()) -- Creamos el grid de anim8
	-- Animaciones
	hero.animations = {}
  	hero.animations.stop = anim8.newAnimation(g('1-1', 1), 1) -- Creamos la animación: Irá del frame 1 al 1, estará en la posición 1 dentro de anim8 y durará 1 seg (no podemos decirle que dure 0) 
end

function hero.update(dt)
	hero.animations.stop:update(dt) -- Declaramos anim8 para que haga sus cálculos internos. 
end

function hero.draw()
	hero.animations.stop:draw(hero.img, hero.x, hero.y) -- Mostramos la animación. 
end

return hero

Antes de seguir animándolo, vamos a moverlo con el teclado. Editamos hero.lua.

local anim8 = require 'assets/scripts/vendor/anim8' 

local hero = {}

function hero.load()
	hero.img = love.graphics.newImage('assets/sprites/hero/hero.png') 
	hero.x = 100
	hero.y = 400
  	hero.num_frames = 4 -- Tenemos 4 posiciones a animar
  	hero.width = hero.img:getWidth() / hero.num_frames -- Debemos indicarle cuanto mide un cuadro de ancho. En nuestro caso es sencillo: la anchura de la imagen dividida por 4, o el número de frames
  	hero.height = hero.img:getHeight() -- También con la altura. Al ser horizontal nos vale la altura de la imagen.
	g = anim8.newGrid(hero.width, hero.height, hero.img:getWidth(), hero.img:getHeight()) -- Creamos el grid de anim8
	-- Animaciones
	hero.animations = {}
  	hero.animations.stop = anim8.newAnimation(g('1-1', 1), 1) -- Creamos la animación: Irá del frame 1 al 1, estará en la posición 1 dentro de anim8 y durará 1 seg (no podemos decirle que dure 0) 
  	-- Controls
  	BUTTON_RIGHT = false
end

function hero.update(dt)
	hero.animations.stop:update(dt) -- Declaramos anim8 para que haga sus cálculos internos. 
	if BUTTON_RIGHT then
		hero.x = hero.x + 1 -- Le indicamos que cuando esté pulsado el botón aumente en x la posición del personaje
	end
end

function hero.draw()
	hero.animations.stop:draw(hero.img, hero.x, hero.y) -- Mostramos la animación. En este caso esta parada.
end

function hero.keypressed(key, scancode, isrepeat) -- Que haremos cuando se pulse un botón
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = true -- Indicamos que se ha pulsado
	end
end

function hero.keyreleased(key, scancode) -- Que haremos cuando se suelte una tecla
	if key == 'right' then
		BUTTON_RIGHT = false -- Indicamos que se ha soltado
	end
end

return hero

Y para reflejar los cambios en main.lua.

local game = require 'assets/scripts/game'
local level = require 'assets/scripts/level' 
local hero = require 'assets/scripts/hero' 

-- Cargar
function love.load()
	game.load()
	level.load()
	hero.load()
end

-- Actualizar
function love.update(dt)
	hero.update(dt)
end

-- Pintar
function love.draw()
	level.draw()
	hero.draw()
end

-- Eventos
function love.keypressed(key, scancode, isrepeat)
	hero.keypressed(key, scancode, isrepeat)
end

function love.keyreleased(key, scancode)
	hero.keyreleased(key, scancode, isrepeat)
end

Repetimos con el resto de las direcciones.

local anim8 = require 'assets/scripts/vendor/anim8' 

local hero = {}

function hero.load()
	hero.img = love.graphics.newImage('assets/sprites/hero/hero.png') 
	hero.x = 100
	hero.y = 400
  	hero.num_frames = 4 -- Tenemos 4 posiciones a animar
  	hero.width = hero.img:getWidth() / hero.num_frames -- Debemos indicarle cuanto mide un cuadro de ancho. En nuestro caso es sencillo: la anchura de la imagen dividida por 4, o el número de frames
  	hero.height = hero.img:getHeight() -- También con la altura. Al ser horizontal nos vale la altura de la imagen.
	g = anim8.newGrid(hero.width, hero.height, hero.img:getWidth(), hero.img:getHeight()) -- Creamos el grid de anim8
	-- Animaciones
	hero.animations = {}
  	hero.animations.stop = anim8.newAnimation(g('1-1', 1), 1) -- Creamos la animación: Irá del frame 1 al 1, estará en la posición 1 dentro de anim8 y durará 1 seg (no podemos decirle que dure 0) 
  	-- Controls
  	BUTTON_RIGHT = false
  	BUTTON_LEFT = false
  	BUTTON_UP = false
  	BUTTON_DOWN = false
end

function hero.update(dt)
	hero.animations.stop:update(dt) -- Declaramos anim8 para que haga sus cálculos internos. 
	if BUTTON_RIGHT then
		hero.x = hero.x + 1 -- Le indicamos que cuando esté pulsado el botón aumente en x la posición del personaje
	elseif BUTTON_LEFT then
		hero.x = hero.x - 1
	elseif BUTTON_UP then
		hero.y = hero.y - 1
	elseif BUTTON_DOWN then
		hero.y = hero.y + 1
	end
end

function hero.draw()
	hero.animations.stop:draw(hero.img, hero.x, hero.y) -- Mostramos la animación. En este caso esta parada.
end

function hero.keypressed(key, scancode, isrepeat) -- Que haremos cuando se pulse un botón
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = true -- Indicamos que se ha pulsado
	elseif key == 'left' then
		BUTTON_LEFT = true 
	elseif key == 'up' then
		BUTTON_UP = true 
	elseif key == 'down' then
		BUTTON_DOWN = true 
	end
end

function hero.keyreleased(key, scancode) -- Que haremos cuando se suelte una tecla
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = false -- Indicamos que se ha pulsado
	elseif key == 'left' then
		BUTTON_LEFT = false 
	elseif key == 'up' then
		BUTTON_UP = false 
	elseif key == 'down' then
		BUTTON_DOWN = false 
	end
end

return hero

Ahora le aplicamos la animación de andar.

local anim8 = require 'assets/scripts/vendor/anim8' 

local hero = {}

function hero.load()
	hero.img = love.graphics.newImage('assets/sprites/hero/hero.png') 
	hero.x = 100
	hero.y = 400
  	hero.num_frames = 4 -- Tenemos 4 posiciones a animar
  	hero.width = hero.img:getWidth() / hero.num_frames -- Debemos indicarle cuanto mide un cuadro de ancho. En nuestro caso es sencillo: la anchura de la imagen dividida por 4, o el número de frames
  	hero.height = hero.img:getHeight() -- También con la altura. Al ser horizontal nos vale la altura de la imagen.
	g = anim8.newGrid(hero.width, hero.height, hero.img:getWidth(), hero.img:getHeight()) -- Creamos el grid de anim8
	-- Animaciones
	hero.animations = {}
  	hero.animations.stop = anim8.newAnimation(g('1-1', 1), 1) -- Creamos la animación: Irá del frame 1 al 1, estará en la posición 1 dentro de anim8 y durará 1 seg (no podemos decirle que dure 0) 
  	hero.animations.walk = anim8.newAnimation(g('2-3', 1), 0.5) -- Creamos la animación de andar: su intervalo será entre frame y frame de 0.5 segundos 
  	-- Controls
  	BUTTON_RIGHT = false
  	BUTTON_LEFT = false
  	BUTTON_UP = false
  	BUTTON_DOWN = false
end

function hero.update(dt)
	hero.animations.stop:update(dt) -- Declaramos anim8 para que haga sus cálculos internos. 
	hero.animations.walk:update(dt)
	if BUTTON_RIGHT then
		hero.x = hero.x + 1 -- Le indicamos que cuando esté pulsado el botón aumente en x la posición del personaje
	elseif BUTTON_LEFT then
		hero.x = hero.x - 1
	elseif BUTTON_UP then
		hero.y = hero.y - 1
	elseif BUTTON_DOWN then
		hero.y = hero.y + 1
	end
end

function hero.draw()
	if BUTTON_DOWN or BUTTON_UP or BUTTON_LEFT or BUTTON_RIGHT then
		hero.animations.walk:draw(hero.img, hero.x, hero.y) -- Si se pulsa algún botón, mostramos la animación de caminado.
	else
		hero.animations.stop:draw(hero.img, hero.x, hero.y) -- En caso contrario solo mostrarmos la animación de parado
	end
end

function hero.keypressed(key, scancode, isrepeat) -- Que haremos cuando se pulse un botón
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = true -- Indicamos que se ha pulsado
	elseif key == 'left' then
		BUTTON_LEFT = true 
	elseif key == 'up' then
		BUTTON_UP = true 
	elseif key == 'down' then
		BUTTON_DOWN = true 
	end
end

function hero.keyreleased(key, scancode) -- Que haremos cuando se suelte una tecla
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = false -- Indicamos que se ha pulsado
	elseif key == 'left' then
		BUTTON_LEFT = false 
	elseif key == 'up' then
		BUTTON_UP = false 
	elseif key == 'down' then
		BUTTON_DOWN = false 
	end
end

return hero

La única pega es que al ir hacia atrás nos hará el moonwalker. Tendremos que volvearlo hacia un lado o al otro cuando pulsemos en las teclas de right o de left.

Primero clonaremos la animación, pero dándole la vuelta en vertical con flip. Después añadiremos una variable llamada flip para controlar si esta dado la vuelta o no. E iremos cambiando la animación dependiendo de esa variable.

También añadiremos hero.vel para controlar la velocidad del personaje. Lo he subido a 2 ya que iba muy lento.

local anim8 = require 'assets/scripts/vendor/anim8' 

local hero = {}

function hero.load()
	hero.img = love.graphics.newImage('assets/sprites/hero/hero.png') 
	hero.x = 100
	hero.y = 400
  	hero.num_frames = 4 -- Tenemos 4 posiciones a animar
  	hero.vel = 2
  	hero.width = hero.img:getWidth() / hero.num_frames -- Debemos indicarle cuanto mide un cuadro de ancho. En nuestro caso es sencillo: la anchura de la imagen dividida por 4, o el número de frames
  	hero.height = hero.img:getHeight() -- También con la altura. Al ser horizontal nos vale la altura de la imagen.
	g = anim8.newGrid(hero.width, hero.height, hero.img:getWidth(), hero.img:getHeight()) -- Creamos el grid de anim8
	-- Animaciones
	hero.animations = {}
  	hero.animations.stop = anim8.newAnimation(g('1-1', 1), 1) -- Creamos la animación: Irá del frame 1 al 1, estará en la posición 1 dentro de anim8 y durará 1 seg (no podemos decirle que dure 0) 
  	hero.animations.stop_flip = hero.animations.stop:clone():flipH() -- Clonamos y volteamos la animación
  	hero.animations.walk = anim8.newAnimation(g('2-3', 1), 0.5) -- Creamos la animación de andar: su intervalo será entre frame y frame de 0.5 segundos 
  	hero.animations.walk_flip = hero.animations.walk:clone():flipH() -- Clonamos y volteamos la animación
  	-- Controls
  	BUTTON_RIGHT = false
  	BUTTON_LEFT = false
  	BUTTON_UP = false
  	BUTTON_DOWN = false
  	flip = false -- Con esta variable controlaremos si esta volteado o no
end

function hero.update(dt)
	hero.animations.stop:update(dt) -- Declaramos anim8 para que haga sus cálculos internos. 
	hero.animations.stop_flip:update(dt)
	hero.animations.walk:update(dt)
	hero.animations.walk_flip:update(dt)
	if BUTTON_RIGHT then
		hero.x = hero.x + hero.vel -- Le indicamos que cuando esté pulsado el botón aumente en x la posición del personaje
	elseif BUTTON_LEFT then
		hero.x = hero.x - hero.vel
	elseif BUTTON_UP then
		hero.y = hero.y - hero.vel
	elseif BUTTON_DOWN then
		hero.y = hero.y + hero.vel
	end
end

function hero.draw()
	if BUTTON_DOWN or BUTTON_UP or BUTTON_LEFT or BUTTON_RIGHT then
		if flip then
			hero.animations.walk_flip:draw(hero.img, hero.x, hero.y) -- Si se pulsa algún botón, mostramos la animación de caminado.
		else
			hero.animations.walk:draw(hero.img, hero.x, hero.y) -- Si se pulsa algún botón, mostramos la animación de caminado.
		end
	else
		if flip then
			hero.animations.stop_flip:draw(hero.img, hero.x, hero.y) -- En caso contrario solo mostrarmos la animación de parado
		else
			hero.animations.stop:draw(hero.img, hero.x, hero.y) -- En caso contrario solo mostrarmos la animación de parado
		end
	end
end

function hero.keypressed(key, scancode, isrepeat) -- Que haremos cuando se pulse un botón
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = true -- Indicamos que se ha pulsado
		flip = false
	elseif key == 'left' then
		BUTTON_LEFT = true 
		flip = true
	elseif key == 'up' then
		BUTTON_UP = true 
	elseif key == 'down' then
		BUTTON_DOWN = true 
	end
end

function hero.keyreleased(key, scancode) -- Que haremos cuando se suelte una tecla
	if key == 'right' then -- Si la tecla pulsado es 'right' (flecha derecha del teclado)
		BUTTON_RIGHT = false -- Indicamos que se ha pulsado
	elseif key == 'left' then
		BUTTON_LEFT = false 
	elseif key == 'up' then
		BUTTON_UP = false 
	elseif key == 'down' then
		BUTTON_DOWN = false 
	end
end

return hero

Ya solo quedará obligar a que no se salga fuera de la calle.

Espero que este ejercicio te haya servido como introducción a la programación de videojuegos y a Love2D.

Si hay muchos comentarios prometo realizar más partes.

Versión escritorio