Tutorial Parte 2 Double Dragon

Continuando la anterior parte del tutorial, 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

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

  • 1: Estado en reposo.
  • 2 y 3: Caminando.
  • 4: Atacando.

Sprite hero

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

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

Héroe esperando

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.

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Will you buy me a coffee?

You can use the terminal.

ssh customer@andros.dev -p 5555

Written by Andros Fenollosa

January 4, 2017

10 min of reading

You may also like