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 te guía por el proceso de crear uno de los juegos clásicos más comunes que puedes intentar recrear. Hay muchas variaciones de este juego; esta presenta una serpiente que come “comida” y que solo crece cuando come. Esta serpiente también se arrastra por un campo de juego que contiene obstáculos.
![]()
En este tutorial aprenderás a:
Este tutorial está diseñado para principiantes, pero si eres completamente nuevo en Defold y el desarrollo de juegos, recomendamos leer primero algunos de los manuales introductorios, especialmente sobre los bloques de construcción de Defold y el glosario. Si todavía no has descargado Defold, revisa el manual de instalación. También se recomienda revisar la visión general del editor para entrar rápidamente en el editor, pero aquí también proporcionamos capturas de pantalla para cada paso.
Inicia Defold y:

¡Hecho!
Empezaremos definiendo la resolución del juego.
game.project en el lado izquierdo, en el panel Assets. Haz doble click en él para abrirlo.game.project.Width y Height) en 768⨉768 o algún otro múltiplo de 16.
La razón por la que quieres hacer esto es que el juego se dibujará sobre una cuadrícula donde cada segmento será de 16x16 pixels, y de esta manera la pantalla del juego no cortará segmentos parciales. El archivo game.project contiene todas las configuraciones importantes del proyecto; puedes leer sobre todas ellas en el manual de configuración del proyecto.
¡Hecho!
Se necesita muy poco en términos de gráficos para un clon minimalista de Snake. Un segmento verde de 16⨉16 para la serpiente, un bloque blanco para los obstáculos y un bloque rojo más pequeño que representa la comida.
Primero, crea un directorio para los assets en el editor Defold:
mainNew Folder.assets y haz click en Create Folder.
¡Hecho!
La imagen de abajo es el único asset que necesitas:


También puedes leer más detalles sobre importar assets aquí.
¡Hecho!
Defold proporciona un componente Tile Map integrado que usarás para crear el campo de juego formado por tiles alineados en una cuadrícula. Un tile map te permite definir y leer tiles individuales, lo que se ajusta perfectamente a este juego. Como los tile maps obtienen sus gráficos de un Tile Source, necesitas crear uno:
assets.New ▸ Tile Source en la sección “Resources”.snake.tilesource).
El tile source se abrirá en un Tile Source Editor dedicado para este tipo de archivo, y se te pedirá que proporciones una imagen para que funcione. En el lado derecho puedes encontrar un panel Properties:
Define la propiedad Image con el archivo de gráficos que acabas de importar.

Las propiedades Width y Height deben mantenerse en 16 (valor predeterminado). Esto dividirá la imagen de 32⨉32 pixels en 4 tiles, numerados 1–4.

Ten en cuenta que la propiedad Extrude Borders está definida en 2 pixels. Esto es para evitar artefactos visuales alrededor de los tiles que tienen gráficos hasta el borde.
Si haces algún cambio en un archivo, aparece una marca de asterisco * junto a su nombre en su pestaña. Selecciona File ▸ Save All o usa el atajo Ctrl+S (⌘Cmd + S en Mac) para guardar todos los archivos.
¡Hecho!
Ahora tienes un tile source listo para usar, así que es momento de crear el componente tile map del campo de juego:
Haz Right click en la carpeta main y selecciona New ▸ Tile Map en la sección “Components”. Nombra el nuevo archivo “grid” (el editor guardará el archivo como “grid.tilemap”).

Se abrirá en un Tile Map Editor, y resaltará que necesita un Tile Source, así que define la propiedad Tile Source al “snake.tilesource” creado previamente.

¡Hecho!
Defold solo almacena el área del tile map que realmente se usa, así que necesitas agregar suficientes tiles para llenar los límites de la pantalla.
layer1 en el panel Outline del lado derecho.Elige la opción de menú Edit ▸ Select Tile... o el atajo Space para mostrar la paleta de tiles, luego haz click en el tile que quieras usar al pintar.


Necesitarás un tile map de tamaño 48x48 tiles (porque nuestro display es 768 y tenemos tiles de 16px, así que 768/16 = 48) para llenar la pantalla del juego.
Guarda el tile map cuando termines.
¡Hecho!
Ahora necesitamos agregar nuestro tile map al juego. Si estás familiarizado con los bloques de construcción de Defold, los componentes son parte de los objetos de juego y los objetos de juego se pueden definir en las colecciones.
Abre main.collection haciendo doble click en él en el panel Assets. En la plantilla Empty Project, esta es por defecto la colección bootstrap que se carga al iniciar el motor.
Haz Right click en la raíz en Outline y selecciona Add Game Object, lo que crea un nuevo objeto de juego en la colección que se carga cuando empieza el juego.

Haz Right click en el nuevo objeto de juego y selecciona Add Component File. Elige el archivo “grid.tilemap” que acabas de crear.

Ahora tenemos un tile map en nuestra colección de juego. Debería ser visible cuando ejecutas el juego desde el editor.
Project ▸ Build o el atajo Ctrl + B (⌘Cmd + B en Mac).
¡Hecho!
Haz Right click en la carpeta main en el navegador Assets y selecciona New ▸ Script en la sección Scripts. Nombra el nuevo archivo script “snake” (se guardará como “snake.script”). Este archivo contendrá toda la lógica del juego.

Vuelve a main.collection y haz right click en el objeto de juego que contiene el tile map. Selecciona Add Component File y elige el archivo “snake.script”.

Ahora tienes el componente tile map y el script en su lugar.
¡Hecho!
El script que vas a escribir controlará todo el juego. Iremos agregando funcionalidades una por una.
La idea de cómo funcionará es la siguiente:
Abre snake.script y localiza la función init(). Esta función es llamada por el motor cuando el script se inicializa al iniciar el juego. Cambia el código a lo siguiente:
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
En este código:
self.segments que contiene una lista de tablas, cada una con una posición X e Y para un segmento.self.dir con una dirección X e Y.self.speed, expresada como tiles por segundo.self.time que se usará para seguir la velocidad de movimiento.El código de script anterior está escrito en el lenguaje Lua. Hay algunas cosas que tener en cuenta sobre el código, pero si todavía no entiendes algo de lo siguiente, no te preocupes. Solo acompaña, experimenta y dale tiempo — eventualmente lo entenderás. Por ahora, puedes recordar que en init() solo inicializamos las variables que usaremos.
self. La referencia self se usa para almacenar datos de instancia.self se puede usar como una tabla Lua en la que puedes almacenar datos. Solo usa la notación de punto como harías con cualquier otra tabla: self.data = "value". La referencia es válida durante toda la vida del script, en este caso desde el inicio del juego hasta que lo cierras.{}.{x = 10, y = 20}), tablas Lua anidadas ({ {a = 1}, {b = 2} }) u otros tipos de datos.¡Hecho!
La función init() se llama exactamente una vez, cuando el componente script se instancia en el juego en ejecución. La función update(), sin embargo, se llama una vez cada frame. Eso hace que la función sea ideal para lógica de juego en tiempo real.
La idea para update es esta: en algún intervalo definido, haz lo siguiente:

:::sidenote Ten en cuenta que nuestra cabeza de la serpiente está al final de la tabla, y la cola al principio. :::
update() en snake.script y cambia el código a lo siguiente: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
En este código:
update() — el llamado “delta time”, o dt.# es el operador usado para obtener la longitud de una tabla cuando se usa como arreglo, que es nuestro caso — todos los segmentos son valores de tabla sin clave especificada.self.dir).#grid tiene solo 1 capa llamada layer1.i definido a la posición en la tabla (empezando desde 1) y s definido al segmento actual.Si ejecutas el juego ahora, deberías ver la serpiente de 4 segmentos de largo arrastrarse de izquierda a derecha sobre el campo de juego.

¡Hecho!
Antes de agregar código para reaccionar al input del jugador, necesitas configurar las conexiones de input.
input el archivo game.input_binding y haz double click para abrirlo.
El archivo de input binding mapea la entrada real del usuario (teclas, movimientos del mouse, etc.) a nombres de acción que se entregan a los scripts que han solicitado input.
¡Hecho!
Con los bindings en su lugar, abre snake.script y agrega la siguiente línea al principio de la función 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 línea agregada:
Luego encuentra la función on_input y escribe el siguiente 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
Estas ramas if...elseif... hacen lo siguiente:
action tiene el campo pressed definido en true (el jugador presionó la tecla), entonces:Ejecuta el juego de nuevo y comprueba que puedes dirigir la serpiente.
¡Hecho!
Ahora, observa que si presionas dos teclas simultáneamente, eso resultará en dos llamadas a on_input(), una por cada pulsación. Tal como está escrito el código anterior, solo la llamada que ocurra al final tendrá efecto en la dirección de la serpiente, ya que las llamadas posteriores a on_input() sobrescribirán los valores en self.dir.
Ten en cuenta también que si la serpiente se mueve a la izquierda y presionas la tecla right, la serpiente se dirigirá hacia sí misma. La solución aparentemente obvia a este problema es agregar una condición adicional a las cláusulas if en 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
...
Sin embargo, si la serpiente se mueve a la izquierda y el jugador presiona rápidamente primero up, luego right antes de que ocurra el siguiente paso de movimiento, solo la pulsación de right tendrá efecto y la serpiente se moverá hacia sí misma. Con las condiciones agregadas a las cláusulas if mostradas arriba, el input se ignorará. ¡No está bien!
Una solución adecuada a este problema es almacenar el input en una cola y extraer entradas de esa cola a medida que la serpiente se mueve:
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
Esta vez:
self.dirqueue que se inicializa como una tabla vacía.En la función update() agrega:
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 no es null), comprueba si newdir apunta en sentido opuesto a self.dir.Y modifica on_input para almacenar el input actual en la cola en su lugar:
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 directamente.Inicia el juego y comprueba que se juega como se espera.
¡Hecho!
La serpiente necesita comida en el mapa para poder crecer larga y rápida. ¡Agreguemos eso!
Encima de la función init() agrega una nueva función:
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
En esta función:
put_food() que coloca una pieza de comida en el mapa.self.food.Luego llámala al final de la función 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(), define la semilla aleatoria; de lo contrario se generará la misma serie de valores aleatorios. Esta semilla solo debe definirse una vez.put_food() al iniciar el juego para que el jugador empiece con una comida en el mapa.¡Hecho!
Ahora, detectar si la serpiente ha colisionado con algo es solo cuestión de mirar qué hay en el tile map hacia donde se dirige la serpiente y reaccionar.
Agrega una variable que mantenga registro de si la serpiente está viva o no:
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
Luego agrega lógica que pruebe colisión con muro/obstáculo y 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
Ahora prueba el juego y asegúrate de que se juega bien.
Esto concluye el tutorial, pero sigue experimentando con el juego y trabaja en algunos de los ejercicios de abajo.
¡Hecho!
Este es el código completo del script como referencia:
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
Es un buen ejercicio intentar implementar estas mejoras: