This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
Beginner
Este tutorial guia você pelo processo de criação de um dos jogos clássicos mais comuns que você pode tentar recriar. Há muitas variações desse jogo; esta apresenta uma cobra que come “comida” e só cresce quando come. Esta cobra também rasteja por um campo de jogo que contém obstáculos.
![]()
Neste tutorial, você aprenderá a:
Este tutorial foi criado para iniciantes, mas se você é completamente novo no Defold e no desenvolvimento de jogos, recomendamos ler primeiro alguns manuais introdutórios, especialmente sobre os Blocos de Construção do Defold e o Glossário. Se ainda não baixou o Defold, confira o manual de Instalação. Também é recomendado conferir a visão geral do Editor para mergulhar rapidamente no próprio Editor, mas também fornecemos aqui capturas de tela para cada etapa.
Inicie o Defold e:

Concluído!
Começaremos definindo a resolução do jogo.
game.project no lado esquerdo, no painel Assets. Dê um duplo clique nele para abrir.game.project.Width e Height) para 768⨉768 ou outro múltiplo de 16.
O motivo para fazer isso é que o jogo será desenhado em uma grade onde cada segmento terá 16x16 pixels. Assim, a tela do jogo não cortará nenhum segmento parcial. Esse arquivo game.project contém todas as configurações importantes dos projetos; você pode ler sobre todas elas no manual de Configurações do Projeto.
Concluído!
Muito pouco é necessário em termos de gráficos para um clone minimalista de Snake. Um segmento verde de 16⨉16 para a cobra, um bloco branco para os obstáculos e um bloco vermelho menor representando a comida.
Primeiro, crie um diretório para os assets no Defold Editor:
mainNew Folder.assets e clique em Create Folder.
Concluído!
Esta imagem abaixo é o único asset de que você precisa:


Você também pode ler mais detalhes sobre importar assets aqui.
Concluído!
O Defold fornece um componente Tilemap integrado que você usará para criar o campo de jogo composto por tiles alinhados em uma grade. Um tilemap permite definir e ler tiles individuais, o que se encaixa perfeitamente neste jogo. Como tilemaps buscam seus gráficos em um Tilesource, você precisa criar um:
assets.New ▸ Tile Source na seção “Resources”.snake.tilesource).
O tilesource será aberto em um Tilesource Editor dedicado para esse tipo de arquivo, e você precisará fornecer uma imagem para ele. No lado direito, você encontrará um painel Properties:
Defina a propriedade Image para o arquivo gráfico que você acabou de importar.

As propriedades Width e Height devem ser mantidas em 16 (valor padrão). Isso dividirá a imagem de 32⨉32 pixels em 4 tiles, numerados de 1–4.

Observe que a propriedade Extrude Borders está definida como 2 pixels. Isso serve para evitar artefatos visuais ao redor dos tiles que têm gráficos até a borda.
Se você fizer qualquer alteração em um arquivo, um asterisco * aparece ao lado do nome dele na aba. Selecione File ▸ Save All ou use o atalho Ctrl+S (⌘Cmd + S no Mac) para salvar todos os arquivos.
Concluído!
Agora você tem um tilesource pronto para uso, então é hora de criar o componente tilemap do campo de jogo:
Dê Right click na pasta main e selecione New ▸ Tile Map na seção “Components”. Nomeie o novo arquivo como “grid” (o editor salvará o arquivo como “grid.tilemap”).

Ele será aberto em um Tilemap Editor e destacará que precisa de uma Tile Source, então defina a propriedade Tile Source para o “snake.tilesource” criado anteriormente.

Concluído!
O Defold armazena apenas a área do tilemap que é realmente usada, então você precisa adicionar tiles suficientes para preencher os limites da tela.
layer1 no painel Outline do lado direito.Escolha a opção de menu Edit ▸ Select Tile... ou o atalho Space para exibir a paleta de tiles, depois clique no tile que deseja usar ao pintar.


Você precisará de um tilemap de tamanho 48x48 tiles (porque nossa tela é 768 e temos tiles de 16px, então 768/16 = 48) para preencher a tela do jogo.
Salve o tilemap quando terminar.
Concluído!
Agora precisamos adicionar nosso tilemap ao jogo. Se você está familiarizado com os Blocos de Construção do Defold, componentes fazem parte de Objetos de Jogo e objetos de jogo podem ser definidos em Coleções.
Abra main.collection dando um duplo clique nele no painel Assets. No template Empty Project, por padrão, esta é a coleção bootstrap carregada ao iniciar a engine.
Dê Right click na raiz no Outline e selecione Add Game Object, o que cria um novo objeto de jogo na coleção que é carregada quando o jogo começa.

Dê Right click no novo objeto de jogo e selecione Add Component File. Escolha o arquivo “grid.tilemap” que você acabou de criar.

Agora temos um tilemap em nossa coleção de jogo. Ele deve ficar visível quando você executar o jogo pelo Editor.
Project ▸ Build ou o atalho Ctrl + B (⌘Cmd + B no Mac).
Concluído!
Dê Right click na pasta main no navegador Assets e selecione New ▸ Script na seção Scripts. Nomeie o novo arquivo de script como “snake” (ele será salvo como “snake.script”). Esse arquivo conterá toda a lógica do jogo.

Volte para main.collection e dê right click no objeto de jogo que contém o tilemap. Selecione Add Component File e escolha o arquivo “snake.script”.

Agora você tem o componente tilemap e o script no lugar.
Concluído!
O script que você vai escrever controlará todo o jogo. Adicionaremos recursos um por um.
A ideia de como isso funcionará é a seguinte:
Abra snake.script e localize a função init(). Essa função é chamada pela engine quando o script é inicializado no início do jogo. Altere o código para o seguinte:
function init(self)
self.segments = { -- <1>
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0} -- <2>
self.speed = 7.0 -- <3>
self.time = 0 -- <4>
end
Neste código, nós:
self.segments, contendo uma lista de tabelas, cada uma com uma posição X e Y para um segmento.self.dir, contendo uma direção X e Y.self.speed, expressa em tiles por segundo.self.time, que será usado para acompanhar a velocidade de movimento.O código de script acima está escrito na linguagem Lua. Há algumas coisas a observar sobre o código, mas se você ainda não entender nada do que vem abaixo, não se preocupe. Apenas acompanhe, experimente e dê tempo — você acabará entendendo. Por enquanto, pode lembrar que em init() apenas inicializamos as variáveis que usaremos.
self. A referência self é usada para armazenar dados da instância.self pode ser usada como uma tabela Lua na qual você pode armazenar dados. Basta usar a notação de ponto como faria com qualquer outra tabela: self.data = "value". A referência é válida durante toda a vida do script, neste caso desde o início do jogo até você encerrá-lo.{}.{x = 10, y = 20}), tabelas Lua aninhadas ({ {a = 1}, {b = 2} }) ou outros tipos de dados.Concluído!
A função init() é chamada exatamente uma vez, quando o componente de script é instanciado no jogo em execução. A função update(), porém, é chamada uma vez a cada frame, por padrão 60 vezes por segundo. Isso torna a função ideal para lógica de jogo em tempo real.
A ideia do update é esta: em algum intervalo definido, faça o seguinte:

Tenha em mente que a cabeça da cobra está no final da tabela, e a cauda está no começo.
update() em snake.script e altere o código para o seguinte:function update(self, dt)
self.time = self.time + dt -- <1>
if self.time >= 1.0 / self.speed then -- <2>
local head = self.segments[#self.segments] -- <3>
local newhead = {
x = head.x + self.dir.x,
y = head.y + self.dir.y
} -- <4>
table.insert(self.segments, newhead) -- <5>
local tail = table.remove(self.segments, 1) -- <6>
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 0) -- <7>
for i, s in ipairs(self.segments) do -- <8>
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2) -- <9>
end
self.time = 0 -- <10>
end
end
Neste código, nós:
update() foi invocada — o chamado “delta time”, ou dt.# é o operador usado para obter o comprimento de uma tabela quando ela é usada como array, que é o nosso caso — todos os segmentos são valores de tabela sem chave especificada.self.dir).#grid tem apenas 1 camada chamada layer1.i definido para a posição na tabela (começando em 1) e s definido para o segmento atual.Se executar o jogo agora, você deve ver a cobra de 4 segmentos rastejar da esquerda para a direita pelo campo de jogo.

Concluído!
Antes de adicionar código para reagir à entrada do jogador, você precisa configurar as conexões de entrada.
input o arquivo game.input_binding e dê double click para abri-lo.
O arquivo de mapeamento de entrada mapeia a entrada real do usuário (teclas, movimentos do mouse etc.) para nomes de ação que são fornecidos aos scripts que solicitaram entrada.
Concluído!
Com os mapeamentos no lugar, abra snake.script e adicione a linha a seguir no início da função init():
function init(self)
msg.post(".", "acquire_input_focus") -- <1>
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.speed = 7.0
self.time = 0
end
A linha adicionada:
Depois encontre a função on_input e digite o seguinte código:
function on_input(self, action_id, action)
if action_id == hash("up") and action.pressed then -- <1>
self.dir.x = 0 -- <2>
self.dir.y = 1
elseif action_id == hash("down") and action.pressed then
self.dir.x = 0
self.dir.y = -1
elseif action_id == hash("left") and action.pressed then
self.dir.x = -1
self.dir.y = 0
elseif action_id == hash("right") and action.pressed then
self.dir.x = 1
self.dir.y = 0
end
end
Esses ramos if...elseif... fazem o seguinte:
action tiver o campo pressed definido como true (o jogador pressionou a tecla), então:Execute o jogo novamente e confira se consegue controlar a cobra.
Concluído!
Agora, observe que, se você pressionar duas teclas simultaneamente, isso resultará em duas chamadas para on_input(), uma para cada tecla pressionada. Como o código acima está escrito, apenas a chamada que acontecer por último terá efeito na direção da cobra, já que chamadas subsequentes a on_input() sobrescreverão os valores em self.dir.
Observe também que, se a cobra se move para a esquerda e você pressiona a tecla right, ela virará para dentro de si mesma. A correção aparentemente óbvia para esse problema é adicionar uma condição extra às cláusulas if em on_input():
if action_id == hash("up") and self.dir.y ~= -1 and action.pressed then
...
elseif action_id == hash("down") and self.dir.y ~= 1 and action.pressed then
...
Porém, se a cobra estiver se movendo para a esquerda e o jogador pressionar rapidamente primeiro up e depois right antes do próximo passo de movimento acontecer, apenas o pressionamento de right terá efeito e a cobra se moverá para dentro de si mesma. Com as condições adicionadas às cláusulas if mostradas acima, a entrada será ignorada. Nada bom!
Uma solução adequada para esse problema é armazenar a entrada em uma fila e retirar entradas dessa fila conforme a cobra se move:
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.speed = 7.0
self.time = 0
self.dirqueue = {} -- <1>
end
Desta vez, nós:
self.dirqueue, inicializada como uma tabela vazia.Na função update(), adicione:
function update(self, dt)
self.time = self.time + dt
if self.time >= 1.0 / self.speed then
local newdir = table.remove(self.dirqueue, 1) -- <1>
if newdir then
local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y -- <2>
if not opposite then
self.dir = newdir -- <3>
end
end
local head = self.segments[#self.segments]
local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}
table.insert(self.segments, newhead)
local tail = table.remove(self.segments, 1)
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 0)
for i, s in ipairs(self.segments) do
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
end
self.time = 0
end
end
newdir não é nulo), verifique se newdir aponta no sentido oposto a self.dir.E modifique on_input para armazenar a entrada atual na fila:
function on_input(self, action_id, action)
if action_id == hash("up") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = 1}) -- <1>
elseif action_id == hash("down") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = -1})
elseif action_id == hash("left") and action.pressed then
table.insert(self.dirqueue, {x = -1, y = 0})
elseif action_id == hash("right") and action.pressed then
table.insert(self.dirqueue, {x = 1, y = 0})
end
end
self.dir diretamente.Inicie o jogo e confira se ele se comporta como esperado.
Concluído!
A cobra precisa de comida no mapa para poder ficar longa e rápida. Vamos adicionar isso!
Acima da função init(), adicione uma nova função:
local function put_food(self) -- <1>
self.food = {x = math.random(2, 47), y = math.random(2, 47)} -- <2>
tilemap.set_tile("#grid", "layer1", self.food.x, self.food.y, 3) -- <3>
end
Nesta função, nós:
put_food() que coloca um pedaço de comida no mapa.self.food.Depois chame-a no final da função init():
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.dirqueue = {}
self.speed = 7.0
self.time = 0
math.randomseed(socket.gettime()) -- <1>
put_food(self) -- <2>
end
math.random(), defina a seed aleatória; caso contrário, a mesma série de valores aleatórios será gerada. Essa seed deve ser definida apenas uma vez.put_food() no início do jogo para que o jogador comece com um item de comida no mapa.Concluído!
Agora, detectar se a cobra colidiu com algo é apenas uma questão de olhar o que há no tilemap para onde a cobra está indo e reagir.
Adicione uma variável que acompanha se a cobra está viva ou não:
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.dirqueue = {}
self.speed = 7.0
self.time = 0
self.alive = true -- <1>
math.randomseed(socket.gettime())
put_food(self)
end
Depois adicione lógica que testa colisão com parede/obstáculo e comida:
function update(self, dt)
self.time = self.time + dt
if self.time >= 1.0 / self.speed and self.alive then -- <1>
local newdir = table.remove(self.dirqueue, 1)
if newdir then
local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y
if not opposite then
self.dir = newdir
end
end
local head = self.segments[#self.segments]
local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}
table.insert(self.segments, newhead)
local tile = tilemap.get_tile("#grid", "layer1", newhead.x, newhead.y) -- <2>
if tile == 2 or tile == 4 then
self.alive = false -- <3>
elseif tile == 3 then
self.speed = self.speed + 1 -- <4>
put_food(self)
else
local tail = table.remove(self.segments, 1) -- <5>
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 1)
end
for i, s in ipairs(self.segments) do
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
end
self.time = 0
end
end
Agora experimente o jogo e certifique-se de que ele funciona bem!
Isso conclui o tutorial, mas continue experimentando o jogo e trabalhando em alguns dos exercícios abaixo!
Concluído!
Aqui está o código completo do script para referência:
local function put_food(self)
self.food = {x = math.random(2, 47), y = math.random(2, 47)}
tilemap.set_tile("#grid", "layer1", self.food.x, self.food.y, 3)
end
function init(self)
msg.post(".", "acquire_input_focus")
self.segments = {
{x = 7, y = 24},
{x = 8, y = 24},
{x = 9, y = 24},
{x = 10, y = 24}
}
self.dir = {x = 1, y = 0}
self.dirqueue = {}
self.speed = 7.0
self.time = 0
self.alive = true
math.randomseed(socket.gettime())
put_food(self)
end
function update(self, dt)
self.time = self.time + dt
if self.time >= 1.0 / self.speed and self.alive then
local newdir = table.remove(self.dirqueue, 1)
if newdir then
local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y
if not opposite then
self.dir = newdir
end
end
local head = self.segments[#self.segments]
local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}
table.insert(self.segments, newhead)
local tile = tilemap.get_tile("#grid", "layer1", newhead.x, newhead.y)
if tile == 2 or tile == 4 then
self.alive = false
elseif tile == 3 then
self.speed = self.speed + 1
put_food(self)
else
local tail = table.remove(self.segments, 1)
tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 1)
end
for i, s in ipairs(self.segments) do
tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
end
self.time = 0
end
end
function on_input(self, action_id, action)
if action_id == hash("up") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = 1})
elseif action_id == hash("down") and action.pressed then
table.insert(self.dirqueue, {x = 0, y = -1})
elseif action_id == hash("left") and action.pressed then
table.insert(self.dirqueue, {x = -1, y = 0})
elseif action_id == hash("right") and action.pressed then
table.insert(self.dirqueue, {x = 1, y = 0})
end
end
É um bom exercício tentar implementar estas melhorias: