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
Questo tutorial ti accompagna nella creazione di uno dei giochi classici più comuni che puoi provare a ricreare. Esistono molte varianti di questo gioco; in questa, un serpente mangia del “cibo” e cresce soltanto quando mangia. Il serpente si muove inoltre su un campo di gioco che contiene ostacoli.
![]()
In questo tutorial imparerai a:
Questo tutorial è pensato per i principianti, ma se non hai mai usato Defold e non hai esperienza nello sviluppo di giochi, ti consigliamo di leggere prima alcuni manuali introduttivi, in particolare quelli sugli elementi fondamentali di Defold e il glossario. Se non hai ancora scaricato Defold, consulta il manuale di installazione. Ti consigliamo anche di consultare la panoramica dell’editor per familiarizzare rapidamente con l’editor stesso; qui, comunque, trovi schermate per ogni passaggio.
Avvia Defold e:

Fatto!
Iniziamo definendo la risoluzione del gioco.
game.project sul lato sinistro, nel pannello Assets. Fai doppio clic sul file per aprirlo.game.project.Width e Height) a 768⨉768 o a un altro multiplo di 16.
Questo passaggio serve perché il gioco verrà disegnato su una griglia in cui ogni segmento misura 16x16 pixel: in questo modo, i bordi dello schermo di gioco non taglieranno alcun segmento. Il file game.project contiene tutte le impostazioni importanti del progetto; puoi scoprirle nel manuale delle impostazioni del progetto.
Fatto!
Per un clone minimalista di Snake servono pochissimi elementi grafici: un segmento verde di 16⨉16 pixel per il serpente, un blocco bianco per gli ostacoli e un blocco rosso più piccolo che rappresenta il cibo.
Per prima cosa, crea una directory per gli asset nell’editor Defold:
mainNew Folder.assets e fai clic su Create Folder.
Fatto!
L’immagine qui sotto è l’unico asset di cui hai bisogno:


Puoi anche trovare maggiori dettagli sull’importazione degli asset qui.
Fatto!
Defold offre un componente mappa di tile (Tile Map) integrato, che userai per creare il campo di gioco composto da tile allineati su una griglia. Una mappa di tile consente di impostare e leggere singoli tile, ed è quindi perfetta per questo gioco. Poiché le mappe di tile ricavano la propria grafica da una sorgente di tile (Tile Source), devi crearne una:
assets.New ▸ Tile Source nella sezione “Resources”.snake.tilesource).
La sorgente di tile si aprirà nell’editor dedicato a questo tipo di file e ti verrà chiesto di assegnarle un’immagine affinché possa funzionare. Sul lato destro trovi il pannello Properties:
Imposta la proprietà Image sul file grafico che hai appena importato.

Le proprietà Width e Height devono rimanere impostate a 16 (valore predefinito). In questo modo l’immagine di 32⨉32 pixel verrà suddivisa in 4 tile, numerati da 1 a 4.

Nota che la proprietà Extrude Borders è impostata a 2 pixel. Questo serve a evitare artefatti visivi attorno ai tile la cui grafica si estende fino al bordo.
Se modifichi un file, nella sua scheda appare un asterisco * accanto al nome. Seleziona File ▸ Save All oppure usa la scorciatoia Ctrl+S (⌘Cmd + S su Mac) per salvare tutti i file.
Fatto!
Ora hai una sorgente di tile pronta all’uso: è il momento di creare il componente mappa di tile del campo di gioco:
Fai clic con il tasto destro sulla cartella main e seleziona New ▸ Tile Map nella sezione “Components”. Assegna al nuovo file il nome “grid” (l’editor lo salverà come “grid.tilemap”).

Il file si aprirà nell’editor delle mappe di tile, che segnalerà la necessità di una Tile Source: imposta quindi la proprietà Tile Source su “snake.tilesource”, creata in precedenza.

Fatto!
Defold memorizza soltanto l’area della mappa di tile effettivamente utilizzata, quindi devi aggiungere abbastanza tile da riempire lo schermo fino ai bordi.
layer1 nel pannello Outline sul lato destro.Scegli l’opzione di menu Edit ▸ Select Tile... oppure la scorciatoia Space per mostrare la tavolozza dei tile, poi fai clic sul tile che vuoi usare per disegnare.


Per riempire lo schermo del gioco, ti servirà una mappa di 48x48 tile (perché lo schermo misura 768 pixel e i tile sono di 16 pixel, quindi 768/16 = 48).
Salva la mappa di tile quando hai finito.
Fatto!
Ora dobbiamo aggiungere la mappa di tile al gioco. Se conosci gli elementi fondamentali di Defold, sai che i componenti fanno parte degli oggetti di gioco (game object) e che gli oggetti di gioco possono essere definiti nelle collezioni (collection).
Apri main.collection facendo doppio clic sul file nel pannello Assets. Nel modello Empty Project, questa è per impostazione predefinita la collezione di bootstrap caricata all’avvio del motore.
Fai clic con il tasto destro sulla radice in Outline e seleziona Add Game Object: questo crea un nuovo oggetto di gioco nella collezione caricata all’avvio del gioco.

Fai clic con il tasto destro sul nuovo oggetto di gioco e seleziona Add Component File. Scegli il file “grid.tilemap” che hai appena creato.

Ora abbiamo una mappa di tile nella collezione del gioco. Dovrebbe essere visibile quando avvii il gioco dall’editor.
Project ▸ Build oppure usa la scorciatoia Ctrl + B (⌘Cmd + B su Mac).
Fatto!
Fai clic con il tasto destro sulla cartella main nel pannello Assets e seleziona New ▸ Script nella sezione Scripts. Assegna al nuovo file script il nome “snake” (verrà salvato come “snake.script”). Questo file conterrà tutta la logica del gioco.

Torna a main.collection e fai clic con il tasto destro sull’oggetto di gioco che contiene la mappa di tile. Seleziona Add Component File e scegli il file “snake.script”.

Ora il componente mappa di tile e lo script sono al loro posto.
Fatto!
Lo script che scriverai gestirà l’intero gioco. Aggiungeremo le funzionalità una alla volta.
L’idea di funzionamento è la seguente:
Apri snake.script e individua la funzione init(). Il motore chiama questa funzione quando lo script viene inizializzato all’avvio del gioco. Sostituisci il codice con il seguente:
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
In questo codice:
self.segments, che contiene un elenco di tabelle, ciascuna con una posizione X e Y per un segmento.self.dir, che contiene una direzione X e Y.self.speed, espressa in tile al secondo.self.time il valore di un timer che verrà usato per gestire la velocità di movimento.Il codice dello script qui sopra è scritto in Lua. Ci sono alcuni aspetti da osservare, ma se non capisci ancora qualcuno dei punti seguenti, non preoccuparti. Continua a seguire il tutorial, sperimenta e prenditi il tempo necessario: con la pratica li capirai. Per ora, ricorda soltanto che in init() abbiamo inizializzato le variabili che useremo.
self. Il riferimento self viene usato per memorizzare i dati dell’istanza.self può essere usato come una tabella Lua in cui memorizzare dati. Basta usare la notazione con il punto come per qualsiasi altra tabella: self.data = "value". Il riferimento è valido per l’intero ciclo di vita dello script, in questo caso dall’avvio del gioco fino alla sua chiusura.{}.{x = 10, y = 20}), tabelle Lua annidate ({ {a = 1}, {b = 2} }) o altri tipi di dati.Fatto!
La funzione init() viene chiamata esattamente una volta, quando il componente script viene istanziato nel gioco in esecuzione. La funzione update(), invece, viene chiamata una volta a ogni fotogramma. Questo la rende ideale per la logica di gioco in tempo reale.
L’idea per l’aggiornamento è questa: a intervalli prestabiliti, esegui le seguenti operazioni:

:::sidenote Ricorda che la testa del nostro serpente si trova alla fine della tabella, mentre la coda si trova all’inizio. :::
update() in snake.script e sostituisci il codice con il seguente: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
In questo codice:
update(), il cosiddetto “delta time”, o dt.# è l’operatore usato per ottenere la lunghezza di una tabella, purché sia usata come array, proprio come in questo caso: tutti i segmenti sono valori della tabella senza una chiave specificata.self.dir).#grid ha un solo livello, chiamato layer1.i contiene la posizione nella tabella (a partire da 1) e s contiene il segmento corrente.Se avvii il gioco ora, dovresti vedere il serpente, lungo 4 segmenti, spostarsi da sinistra a destra sul campo di gioco.

Fatto!
Prima di aggiungere il codice che reagisce all’input del giocatore, devi configurare le associazioni di input.
input, trova il file game.input_binding e fai doppio clic per aprirlo.
Il file dei binding di input associa gli input effettivi dell’utente (tasti, movimenti del mouse e così via) ai nomi delle azioni che vengono passati agli script che hanno richiesto di ricevere input.
Fatto!
Una volta configurati i binding, apri snake.script e aggiungi la seguente riga all’inizio della funzione 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
La riga aggiunta:
Poi individua la funzione on_input e digita il seguente codice:
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
Questi rami if...elseif... eseguono le seguenti operazioni:
action il campo pressed è impostato a true (il giocatore ha premuto il tasto), allora:Avvia di nuovo il gioco e verifica di riuscire a guidare il serpente.
Fatto!
Ora nota che, se premi due tasti contemporaneamente, vengono effettuate due chiamate a on_input(), una per ogni pressione. Con il codice scritto sopra, soltanto l’ultima chiamata ha effetto sulla direzione del serpente, poiché le chiamate successive a on_input() sovrascrivono i valori in self.dir.
Nota anche che, se il serpente si muove verso sinistra e premi il tasto right, il serpente andrà contro sé stesso. La soluzione apparentemente ovvia a questo problema è aggiungere un’ulteriore condizione alle clausole if in 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
...
Tuttavia, se il serpente si muove verso sinistra e il giocatore preme rapidamente prima up, poi right, prima che avvenga il passo di movimento successivo, soltanto la pressione di right avrà effetto e il serpente andrà contro sé stesso. Con le condizioni aggiunte alle clausole if mostrate sopra, l’input verrà ignorato. Non va bene!
Una soluzione corretta a questo problema consiste nel memorizzare gli input in una coda e nell’estrarli man mano che il serpente si muove:
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
Questa volta:
self.dirqueue, inizializzata come tabella vuota.Nella funzione update() aggiungi:
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 non è nullo), controlla se newdir punta nella direzione opposta a self.dir.Modifica inoltre on_input affinché memorizzi l’input corrente nella coda:
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.Avvia il gioco e verifica che funzioni come previsto.
Fatto!
Il serpente ha bisogno di cibo sulla mappa per diventare più lungo e più veloce. Aggiungiamolo!
Sopra la funzione init(), aggiungi una nuova funzione:
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
In questa funzione:
put_food() che posiziona un pezzo di cibo sulla mappa.self.food.Poi chiamala alla fine della funzione 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(), imposta il seme del generatore casuale; altrimenti verrà generata sempre la stessa sequenza di valori. Questo seme va impostato una sola volta.put_food() all’avvio del gioco, in modo che il giocatore inizi con un pezzo di cibo sulla mappa.Fatto!
Ora, per rilevare se il serpente ha urtato qualcosa, basta controllare che cosa si trova sulla mappa di tile nella direzione in cui si sta muovendo e reagire di conseguenza.
Aggiungi una variabile che tenga traccia del fatto che il serpente sia vivo o meno:
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
Poi aggiungi la logica che verifica le collisioni con pareti, ostacoli e cibo:
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
Ora prova il gioco e assicurati che funzioni bene!
Il tutorial termina qui, ma continua a sperimentare con il gioco e prova a svolgere alcuni degli esercizi qui sotto!
Fatto!
Ecco il codice completo dello script come riferimento:
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
Per esercitarti, prova a implementare questi miglioramenti: