This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English
El motor Defold lleva integrado el lenguaje Lua para scripting. Lua es un lenguaje dinámico ligero que es potente, rápido y fácil de integrar. Se usa ampliamente como lenguaje de scripting para videojuegos. Los programas Lua se escriben con una sintaxis procedimental simple. El lenguaje tiene tipado dinámico y se ejecuta mediante un intérprete de bytecode. Incluye gestión automática de memoria con recolección de basura incremental.
Este manual ofrece una introducción rápida a los fundamentos de la programación Lua en general y a lo que debes considerar al trabajar con Lua en Defold. Si tienes algo de experiencia con Python, Perl, Ruby, JavaScript o un lenguaje dinámico similar, podrás avanzar bastante rápido. Si eres nuevo en la programación, tal vez quieras empezar con un libro de Lua orientado a principiantes. Hay muchos para elegir.
Defold usa LuaJIT, una versión altamente optimizada de Lua adecuada para juegos y otro software donde el rendimiento es crítico. Es totalmente compatible hacia arriba con Lua 5.1 y soporta todas las funciones de la biblioteca estándar de Lua y el conjunto completo de funciones de la API Lua/C.
LuaJIT también agrega varias extensiones del lenguaje y algunas funcionalidades de Lua 5.2 y 5.3.
Nuestro objetivo es mantener Defold igual en todas las plataformas, pero actualmente tenemos algunas discrepancias menores en la versión del lenguaje Lua entre plataformas:
Para garantizar que tu juego funcione en todas las plataformas compatibles, recomendamos firmemente que uses SOLO funcionalidades del lenguaje de Lua 5.1.
Defold incluye todas las bibliotecas estándar de Lua 5.1, así como una biblioteca de socket y una de operaciones de bits:
assert(), error(), print(), ipairs(), require(), etc.)Todas las bibliotecas están documentadas en la documentación de referencia de la API.
Los programas tienen una sintaxis simple y fácil de leer. Las sentencias se escriben una por línea y no es necesario marcar el final de una sentencia. Opcionalmente puedes usar punto y coma ; para separar sentencias. Los bloques de código están delimitados por palabras clave y terminan con la palabra clave end. Los comentarios pueden escribirse como bloque o hasta el final de la línea:
--[[
Aquí hay un bloque de comentarios que puede ocupar
varias líneas en el archivo fuente.
--]]
a = 10
b = 20 ; c = 30 -- dos sentencias en una línea
if my_variable == 3 then
call_some_function(true) -- Este es un comentario de línea
else
call_another_function(false)
end
Lua tiene tipado dinámico, lo que significa que las variables no tienen tipos, pero los valores sí. A diferencia de los lenguajes con tipado estático, puedes asignar cualquier valor a cualquier variable como quieras.
Hay ocho tipos básicos en Lua:
nilnil. Normalmente representa la ausencia de un valor útil, por ejemplo variables sin asignar.
print(my_var) -- imprimirá 'nil' porque 'my_var' todavía no tiene un valor asignado
true o false. Las condiciones que son false o nil se consideran falsas. Cualquier otro valor se considera verdadero.
flag = true
if flag then
print("flag is true")
else
print("flag is false")
end
if my_var then
print("my_var is not nil nor false!")
end
if not my_var then
print("my_var is either nil or false!")
end
print(10) --> imprime '10'
print(10.0) --> '10'
print(10.000000000001) --> '10.000000000001'
a = 5 -- entero
b = 7/3 -- float
print(a - b) --> '2.6666666666667'
\0). Lua no asume nada sobre el contenido de un string, así que puedes guardar en ellos cualquier dato que quieras. Los literales de string se escriben con comillas simples o dobles. Lua convierte entre números y strings en tiempo de ejecución. Los strings se pueden concatenar con el operador ...
Los strings pueden contener las siguientes secuencias de escape de estilo C:
| Secuencia | Carácter |
|---|---|
\a |
campana |
\b |
retroceso |
\f |
avance de página |
\n |
nueva línea |
\r |
retorno de carro |
\t |
tabulación horizontal |
\v |
tabulación vertical |
\\ |
barra invertida |
\" |
comilla doble |
\' |
comilla simple |
\[ |
corchete izquierdo |
\] |
corchete derecho |
\ddd |
carácter indicado por su valor numérico, donde ddd es una secuencia de hasta tres dígitos decimales |
my_string = "hello"
another_string = 'world'
print(my_string .. another_string) --> "helloworld"
print("10.2" + 1) --> 11.2
print(my_string + 1) -- error, no puede convertir "hello"
print(my_string .. 1) --> "hello1"
print("one\nstring") --> one
--> string
print("\097bc") --> "abc"
multi_line_string = [[
Here is a chunk of text that runs over several lines. This is all
put into the string and is sometimes very handy.
]]
function name(param1, param2) ... end) por comodidad.
-- Asigna 'my_plus' a una función
my_plus = function(p, q)
return p + q
end
print(my_plus(4, 5)) --> 9
-- Sintaxis conveniente para asignar una función a la variable 'my_mult'
function my_mult(p, q)
return p * q
end
print(my_mult(4, 5)) --> 20
-- Toma una función como parámetro 'func'
function operate(func, p, q)
return func(p, q) -- Llama a la función proporcionada con los parámetros 'p' y 'q'
end
print(operate(my_plus, 4, 5)) --> 9
print(operate(my_mult, 4, 5)) --> 20
-- Crea una función sumadora y la devuelve
function create_adder(n)
return function(a)
return a + n
end
end
adder = create_adder(2)
print(adder(3)) --> 5
print(adder(10)) --> 12
1, no 0.
-- Inicializa una tabla como secuencia
weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
print(weekdays[1]) --> "Sunday"
print(weekdays[5]) --> "Thursday"
-- Inicializa una tabla como registro con valores de secuencia
moons = { Earth = { "Moon" },
Uranus = { "Puck", "Miranda", "Ariel", "Umbriel", "Titania", "Oberon" } }
print(moons.Uranus[3]) --> "Ariel"
-- Construye una tabla desde un constructor vacío {}
a = 1
t = {}
t[1] = "first"
t[a + 1] = "second"
t.x = 1 -- igual que t["x"] = 1
-- Itera sobre los pares clave-valor de la tabla
for key, value in pairs(t) do
print(key, value)
end
--> 1 first
--> 2 second
--> x 1
u = t -- ahora u se refiere a la misma tabla que t
u[1] = "changed"
for key, value in pairs(t) do -- sigue iterando sobre t
print(key, value)
end
--> 1 changed
--> 2 second
--> x 1
+, -, *, /, el - unario (negación) y el exponencial ^.
a = -1
print(a * 2 + 3 / 4^5) --> -1.9970703125
Lua proporciona conversiones automáticas entre números y strings en tiempo de ejecución. Cualquier operación numérica aplicada a un string intenta convertir el string en un número:
print("10" + 1) --> 11
< (menor que), > (mayor que), <= (menor o igual), >= (mayor o igual), == (igual), ~= (distinto). Estos operadores siempre devuelven true o false. Los valores de tipos distintos se consideran distintos. Si los tipos son iguales, se comparan según su valor. Lua compara tablas, userdata y funciones por referencia. Dos valores de ese tipo solo se consideran iguales si se refieren al mismo objeto.
a = 5
b = 6
if a <= b then
print("a is less than or equal to b")
end
print("A" < "a") --> true
print("aa" < "ab") --> true
print(10 == "10") --> false
print(tostring(10) == "10") --> true
and, or y not. and devuelve su primer argumento si es false; de lo contrario devuelve su segundo argumento. or devuelve su primer argumento si no es false; de lo contrario devuelve su segundo argumento.
print(true or false) --> true
print(true and false) --> false
print(not false) --> true
if a == 5 and b == 6 then
print("a is 5 and b is 6")
end
... Los números se convierten en strings al concatenarlos.
print("donkey" .. "kong") --> "donkeykong"
print(1 .. 2) --> "12"
#. La longitud de un string es su número de bytes. La longitud de una tabla es la longitud de su secuencia: el número de índices numerados desde 1 en adelante cuyo valor no es nil. Nota: Si la secuencia tiene “huecos” con valores nil, la longitud puede ser cualquier índice anterior a un valor nil.
s = "donkey"
print(#s) --> 6
t = { "a", "b", "c", "d" }
print(#t) --> 4
u = { a = 1, b = 2, c = 3 }
print(#u) --> 0
v = { "a", "b", nil }
print(#v) --> 2
Lua proporciona el conjunto habitual de construcciones de control de flujo.
then si la condición es verdadera; de lo contrario ejecuta la parte else (opcional). En lugar de anidar sentencias if, puedes usar elseif. Esto reemplaza a una sentencia switch, que Lua no tiene.
a = 5
b = 4
if a < b then
print("a is smaller than b")
end
if a == '1' then
print("a is 1")
elseif a == '2' then
print("a is 2")
elseif a == '3' then
print("a is 3")
else
print("I have no idea what a is...")
end
weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
-- Imprime cada día de la semana
i = 1
while weekdays[i] do
print(weekdays[i])
i = i + 1
end
weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
-- Imprime cada día de la semana
i = 0
repeat
i = i + 1
print(weekdays[i])
until weekdays[i] == "Saturday"
for: numérico y genérico. El for numérico toma 2 o 3 valores numéricos, mientras que el for genérico itera sobre todos los valores devueltos por una función iteradora.
-- Imprime los números del 1 al 10
for i = 1, 10 do
print(i)
end
-- Imprime los números del 1 al 10 e incrementa en 2 cada vez
for i = 1, 10, 2 do
print(i)
end
-- Imprime los números del 10 al 1
for i=10, 1, -1 do
print(i)
end
t = { "a", "b", "c", "d" }
-- Itera sobre la secuencia e imprime los valores
for i, v in ipairs(t) do
print(v)
end
break para salir de un bloque interno de un bucle for, while o repeat. Usa return para devolver un valor desde una función o para finalizar la ejecución de una función y volver al llamador. break o return solo pueden aparecer como la última sentencia de un bloque.
a = 1
while true do
a = a + 1
if a >= 100 then
break
end
end
function my_add(a, b)
return a + b
end
print(my_add(10, 12)) --> 22
Todas las variables que declaras son globales por defecto, lo que significa que están disponibles en todas las partes del contexto de runtime de Lua. Puedes declarar variables explícitamente como local, lo que significa que la variable solo existirá dentro del ámbito actual.
Cada archivo fuente Lua define un ámbito separado. Las declaraciones locales en el nivel superior de un archivo hacen que la variable sea local al archivo script Lua. Cada función crea otro ámbito anidado y cada bloque de estructura de control crea ámbitos adicionales. Puedes crear un ámbito explícitamente con las palabras clave do y end. Lua tiene ámbito léxico, lo que significa que un ámbito tiene acceso completo a variables locales del ámbito que lo contiene. Ten en cuenta que las variables locales deben declararse antes de usarlas.
function my_func(a, b)
-- 'a' y 'b' son locales a esta función y están disponibles en su ámbito
do
local x = 1
end
print(x) --> nil. 'x' no está disponible fuera del ámbito do-end
print(foo) --> nil. 'foo' se declara después de 'my_func'
print(foo_global) --> "value 2"
end
local foo = "value 1"
foo_global = "value 2"
print(foo) --> "value 1". 'foo' está disponible en el ámbito superior después de la declaración.
Ten en cuenta que si declaras funciones local en un archivo script (lo que generalmente es una buena idea), debes prestar atención al orden del código. Puedes usar declaraciones adelantadas si tienes funciones que se llaman mutuamente.
local func2 -- Declaración adelantada de 'func2'
local function func1(a)
print("func1")
func2(a)
end
function func2(a) -- o func2 = function(a)
print("func2")
if a < 10 then
func1(a + 1)
end
end
function init(self)
func1(1)
end
Si escribes una función dentro de otra función, también tiene acceso completo a las variables locales de la función que la contiene. Esta es una construcción muy potente.
function create_counter(x)
-- 'x' es una variable local en 'create_counter'
return function()
x = x + 1
return x
end
end
count1 = create_counter(10)
count2 = create_counter(20)
print(count1()) --> 11
print(count2()) --> 21
print(count1()) --> 12
Las variables locales declaradas en un bloque ocultarán variables de un bloque envolvente que tengan el mismo nombre.
my_global = "global"
print(my_global) -->"global"
local v = "local"
print(v) --> "local"
local function test(v)
print(v)
end
function init(self)
v = "apple"
print(v) --> "apple"
test("banana") --> "banana"
end
Las funciones se ejecutan desde el principio hasta el final y no hay forma de detenerlas a la mitad. Las corrutinas te permiten hacerlo, lo que puede ser muy conveniente en algunos casos. Supongamos que queremos crear una animación muy específica fotograma por fotograma donde movemos un objeto de juego desde la posición y 0 hasta algunas posiciones y muy específicas desde el fotograma 1 hasta el fotograma 5. Podríamos resolverlo con un contador en la función update() (ver abajo) y una lista de posiciones. Sin embargo, con una corrutina obtenemos una implementación muy limpia, fácil de extender y de trabajar. Todo el estado queda contenido dentro de la propia corrutina.
Cuando una corrutina hace yield, devuelve el control al llamador pero recuerda su punto de ejecución para poder continuar desde ahí más tarde.
-- Esta es nuestra corrutina
local function sequence(self)
coroutine.yield(120)
coroutine.yield(320)
coroutine.yield(510)
coroutine.yield(240)
return 440 -- devuelve el valor final
end
function init(self)
self.co = coroutine.create(sequence) -- Crea la corrutina. 'self.co' es un objeto thread
go.set_position(vmath.vector3(100, 0, 0)) -- Define la posición inicial
end
function update(self, dt)
local status, y_pos = coroutine.resume(self.co, self) -- Continúa la ejecución de la corrutina.
if status then
-- Si la corrutina todavía no está terminada/muerta, usa su valor devuelto por yield como nueva posición
go.set_position(vmath.vector3(100, y_pos, 0))
end
end
Todas las variables que declaras son globales por defecto, lo que significa que están disponibles en todas las partes del contexto de runtime de Lua. Defold tiene una configuración shared_state en game.project que controla este contexto. Si la opción está activada, todos los scripts, scripts GUI y el render script se evalúan en el mismo contexto Lua y las variables globales son visibles en todas partes. Si la opción no está activada, el motor ejecuta scripts, scripts GUI y el render script en contextos separados.

Defold te permite usar el mismo archivo script en varios componentes de objeto de juego separados. Cualquier variable declarada localmente se comparte entre los componentes que ejecutan el mismo archivo script.
-- 'my_global_value' estará disponible desde todos los scripts, gui_scripts, render script y módulos (archivos Lua)
my_global_value = "global scope"
-- este valor se compartirá entre todas las instancias de componente que usen este archivo script concreto
local script_value = "script scope"
function init(self, dt)
-- Este valor estará disponible en esta instancia de componente script
self.foo = "self scope"
-- este valor estará disponible dentro de init() y después de su declaración
local local_foo = "local scope"
print(local_foo)
end
function update(self, dt)
print(self.foo)
print(my_global_value)
print(script_value)
print(local_foo) -- imprimirá nil, porque local_foo solo es visible en init()
end
En un juego de alto rendimiento pensado para ejecutarse a 60 FPS fluidos, los pequeños errores de rendimiento pueden tener un gran impacto en la experiencia. Hay algunas cosas generales simples que considerar, y algunas cosas que pueden no parecer problemáticas.
Empezando por lo simple: generalmente es buena idea escribir código directo que no contenga bucles innecesarios. A veces sí necesitas iterar sobre listas de cosas, pero ten cuidado si la lista es lo suficientemente grande. Este ejemplo se ejecuta en poco más de 1 milisegundo en una laptop bastante decente, lo que puede marcar toda la diferencia si cada fotograma solo dura 16 milisegundos (a 60 FPS) y el motor, el render script, la simulación de física, etc., consumen parte de ese tiempo.
local t = socket.gettime()
local table = {}
for i=1,2000 do
table[i] = vmath.vector3(i, i, i)
end
print((socket.gettime() - t) * 1000)
-- DEBUG:SCRIPT: 0.40388
Usa el valor devuelto por socket.gettime() (segundos desde la época del sistema) para medir código sospechoso.
La recolección de basura de Lua se ejecuta automáticamente en segundo plano de forma predeterminada y recupera memoria que el runtime de Lua ha asignado. Recolectar mucha basura puede llevar tiempo, así que conviene reducir la cantidad de objetos que necesitan ser recolectados:
local v = 42)local s = "some_string" creará un objeto nuevo y asignará s a él. La variable local s en sí no generará basura, pero el objeto string sí. Usar el mismo string varias veces no agrega coste de memoria adicional.{ ... }), se crea una tabla nueva.function () ... end, no llamar a una función definida)function(v, ...) end) crean una tabla para los puntos suspensivos cada vez que se llama a la función (en Lua anterior a la versión 5.2, o si no se usa LuaJIT).dofile() y dostring()Hay muchos casos en los que puedes evitar crear objetos nuevos y reutilizar los que ya tienes. Por ejemplo, lo siguiente es común al final de cada update():
-- Restablecer velocidad
self.velocity = vmath.vector3()
Es fácil olvidar que cada llamada a vmath.vector3() crea un objeto nuevo. Averigüemos cuánta memoria usa un vector3:
print(collectgarbage("count") * 1024) -- 88634
local v = vmath.vector3()
print(collectgarbage("count") * 1024) -- 88704. Se han asignado 70 bytes en total
Se han agregado 70 bytes entre las llamadas a collectgarbage(), pero esto incluye asignaciones para algo más que el objeto vector3. Cada impresión del resultado de collectgarbage() construye un string que por sí mismo agrega 22 bytes de basura:
print(collectgarbage("count") * 1024) -- 88611
print(collectgarbage("count") * 1024) -- 88633. 22 bytes asignados
Así que un vector3 ocupa 70-22=48 bytes. No es mucho, pero si creas uno en cada fotograma en un juego a 60 FPS, de pronto son 2.8 kB de basura por segundo. Con 360 componentes script que crean un vector3 cada uno en cada fotograma, hablamos de 1 MB de basura generada por segundo. Los números pueden crecer muy rápido. Cuando el runtime de Lua recolecta basura, puede consumir muchos milisegundos valiosos—especialmente en plataformas móviles.
Una forma de evitar asignaciones es crear un vector3 y seguir trabajando con el mismo objeto. Por ejemplo, para restablecer un vector3 podemos usar la siguiente construcción:
-- En lugar de hacer self.velocity = vmath.vector3(), lo que crea un objeto nuevo,
-- ponemos a cero los componentes de un objeto vector de velocidad existente
self.velocity.x = 0
self.velocity.y = 0
self.velocity.z = 0
El esquema predeterminado de recolección de basura puede no ser óptimo para algunas aplicaciones con restricciones estrictas de tiempo. Si ves tirones en tu juego o app, tal vez quieras ajustar cómo Lua recolecta basura mediante la función Lua collectgarbage(). Por ejemplo, puedes ejecutar el recolector durante un tiempo corto en cada fotograma con un valor bajo de step. Para hacerte una idea de cuánta memoria consume tu juego o app, puedes imprimir la cantidad actual de bytes de basura con:
print(collectgarbage("count") * 1024)
Una consideración de diseño de implementación común es cómo estructurar el código para comportamientos compartidos. Son posibles varios enfoques.

Además, aunque sea posible que el código de un módulo modifique directamente los internos de un objeto de juego (pasando self a una función del módulo), lo desaconsejamos firmemente porque crearás un acoplamiento muy fuerte.

go.property() que apunte al objeto de juego objetivo.

El beneficio de esta configuración es que puedes colocar un objeto de juego de comportamiento dentro de una colección que contenga el objeto objetivo. No se necesita código adicional.
En situaciones donde necesitas gestionar grandes cantidades de objetos de juego, este diseño no es preferible porque el objeto de comportamiento se duplica por cada instancia y cada objeto costará memoria.