Lua en videojuegos: LÖVE 2D y Defold, los frameworks que usan Lua como lenguaje de scripting

Lua y los videojuegos tienen una historia larga. World of Warcraft usa Lua para su sistema de addons. Roblox también. Garry's Mod, Corona SDK, CryEngine y decenas de motores más han elegido Lua como lenguaje de scripting por la misma razón: es pequeño, rápido de embeber y suficientemente expresivo para describir comportamientos de juego sin ser tan pesado como Python o JavaScript. En este artículo nos centramos en dos frameworks que ponen Lua en el centro: LÖVE 2D y Defold.

LÖVE 2D: el framework para prototipos y juegos indie

LÖVE (pronunciado «love») es un framework de código abierto que lleva desde 2008 en activo. La versión actual es LÖVE 11.5, publicada en 2023. Está orientado a juegos 2D y usa Lua 5.1 como lenguaje, aunque funciona con LuaJIT si se compila con él. El bucle principal de cualquier juego LÖVE tiene tres funciones opcionales que el motor llama automáticamente:

  • love.load(): se ejecuta una sola vez al arrancar. Aquí se cargan los recursos.
  • love.update(dt): se llama en cada frame. dt es el tiempo en segundos desde el frame anterior.
  • love.draw(): renderiza la escena. Solo hay que dibujar; LÖVE llama a OpenGL por debajo.
-- main.lua de un juego LÖVE mínimo
local jugador = {x = 100, y = 100, velocidad = 200}
local imagen

function love.load()
    imagen = love.graphics.newImage("jugador.png")
    love.window.setTitle("Mi primer juego LÖVE")
end

function love.update(dt)
    -- Movimiento con teclas de cursor
    if love.keyboard.isDown("right") then
        jugador.x = jugador.x + jugador.velocidad * dt
    elseif love.keyboard.isDown("left") then
        jugador.x = jugador.x - jugador.velocidad * dt
    end
    if love.keyboard.isDown("down") then
        jugador.y = jugador.y + jugador.velocidad * dt
    elseif love.keyboard.isDown("up") then
        jugador.y = jugador.y - jugador.velocidad * dt
    end
end

function love.draw()
    love.graphics.draw(imagen, jugador.x, jugador.y)
    love.graphics.print("FPS: " .. love.timer.getFPS(), 10, 10)
end

function love.keypressed(key)
    if key == "escape" then love.event.quit() end
end

API gráfica de LÖVE

El módulo love.graphics cubre la mayoría de necesidades 2D:

function love.draw()
    -- Rectángulos y formas
    love.graphics.setColor(1, 0, 0, 1)      -- rojo (RGBA 0-1)
    love.graphics.rectangle("fill", 50, 50, 100, 60)
    love.graphics.setColor(0, 1, 0, 1)      -- verde
    love.graphics.circle("line", 200, 100, 40)

    -- Texto
    love.graphics.setColor(1, 1, 1, 1)
    love.graphics.print("Puntuación: " .. puntos, 10, 10)

    -- Imagen con rotación y escala
    love.graphics.draw(
        sprite,
        x, y,         -- posición
        angulo,       -- rotación en radianes
        escala,       -- escala X
        escala,       -- escala Y
        ox, oy        -- origen (pivot)
    )
end

Defold: el motor profesional de King

Defold es el motor de juegos que desarrolló King (los creadores de Candy Crush) y que en 2020 pasó a ser gratuito con código fuente abierto. Usa Lua 5.1 internamente (compatible con LuaJIT) y su arquitectura es más estructurada que LÖVE: los juegos se organizan en objetos de juego (game objects) con componentes. Un componente de script es un fichero .script con las funciones de ciclo de vida del objeto.

-- script de un objeto jugador en Defold

function init(self)
    -- self es la instancia del componente
    self.velocidad = 300
    self.salud = 100
    msg.post(".", "acquire_input_focus")
end

function update(self, dt)
    -- Mover el objeto en el espacio mundial
    local pos = go.get_position()
    if self.moviendose then
        pos.x = pos.x + self.velocidad * dt
        go.set_position(pos)
    end
end

function on_message(self, message_id, message, sender)
    if message_id == hash("recibir_danyo") then
        self.salud = self.salud - message.cantidad
        if self.salud <= 0 then
            go.delete()   -- eliminar el objeto de juego
        end
    end
end

function on_input(self, action_id, action)
    if action_id == hash("mover_derecha") then
        self.moviendose = action.pressed or action.repeated
    end
    return false
end

function final(self)
    msg.post(".", "release_input_focus")
end

Comparativa LÖVE vs Defold

Aspecto

LÖVE 2D

Defold

Versión Lua

5.1 (con LuaJIT opcional)

5.1 (LuaJIT integrado)

Arquitectura

Loop + callbacks libres

Componentes + mensajes

Plataformas

Windows, macOS, Linux, Android, iOS

Windows, macOS, Linux, Android, iOS, HTML5, Switch

Editor

Cualquier editor de texto

Editor propio (Java)

Curva de aprendizaje

Baja (ideal para prototipos)

Media (más estructura)

Uso en producción

Indie, game jams

Móvil, juegos comerciales

Gestión de entidades con tablas Lua

En LÖVE es habitual implementar un sistema de entidades usando las tablas de Lua. Cada entidad es una tabla con sus propiedades y una función update:

local entidades = {}

local function crearEnemigo(x, y)
    local e = {
        x = x, y = y,
        velocidad = 80,
        vida = 3,
        activo = true
    }
    function e:update(dt)
        self.y = self.y + self.velocidad * dt
        if self.y > love.graphics.getHeight() then
            self.activo = false
        end
    end
    function e:draw()
        love.graphics.setColor(1, 0.2, 0.2)
        love.graphics.circle("fill", self.x, self.y, 12)
    end
    return e
end

function love.update(dt)
    -- Crear un enemigo cada 2 segundos
    table.insert(entidades, crearEnemigo(math.random(800), -20))
    -- Actualizar y filtrar los inactivos
    local vivos = {}
    for _, e in ipairs(entidades) do
        e:update(dt)
        if e.activo then table.insert(vivos, e) end
    end
    entidades = vivos
end

function love.draw()
    for _, e in ipairs(entidades) do
        e:draw()
    end
end

Tanto LÖVE como Defold siguen activos y con comunidades considerables. LÖVE es la opción natural para aprender desarrollo de juegos con Lua, hacer prototipos rápidos o participar en game jams. Defold apunta más a proyectos comerciales, especialmente móvil, donde su compilación a código nativo y LuaJIT ofrecen un rendimiento difícil de igualar. Ambos se benefician del mismo conocimiento del lenguaje, así que aprender uno facilita el salto al otro.

Imagen: Pexels / UMUT RAW

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP