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
A engine Defold incorpora a linguagem Lua para scripting. Lua é uma linguagem dinâmica leve, poderosa, rápida e fácil de incorporar. Ela é amplamente usada como linguagem de scripting para videogames. Programas Lua são escritos em uma sintaxe procedural simples. A linguagem é tipada dinamicamente e executada por um interpretador de bytecode. Ela conta com gerenciamento automático de memória por coleta de lixo incremental.
Este manual dará uma introdução rápida aos conceitos básicos de programação Lua em geral e ao que você precisa considerar ao trabalhar com Lua no Defold. Se você tem alguma experiência com Python, Perl, Ruby, Javascript ou uma linguagem dinâmica semelhante, começará rapidamente. Se você é iniciante em programação, talvez queira começar com um livro de Lua voltado para iniciantes. Há muitos para escolher.
O Defold usa LuaJIT, uma versão altamente otimizada de Lua adequada para uso em jogos e outros softwares críticos para desempenho. Ela é totalmente compatível para cima com Lua 5.1 e oferece suporte a todas as funções da biblioteca padrão de Lua e ao conjunto completo de funções da API Lua/C.
LuaJIT também adiciona várias extensões de linguagem e alguns recursos de Lua 5.2 e 5.3.
Nosso objetivo é manter o Defold igual em todas as plataformas, mas atualmente temos algumas pequenas discrepâncias na versão da linguagem Lua entre plataformas:
Para garantir que seu jogo funcione em todas as plataformas compatíveis, recomendamos fortemente que você use APENAS recursos de linguagem de Lua 5.1.
O Defold inclui todas as bibliotecas padrão de Lua 5.1, além de uma biblioteca de socket e uma de operações de bit:
assert(), error(), print(), ipairs(), require() etc.)Todas as bibliotecas estão documentadas na documentação de referência da API.
Programas têm uma sintaxe simples e fácil de ler. Instruções são escritas uma por linha e não há necessidade de marcar o fim de uma instrução. Você pode opcionalmente usar ponto e vírgula ; para separar instruções. Blocos de código são delimitados por palavras-chave, terminando com a palavra-chave end. Comentários podem ser escritos em bloco ou até o fim da linha:
--[[
Aqui há um bloco de comentários que pode ocupar
várias linhas no arquivo-fonte.
--]]
a = 10
b = 20 ; c = 30 -- duas instruções em uma linha
if my_variable == 3 then
call_some_function(true) -- Aqui há um comentário de linha
else
call_another_function(false)
end
Lua é tipada dinamicamente, o que significa que variáveis não têm tipos, mas valores têm. Ao contrário de linguagens com tipagem estática, você pode atribuir qualquer valor a qualquer variável como quiser.
Há oito tipos básicos em Lua:
nilnil. Ele geralmente representa a ausência de um valor útil, por exemplo variáveis não atribuídas.
print(my_var) -- imprimirá 'nil', já que 'my_var' ainda não recebeu um valor
true ou false. Condições que são false ou nil são consideradas falsas. Qualquer outro valor a torna verdadeira.
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) --> prints '10'
print(10.0) --> '10'
print(10.000000000001) --> '10.000000000001'
a = 5 -- inteiro
b = 7/3 -- float
print(a - b) --> '2.6666666666667'
\0). Lua não faz suposições sobre o conteúdo de uma string, então você pode armazenar nelas qualquer dado que quiser. Literais de string são escritos entre aspas simples ou duplas. Lua converte entre números e strings em tempo de execução. Strings podem ser concatenadas com o operador ...
Strings podem conter as seguintes sequências de escape no estilo C:
| Sequência | Caractere |
|---|---|
\a |
bell |
\b |
back space |
\f |
form feed |
\n |
nova linha |
\r |
carriage return |
\t |
tabulação horizontal |
\v |
tabulação vertical |
\\ |
barra invertida |
\" |
aspas duplas |
\' |
aspas simples |
\[ |
colchete esquerdo |
\] |
colchete direito |
\ddd |
caractere indicado por seu valor numérico, em que ddd é uma sequência de até três dígitos decimais |
my_string = "hello"
another_string = 'world'
print(my_string .. another_string) --> "helloworld"
print("10.2" + 1) --> 11.2
print(my_string + 1) -- erro, não é possível converter "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 conveniência.
-- Atribui 'my_plus' a uma função
my_plus = function(p, q)
return p + q
end
print(my_plus(4, 5)) --> 9
-- Sintaxe conveniente para atribuir função à variável 'my_mult'
function my_mult(p, q)
return p * q
end
print(my_mult(4, 5)) --> 20
-- Recebe uma função como parâmetro 'func'
function operate(func, p, q)
return func(p, q) -- Chama a função fornecida com os parâmetros 'p' e 'q'
end
print(operate(my_plus, 4, 5)) --> 9
print(operate(my_mult, 4, 5)) --> 20
-- Cria uma função somadora e a retorna
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, não 0.
-- Inicializa uma tabela como sequência
weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
print(weekdays[1]) --> "Sunday"
print(weekdays[5]) --> "Thursday"
-- Inicializa uma tabela como registro com valores de sequência
moons = { Earth = { "Moon" },
Uranus = { "Puck", "Miranda", "Ariel", "Umbriel", "Titania", "Oberon" } }
print(moons.Uranus[3]) --> "Ariel"
-- Constrói uma tabela a partir de um construtor vazio {}
a = 1
t = {}
t[1] = "first"
t[a + 1] = "second"
t.x = 1 -- igual a t["x"] = 1
-- Itera sobre os pares chave, valor da tabela
for key, value in pairs(t) do
print(key, value)
end
--> 1 first
--> 2 second
--> x 1
u = t -- u agora referencia a mesma tabela que t
u[1] = "changed"
for key, value in pairs(t) do -- ainda iterando sobre t!
print(key, value)
end
--> 1 changed
--> 2 second
--> x 1
+, -, *, /, o - unário (negação) e exponencial ^.
a = -1
print(a * 2 + 3 / 4^5) --> -1.9970703125
Lua fornece conversões automáticas entre números e strings em tempo de execução. Qualquer operação numérica aplicada a uma string tenta converter a string para um número:
print("10" + 1) --> 11
< (menor que), > (maior que), <= (menor ou igual), >= (maior ou igual), == (igual), ~= (diferente). Esses operadores sempre retornam true ou false. Valores de tipos diferentes são considerados diferentes. Se os tipos forem os mesmos, eles são comparados de acordo com seu valor. Lua compara tabelas, userdata e funções por referência. Dois valores assim são considerados iguais apenas se referenciam o mesmo 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 e not. and retorna seu primeiro argumento se ele for false; caso contrário, retorna seu segundo argumento. or retorna seu primeiro argumento se ele não for false; caso contrário, retorna seu 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
... Números são convertidos para strings quando concatenados.
print("donkey" .. "kong") --> "donkeykong"
print(1 .. 2) --> "12"
#. O comprimento de uma string é seu número de bytes. O comprimento de uma tabela é o comprimento da sequência, o número de índices numerados a partir de 1 e para cima em que o valor não é nil. Observação: se a sequência tiver “buracos” com valor nil, o comprimento pode ser qualquer índice anterior a um 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 fornece o conjunto usual de construções de controle de fluxo.
then se a condição for verdadeira; caso contrário, executa a parte else (opcional). Em vez de aninhar instruções if, você pode usar elseif. Isso substitui uma instrução switch, que Lua não tem.
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 dia da semana
i = 1
while weekdays[i] do
print(weekdays[i])
i = i + 1
end
weekdays = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
-- Imprime cada dia da semana
i = 0
repeat
i = i + 1
print(weekdays[i])
until weekdays[i] == "Saturday"
for: numérico e genérico. O for numérico recebe 2 ou 3 valores numéricos, enquanto o for genérico itera sobre todos os valores retornados por uma função iterator.
-- Imprime os números de 1 a 10
for i = 1, 10 do
print(i)
end
-- Imprime os números de 1 a 10 e incrementa em 2 a cada vez
for i = 1, 10, 2 do
print(i)
end
-- Imprime os números de 10 a 1
for i=10, 1, -1 do
print(i)
end
t = { "a", "b", "c", "d" }
-- Itera sobre a sequência e imprime os valores
for i, v in ipairs(t) do
print(v)
end
break para sair de um bloco interno de um loop for, while ou repeat. Use return para retornar um valor de uma função ou para finalizar a execução de uma função e retornar ao chamador. break ou return podem aparecer apenas como a última instrução de um bloco.
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 as variáveis que você declara são globais por padrão, o que significa que elas ficam disponíveis por todas as partes do contexto de runtime Lua. Você pode declarar variáveis explicitamente como local, o que significa que a variável existirá apenas dentro do escopo atual.
Cada arquivo-fonte Lua define um escopo separado. Declarações locais no nível mais alto de um arquivo significam que a variável é local ao arquivo de script Lua. Cada função cria outro escopo aninhado, e cada bloco de estrutura de controle cria escopos adicionais. Você pode criar explicitamente um escopo com as palavras-chave do e end. Lua tem escopo léxico, o que significa que um escopo tem acesso total às variáveis local do escopo envolvente. Observe que as variáveis locais devem ser declaradas antes do uso.
function my_func(a, b)
-- 'a' e 'b' são locais a esta função e disponíveis em seu escopo
do
local x = 1
end
print(x) --> nil. 'x' não está disponível fora do escopo do-end
print(foo) --> nil. 'foo' é declarado depois de 'my_func'
print(foo_global) --> "value 2"
end
local foo = "value 1"
foo_global = "value 2"
print(foo) --> "value 1". 'foo' está disponível no escopo mais alto após a declaração.
Observe que, se você declarar funções local em um arquivo de script (o que geralmente é uma boa ideia), precisa prestar atenção à ordem do código. Você pode usar declarações antecipadas se tiver funções que chamam umas às outras mutuamente.
local func2 -- Declara antecipadamente 'func2'
local function func1(a)
print("func1")
func2(a)
end
function func2(a) -- ou func2 = function(a)
print("func2")
if a < 10 then
func1(a + 1)
end
end
function init(self)
func1(1)
end
Se você escrever uma função dentro de outra função, ela também terá acesso total às variáveis locais da função envolvente. Esta é uma construção muito poderosa.
function create_counter(x)
-- 'x' é uma variável local em '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
Variáveis locais declaradas em um bloco sombrearão variáveis de um bloco externo com o mesmo nome.
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
Funções executam do início ao fim e não há como pará-las no meio. Corrotinas permitem fazer isso, o que pode ser muito conveniente em alguns casos. Suponha que queremos criar uma animação frame a frame muito específica em que movemos um objeto de jogo da posição y 0 para algumas posições y específicas do frame 1 ao frame 5. Poderíamos resolver isso com um contador na função update() (veja abaixo) e uma lista de posições. No entanto, com uma corrotina, obtemos uma implementação muito limpa, fácil de estender e de trabalhar. Todo o estado fica contido dentro da própria corrotina.
Quando uma corrotina cede, ela retorna o controle ao chamador, mas lembra seu ponto de execução para poder continuar dali mais tarde.
-- Esta é a nossa corrotina
local function sequence(self)
coroutine.yield(120)
coroutine.yield(320)
coroutine.yield(510)
coroutine.yield(240)
return 440 -- retorna o valor final
end
function init(self)
self.co = coroutine.create(sequence) -- Cria a corrotina. 'self.co' é um objeto thread
go.set_position(vmath.vector3(100, 0, 0)) -- Define a posição inicial
end
function update(self, dt)
local status, y_pos = coroutine.resume(self.co, self) -- Continua a execução da corrotina.
if status then
-- Se a corrotina ainda não terminou/morreu, usa seu valor retornado por yield como nova posição
go.set_position(vmath.vector3(100, y_pos, 0))
end
end
Todas as variáveis que você declara são globais por padrão, o que significa que elas ficam disponíveis por todas as partes do contexto de runtime Lua. O Defold tem uma configuração shared_state em game.project que controla esse contexto. Se a opção estiver definida, todos os scripts, scripts de GUI e o script de renderização serão avaliados no mesmo contexto Lua, e variáveis globais ficarão visíveis em todos os lugares. Se a opção não estiver definida, a engine executará scripts, scripts de GUI e o script de renderização em contextos separados.

O Defold permite usar o mesmo arquivo de script em vários componentes de objeto de jogo separados. Quaisquer variáveis declaradas localmente são compartilhadas entre componentes que executam o mesmo arquivo de script.
-- 'my_global_value' estará disponível a partir de todos os scripts, gui_scripts, render script e módulos (arquivos Lua)
my_global_value = "global scope"
-- este valor será compartilhado por todas as instâncias de componente que usam este arquivo de script específico
local script_value = "script scope"
function init(self, dt)
-- Este valor estará disponível nesta instância de componente de script
self.foo = "self scope"
-- este valor estará disponível dentro de init() e após sua declaração
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, pois local_foo só é visível em init()
end
Em um jogo de alto desempenho destinado a rodar a 60 FPS estáveis, pequenos erros de desempenho podem ter um grande impacto na experiência. Há algumas coisas gerais simples a considerar e algumas coisas que talvez não pareçam problemáticas.
Começando pelas coisas simples. Em geral, é uma boa ideia escrever código direto que não contenha loops desnecessários. Às vezes você precisa iterar sobre listas de coisas, mas tenha cuidado se a lista de coisas for suficientemente grande. Este exemplo roda em pouco mais de 1 milissegundo em um laptop bastante decente, o que pode fazer toda a diferença se cada frame tiver apenas 16 milissegundos (a 60 FPS) e a engine, o script de renderização, a simulação de física e assim por diante consumirem uma parte disso.
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
Use o valor retornado por socket.gettime() (segundos desde a época do sistema) para fazer benchmark de código suspeito.
A coleta de lixo de Lua roda automaticamente em segundo plano por padrão e recupera memória alocada pelo runtime Lua. Coletar muito lixo pode ser uma tarefa demorada, então é bom manter baixo o número de objetos que precisam ser coletados:
local v = 42)local s = "some_string" criará um novo objeto e atribuirá s a ele. O s local em si não gerará lixo, mas o objeto string sim. Usar a mesma string várias vezes não adiciona custo extra de memória.{ ... }), uma nova tabela é criada.function () ... end, não chamar uma função definida)function(v, ...) end) criam uma tabela para as reticências cada vez que a função é chamada (em Lua antes da versão 5.2, ou se não estiver usando LuaJIT).dofile() e dostring()Há muitos casos em que você pode evitar criar novos objetos e, em vez disso, reutilizar os que já tem. Por exemplo, o seguinte é comum ao final de cada update():
-- Redefine a velocidade
self.velocity = vmath.vector3()
É fácil esquecer que cada chamada a vmath.vector3() cria um novo objeto. Vamos descobrir quanta memória um vector3 usa:
print(collectgarbage("count") * 1024) -- 88634
local v = vmath.vector3()
print(collectgarbage("count") * 1024) -- 88704. 70 bytes no total foram alocados
70 bytes foram adicionados entre as chamadas a collectgarbage(), mas isso inclui alocações para mais do que o objeto vector3. Cada impressão do resultado de collectgarbage() cria uma string que por si só adiciona 22 bytes de lixo:
print(collectgarbage("count") * 1024) -- 88611
print(collectgarbage("count") * 1024) -- 88633. 22 bytes alocados
Então um vector3 pesa 70-22=48 bytes. Isso não é muito, mas se você criar um a cada frame em um jogo de 60 FPS, de repente são 2,8 kB de lixo por segundo. Com 360 componentes de script, cada um criando um vector3 a cada frame, estamos olhando para 1 MB de lixo gerado por segundo. Os números podem crescer muito rapidamente. Quando o runtime Lua coleta lixo, ele pode consumir muitos milissegundos preciosos, especialmente em plataformas mobile.
Uma forma de evitar alocações é criar um vector3 e continuar trabalhando com o mesmo objeto. Por exemplo, para redefinir um vector3, podemos usar a seguinte construção:
-- Em vez de fazer self.velocity = vmath.vector3(), que cria um novo objeto,
-- zeramos os componentes de um objeto vetor de velocidade existente
self.velocity.x = 0
self.velocity.y = 0
self.velocity.z = 0
O esquema padrão de coleta de lixo pode não ser ideal para algumas aplicações críticas em tempo. Se você notar um stutter no seu jogo ou app, talvez queira ajustar como Lua coleta lixo por meio da função Lua collectgarbage(). Você pode, por exemplo, executar o coletor por um curto período a cada frame com um valor baixo de step. Para ter uma ideia de quanta memória seu jogo ou app está consumindo, você pode imprimir a quantidade atual de bytes de lixo com:
print(collectgarbage("count") * 1024)
Uma consideração comum de design de implementação é como estruturar código para comportamentos compartilhados. Várias abordagens são possíveis.

Além disso, mesmo que seja possível fazer o código de módulo modificar diretamente os internos de um objeto de jogo (passando self para uma função de módulo), desencorajamos fortemente isso, pois você criará um acoplamento muito forte.

go.property() que aponta para o objeto de jogo alvo.

O benefício dessa configuração é que você pode soltar um objeto de jogo de comportamento em uma coleção contendo o objeto alvo. Nenhum código adicional é necessário.
Em situações em que você precisa gerenciar grandes quantidades de objetos de jogo, esse design não é preferível, pois o objeto de comportamento é duplicado para cada instância e cada objeto terá custo de memória.