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