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
En este tutorial empezamos con un proyecto vacío y construimos un juego runner completo con un personaje animado, colisión física, pickups y puntuación.
Hay mucho que asimilar al aprender un motor de juegos nuevo, así que creamos este tutorial para ayudarte a empezar. Es un tutorial bastante completo que recorre cómo funcionan el motor y el editor. Asumimos que tienes cierta familiaridad con la programación.
Si necesitas una introducción a la programación Lua, revisa nuestro manual Lua en Defold.
Si sientes que este tutorial es demasiado para empezar, revisa nuestra página de tutoriales, donde tenemos una selección de tutoriales de dificultad variada.
Si prefieres ver tutoriales en video, revisa la versión en video en Youtube.
Usamos assets de juego de otros dos tutoriales, con algunas modificaciones pequeñas. El tutorial se divide en varios pasos, donde cada parte nos lleva un paso importante hacia el juego final.
El resultado final será un juego donde controlas un personaje héroe que corre por un entorno, recogiendo monedas y evitando obstáculos. El personaje héroe corre a velocidad fija y el jugador controla solo el salto del héroe pulsando un único botón (o tocando la pantalla en un dispositivo móvil). El nivel consiste en un flujo interminable de plataformas sobre las que saltar, y monedas para recoger.
Si en algún punto te quedas atascado en este tutorial o al crear tu juego, no dudes en pedirnos ayuda en el Defold Forum. En el foro puedes discutir sobre Defold, pedir ayuda al equipo de Defold, ver cómo otros desarrolladores de juegos resolvieron sus problemas y encontrar nueva inspiración. Empieza ahora.
A lo largo del tutorial, las descripciones detalladas sobre conceptos y cómo hacer ciertos momentos se marcan como este párrafo. Si sientes que estas secciones entran en demasiado detalle, puedes saltarlas.
Así que empecemos. Esperamos que te diviertas mucho recorriendo este tutorial y que te ayude a ponerte en marcha con Defold.
Descarga los assets para este tutorial aquí.
El primer paso es descargar los siguientes archivos.
Ahora, si todavía no has descargado e instalado el editor Defold, es momento de hacerlo:
Ve a la página de descarga de Defold donde encontrarás botones de descarga para macOS, Windows y Linux (Ubuntu):

Para iniciar el editor, abre tu carpeta de “Applications” y haz doble click al archivo “Defold”.

D:\Defold). No deberías mover Defold a C:\Program Files (x86)\ o C:\Program Files\, ya que esto impedirá que el editor se actualice.Para iniciar el editor, abre la carpeta “Defold” y doble click al ejecutable “Defold.exe”.

Desde una terminal, localiza el archivo “Defold-x86_64-linux.zip” y extráelo a un directorio llamado “Defold”.
$ unzip Defold-x86_64-linux.zip -d Defold
Para iniciar el editor, cambia el directorio a donde quieras extraer la aplicación, entonces arranca el ejecutable Defold, o hazle doble click en tu escritorio.
$ cd Defold
$ ./Defold
Hay una ayuda para instalar una entrada de escritorio en el menú Help > Create Desktop Entry.
Si presentas problemas iniciando el editor, abriendo un proyecto o corriendo un juego de Defold por favor refiere a la sección del FAQ de Linux.
Cada versión beta y estable de Defold también está disponible en GitHub.
Cuando el editor esté instalado e iniciado, es momento de crear un proyecto nuevo y dejarlo listo. Crea un proyecto nuevo desde la plantilla “Empty Project”.
Este tutorial usa funcionalidades de Spine de la Spine Extension. Agrega la extensión a la sección de dependencias de game.project.
La primera vez que inicias el editor, el editor empieza en blanco, sin ningún proyecto abierto, así que elige Open Project desde el menú y selecciona tu proyecto recién creado. También se te pedirá crear una “branch” para el proyecto.
Ahora, en el Assets pane verás todos los archivos que forman parte del proyecto. Si haces doble click en el archivo “main/main.collection”, el archivo se abrirá en la vista del editor en el centro:

El editor consta de las siguientes áreas principales:
print() y pprint() desde tus scripts. Si tu app o juego no inicia, la consola es lo primero que debes revisar. Detrás de la consola hay un conjunto de pestañas que muestran información de errores, así como un editor de curvas que se usa al crear efectos de partículas.La plantilla de proyecto “Empty” en realidad está completamente vacía. Aun así, selecciona Project ▸ Build para compilar el proyecto y lanzar el juego.

Una pantalla negra quizá no sea muy emocionante, pero es una aplicación de juego Defold en ejecución y podemos modificarla fácilmente para convertirla en algo más interesante. Así que hagamos eso.
El editor Defold trabaja con archivos. Al hacer doble click en un archivo en el Assets pane, lo abres en un editor adecuado. Luego puedes trabajar con el contenido del archivo.
Cuando termines de editar un archivo, tienes que guardarlo. Selecciona File ▸ Save en el menú principal. El editor da una pista agregando un asterisco ‘*’ al nombre del archivo en la pestaña para cualquier archivo que contenga cambios sin guardar.

Antes de empezar, configuremos varios ajustes para nuestro proyecto. Abre el asset game.project desde el Assets Pane y desplázate hacia abajo hasta la sección Display. Define width y height del proyecto en 1280 y 720, respectivamente.
También necesitas agregar la extensión Spine al proyecto para que podamos animar el personaje héroe. Agrega una versión de la extensión Spine que sea compatible con la versión del editor Defold que tienes instalada. Las versiones disponibles de Spine se pueden ver aquí:
https://github.com/defold/extension-spine/releases
Haz click derecho en el enlace al archivo zip del release que quieras usar:

Agrega el enlace al release a tu lista de dependencias de game.project. Cuando se haya agregado la extensión Spine, también necesitas reiniciar el editor para activar la integración del editor incluida con la extensión Spine.
Demos los primeros pasos y creemos una arena para nuestro personaje, o más bien una pieza de suelo con desplazamiento. Lo hacemos en unos pocos pasos.
Un Atlas es un archivo que combina un conjunto de imágenes separadas en un archivo de imagen más grande. La razón para hacer esto es ahorrar espacio y también ganar rendimiento. Puedes leer más sobre Atlas y otras funcionalidades de gráficos 2D en la documentación de gráficos 2D.


¿Por qué no funciona!? Un problema común que tienen las personas al empezar con Defold es olvidarse de guardar. Después de agregar imágenes a un atlas, necesitas guardar el archivo antes de poder acceder a esa imagen.
Crea un archivo de colección ground.collection para el suelo y agrégale 7 objetos de juego (haz click derecho en la raíz de la colección en la vista Outline y selecciona Add Game Object). Nombra los objetos “ground0”, “ground1”, “ground2”, etc., cambiando la propiedad Id en la vista Properties. Ten en cuenta que Defold asigna automáticamente un id único a los objetos de juego nuevos.
En cada objeto, agrega un componente sprite (haz click derecho en el objeto de juego en la vista Outline y selecciona Add Component, luego selecciona Sprite y haz click en OK), define la propiedad Image del componente sprite al atlas que acabas de crear y define la animación predeterminada del sprite como una de las dos imágenes de suelo. Define la posición X del componente sprite (no del objeto de juego) en 190 y la posición Y en 40. Como el ancho de la imagen es 380 pixels y la desplazamos lateralmente la mitad de esos pixels, el pivote del objeto de juego quedará en el borde más izquierdo de la imagen del sprite.


Probablemente lo más fácil sea crear un objeto de juego completo escalado con un componente sprite y luego copiarlo. Márcalo en la vista Outline, luego selecciona Edit ▸ Copy y después Edit ▸ Paste.
Vale la pena notar que si quieres tiles más grandes o más pequeños, puedes simplemente cambiar la escala. Sin embargo, al hacerlo también tendrás que cambiar las posiciones X de todos los objetos de juego del suelo a múltiplos del nuevo ancho.
Guarda el archivo, luego agrega ground.collection al archivo main.collection: primero haz doble click en el archivo main.collection, luego haz click derecho en el objeto raíz en la vista Outline y selecciona Add Collection From File. En el diálogo, selecciona ground.collection y haz click en OK. Asegúrate de colocar ground.collection en la posición 0, 0, 0 o se desplazará visualmente. Guárdalo.
Inicia el juego (Project ▸ Build para ver que todo está en su lugar.

A estas alturas quizá estés confundido y te preguntes qué son realmente todas estas cosas que hemos estado creando, así que tomemos un momento para ver los bloques de construcción más básicos en cualquier proyecto Defold:
Por ahora estas descripciones probablemente sean suficientes. Sin embargo, puedes encontrar un recorrido mucho más completo por estas cosas en el manual de bloques de construcción. Es buena idea visitar ese manual más adelante para obtener una comprensión más profunda de cómo funcionan las cosas en Defold.
Ahora que tenemos todas las piezas del suelo en su lugar, es bastante simple hacer que se muevan. La idea es esta: mover las piezas de derecha a izquierda y, cuando una pieza llegue al borde izquierdo fuera de la pantalla, moverla a la posición más a la derecha. Mover todos estos objetos de juego requiere un script Lua, así que creemos uno:
-- ground.script
local pieces = { "ground0", "ground1", "ground2", "ground3",
"ground4", "ground5", "ground6" } -- <1>
function init(self) -- <2>
self.speed = 360 -- Velocidad en pixels/s
end
function update(self, dt) -- <3>
for i, p in ipairs(pieces) do -- <4>
local pos = go.get_position(p)
if pos.x <= -228 then -- <5>
pos.x = 1368 + (pos.x + 228)
end
pos.x = pos.x - self.speed * dt -- <6>
go.set_position(pos, p) -- <7>
end
end
init() se llama cuando el objeto de juego cobra vida en el juego. Iniciamos una variable miembro local del objeto que contiene la velocidad del suelo.update() se llama una vez por frame, típicamente 60 veces por segundo. dt contiene el número de segundos desde la última llamada.dt para obtener velocidad independiente del framerate en pixels/s.Defold es un núcleo de motor rápido que gestiona tus datos y objetos de juego. Cualquier lógica o comportamiento que necesites para tu juego se crea en el lenguaje Lua. Lua es un lenguaje de programación rápido y ligero que es excelente para escribir lógica de juego. Hay grandes recursos disponibles para aprender el lenguaje, como el libro Programming in Lua y el manual de referencia oficial de Lua.
Defold agrega un conjunto de API encima de Lua, así como un sistema de paso de mensajes que te permite programar comunicaciones entre objetos de juego. Consulta el manual de paso de mensajes para detalles sobre cómo funciona.
Puedes alternar las secciones Assets Pane, Console y Outline del editor usando las teclas F6, F7 y F8, respectivamente
Ahora que tenemos un archivo script, debemos agregar una referencia a él en un componente de un objeto de juego. De esa manera, el script se ejecutará como parte del ciclo de vida del objeto de juego. Hacemos esto creando un nuevo objeto de juego en ground.collection y agregando un componente Script al objeto que se refiere al archivo script Lua que acabamos de crear:

Ahora, cuando ejecutes el juego, el objeto de juego “controller” ejecutará el script en su componente Script, haciendo que el suelo se desplace suavemente por la pantalla.
El personaje héroe será un objeto de juego formado por los siguientes componentes:
Empieza importando las imágenes de las partes del cuerpo, luego agrégalas a un atlas nuevo que llamaremos hero.atlas:

También necesitamos importar los datos de animación Spine y configurar una Spine Scene para ellos:

El archivo hero.spinejson ha sido exportado en formato Spine JSON. Necesitarás el software de animación Spine para poder crear dichos archivos. Si quieres usar otro software de animación, puedes exportar tus animaciones como sprite-sheets y usarlas como animaciones flip-book desde recursos Tile Source o Atlas. Consulta el manual sobre Animación para más información.
Ahora podemos empezar a construir el gameobject del héroe:

Ahora es momento de agregar físicas para que funcione la colisión:
Colisión “Kinematic” significa que queremos que las colisiones se registren, pero que el motor de físicas no resuelva las colisiones automáticamente ni simule los objetos. El motor de físicas soporta varios tipos distintos de objetos de colisión. Puedes leer más sobre ellos en la documentación de Physics.
Es importante que especifiquemos con qué debe interactuar el objeto de colisión:
Finalmente, crea un nuevo archivo hero.script y agrégalo al objeto de juego.
handle_geometry_contact().)
La razón por la que manejamos la colisión nosotros mismos es que si en su lugar definiéramos el tipo del objeto de colisión del personaje como dynamic, el motor realizaría una simulación newtoniana de los cuerpos involucrados. Para un juego como este, dicha simulación está lejos de ser óptima, así que en lugar de pelear con el motor de físicas usando varias fuerzas, tomamos control total.
Ahora, para hacer eso y manejar bien la colisión se necesita un poco de matemática vectorial. En la documentación de Physics se da una explicación detallada sobre cómo resolver colisiones cinemáticas.
-- gravedad que tira del jugador hacia abajo en unidades de pixel/sˆ2
local gravity = -20
-- velocidad de despegue al saltar en unidades de pixel/s
local jump_takeoff_speed = 900
function init(self)
-- esto le dice al motor que envíe input a on_input() en este script
msg.post(".", "acquire_input_focus")
-- guarda la posición inicial
self.position = go.get_position()
-- lleva registro del vector de movimiento y de si hay contacto con el suelo
self.velocity = vmath.vector3(0, 0, 0)
self.ground_contact = false
end
function final(self)
-- Devuelve el foco de input cuando se elimina el objeto
msg.post(".", "release_input_focus")
end
function update(self, dt)
local gravity = vmath.vector3(0, gravity, 0)
if not self.ground_contact then
-- Aplica gravedad si no hay contacto con el suelo
self.velocity = self.velocity + gravity
end
-- aplica velocidad al personaje del jugador
go.set_position(go.get_position() + self.velocity * dt)
-- reinicia estado volátil
self.correction = vmath.vector3()
self.ground_contact = false
end
local function handle_geometry_contact(self, normal, distance)
-- proyecta el vector de corrección sobre la normal de contacto
-- (el vector de corrección es el vector 0 para el primer punto de contacto)
local proj = vmath.dot(self.correction, normal)
-- calcula la compensación que necesitamos hacer para este punto de contacto
local comp = (distance - proj) * normal
-- súmala al vector de corrección
self.correction = self.correction + comp
-- aplica la compensación al personaje del jugador
go.set_position(go.get_position() + comp)
-- comprueba si la normal apunta suficientemente hacia arriba para considerar que el jugador está en el suelo
-- (0.7 equivale aproximadamente a 45 grados de desviación desde una dirección puramente vertical)
if normal.y > 0.7 then
self.ground_contact = true
end
-- proyecta la velocidad sobre la normal
proj = vmath.dot(self.velocity, normal)
-- si la proyección es negativa, significa que parte de la velocidad apunta hacia el punto de contacto
if proj < 0 then
-- elimina ese componente en ese caso
self.velocity = self.velocity - proj * normal
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- comprueba si recibimos un mensaje de punto de contacto. Un mensaje por cada punto de contacto
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
local function jump(self)
-- solo permite saltar desde el suelo
if self.ground_contact then
-- define la velocidad de despegue
self.velocity.y = jump_takeoff_speed
end
end
local function abort_jump(self)
-- corta el salto si todavía estamos subiendo
if self.velocity.y > 0 then
-- reduce la velocidad hacia arriba
self.velocity.y = self.velocity.y * 0.5
end
end
function on_input(self, action_id, action)
if action_id == hash("jump") or action_id == hash("touch") then
if action.pressed then
jump(self)
elseif action.released then
abort_jump(self)
end
end
end
Si quieres, ahora puedes probar y agregar temporalmente el personaje héroe a la colección principal y ejecutar el juego para verlo caer a través del mundo.
Lo último que necesitamos para que el héroe sea funcional es input. El script anterior ya contiene una función on_input() que responde a las acciones “jump” y “touch” (para pantallas táctiles). Agreguemos input bindings para estas acciones.

Ahora que tenemos un personaje héroe configurado con colisión y todo, también necesitamos agregar colisión al suelo para que el personaje héroe tenga algo con lo que colisionar (o sobre lo que correr). Lo haremos en un segundo, pero primero deberíamos hacer un pequeño refactor y poner todo lo del nivel en una colección separada y limpiar un poco la estructura de archivos:
Como quizá ya hayas notado, la jerarquía de archivos que se ve en el Assets pane está desacoplada de la estructura de contenido que construyes en tus colecciones. Los archivos individuales se referencian desde archivos de colección y de objetos de juego, pero su ubicación es completamente arbitraria.
Si quieres mover un archivo a una nueva ubicación, Defold ayuda actualizando automáticamente las referencias al archivo (refactoring). Al crear una pieza de software compleja, como un juego, es extremadamente útil poder cambiar la estructura del proyecto a medida que crece y cambia. Defold fomenta eso y hace que el proceso sea fluido, así que no tengas miedo de mover tus archivos.
También debemos agregar un objeto de juego controller con un componente script a la colección de nivel:
Abre el archivo script, copia el siguiente código en él y guarda el archivo:
-- controller.script
go.property("speed", 360) -- <1>
function init(self)
msg.post("ground/controller#ground", "set_speed", { speed = self.speed })
end

El objeto de juego “controller” no existe en un archivo sino que se crea in-place en la colección de nivel. Esto significa que la instancia del objeto de juego se crea a partir de los datos in-place. Eso está bien para objetos de juego de propósito único como este. Si necesitas múltiples instancias de algún objeto de juego y quieres poder modificar el prototipo/plantilla usado para crear cada instancia, simplemente crea un archivo de objeto de juego y agrega el objeto de juego desde archivo a la colección. Eso crea un objeto de juego con una referencia al archivo como prototipo/plantilla.
Ahora, el propósito de este objeto de juego “controller” es controlar todo lo relacionado con el nivel en ejecución. Pronto, este script se encargará de generar plataformas y monedas con las que el héroe interactuará, pero por ahora solo definirá la velocidad del nivel.
En la función init() del script controller del nivel, envía un mensaje al componente script del objeto controller del suelo, direccionado por su id:
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
El id del objeto de juego controller está definido como "ground/controller" ya que vive en la colección “ground”. Luego agregamos el id de componente "controller" después del carácter hash "#" que separa el id de objeto del id de componente. Ten en cuenta que el script de suelo todavía no tiene ningún código para reaccionar al mensaje set_speed, así que debemos agregar una función on_message() a ground.script y agregar lógica para eso.
-- ground.script
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then -- <1>
self.speed = message.speed -- <2>
end
end

En este punto deberíamos agregar colisión física para el suelo:

Ahora deberías poder probar ejecutar el juego (Project ▸ Build). El personaje héroe debería correr sobre el suelo y debería ser posible saltar con el botón Space. Si ejecutas el juego en un dispositivo móvil, puedes saltar tocando la pantalla.
Para hacer la vida en nuestro mundo de juego un poco menos aburrida, debemos agregar plataformas a las que saltar.
Crea un archivo Script platform.script (haz click derecho en el Assets pane y luego selecciona New ▸ Script File) y pon el siguiente código en el archivo, luego guárdalo:
-- platform.script
function init(self)
self.speed = 540 -- Velocidad predeterminada en pixels/s
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
go.delete() -- <1>
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
end
end

Ten en cuenta que tanto platform.go como platform_long.go tienen componentes Script que se refieren al mismo archivo script. Esto es bueno, ya que cualquier cambio que hagamos al archivo script afectará el comportamiento de las plataformas normales y largas.
La idea del juego es que sea un endless runner simple. Esto significa que los objetos de juego de plataforma no se pueden colocar en una colección en el editor. En su lugar debemos generarlos dinámicamente:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 } -- <1>
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
end
function update(self, dt) -- <2>
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Quizá generar una plataforma a una altura aleatoria
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, 0.6)
msg.post(p, "set_speed", { speed = self.speed })
end
end
end
update() se llama una vez en cada frame y la usamos para decidir si generar una plataforma normal o larga en ciertos intervalos (para evitar solapamientos) y alturas. Es fácil experimentar con varios algoritmos de spawning para crear gameplay diferente.Ahora ejecuta el juego (Project ▸ Build).
Vaya, esto empieza a convertirse en algo (casi) jugable…

Lo primero que vamos a hacer es dar vida al personaje héroe. Ahora mismo el pobre está atrapado en un bucle de correr y no responde bien a los saltos ni a nada. El archivo spine que agregamos desde el paquete de assets en realidad contiene un conjunto de animaciones justo para eso.
update() existente: -- hero.script
local function play_animation(self, anim)
-- solo reproduce animaciones que no se estén reproduciendo ya
if self.anim ~= anim then
-- dile al modelo spine que reproduzca la animación
local anim_props = { blend_duration = 0.15 }
spine.play_anim("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, anim_props)
-- recuerda qué animación se está reproduciendo
self.anim = anim
end
end
local function update_animation(self)
-- asegúrate de que se esté reproduciendo la animación correcta
if self.ground_contact then
play_animation(self, hash("run"))
else
play_animation(self, hash("jump"))
end
end
update() y agrega una llamada a update_animation: ...
-- aplícala al personaje del jugador
go.set_position(go.get_position() + self.velocity * dt)
update_animation(self)
...

Lua tiene “alcance léxico” para variables locales y es sensible al orden en que colocas funciones local. La función update() llama a las funciones locales update_animation() y play_animation(), lo que significa que el runtime debe haber visto las funciones locales para poder llamarlas. Por eso debemos poner las funciones antes de update(). Si cambias el orden de las funciones, obtendrás un error. Ten en cuenta que esto se aplica solo a variables local. Puedes leer más sobre las reglas de alcance y funciones locales de Lua en http://www.lua.org/pil/6.2.html
Eso es todo lo necesario para agregar animaciones de salto y caída al héroe. Si ejecutas el juego, notarás que se siente mucho mejor jugarlo. Quizá también notes que las plataformas desafortunadamente pueden empujar al héroe fuera de la pantalla. Ese es un efecto secundario del manejo de colisiones, pero el remedio es fácil: ¡agregar violencia y hacer peligrosos los bordes de las plataformas!
Guarda el archivo.

Abre hero.go, marca el Collision Object y agrega el nombre “danger” a la propiedad Mask. Luego guarda el archivo.

Abre hero.script y cambia la función on_message() para que tengamos una reacción si el personaje héroe colisiona con un borde “danger”:
-- hero.script
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then
self.velocity = vmath.vector3(0, 0, 0)
self.correction = vmath.vector3()
self.ground_contact = false
self.anim = nil
go.set(".", "euler.z", 0)
go.set_position(self.position)
msg.post("#collisionobject", "enable")
elseif message_id == hash("contact_point_response") then
-- comprueba si recibimos un mensaje de punto de contacto
if message.group == hash("danger") then
-- Morir y reiniciar
play_animation(self, hash("death"))
msg.post("#collisionobject", "disable")
-- <1>
go.animate(".", "euler.z", go.PLAYBACK_ONCE_FORWARD, 160, go.EASING_LINEAR, 0.7)
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("#", "reset")
end)
elseif message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
Cambia la función init() para enviar un mensaje “reset” que inicialice el objeto, luego guarda el archivo:
-- hero.script
function init(self)
-- esto nos permite manejar input en este script
msg.post(".", "acquire_input_focus")
-- guarda posición
self.position = go.get_position()
msg.post("#", "reset")
end
Si pruebas el juego ahora, rápidamente se vuelve evidente que el mecanismo de reset no funciona. El reset del héroe está bien, pero puedes reiniciar fácilmente en una situación donde caerás instantáneamente sobre un borde de plataforma y morirás otra vez. Lo que queremos hacer es reiniciar correctamente todo el nivel al morir. Como el nivel es solo una serie de plataformas generadas, solo necesitamos llevar registro de todas las plataformas generadas y luego eliminarlas al hacer reset:
Abre el archivo controller.script y edita el código para almacenar los ids de todas las plataformas generadas:
-- controller.script
go.property("speed", 360)
local grid = 460
local platform_heights = { 100, 200, 350 }
function init(self)
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
self.gridw = 0
self.spawns = {} -- <1>
end
function update(self, dt)
self.gridw = self.gridw + self.speed * dt
if self.gridw >= grid then
self.gridw = 0
-- Quizá generar una plataforma a una altura aleatoria
if math.random() > 0.2 then
local h = platform_heights[math.random(#platform_heights)]
local f = "#platform_factory"
if math.random() > 0.5 then
f = "#platform_long_factory"
end
local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, 0.6)
msg.post(p, "set_speed", { speed = self.speed })
table.insert(self.spawns, p) -- <1>
end
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("reset") then -- <2>
-- Dile al héroe que se reinicie.
msg.post("hero#hero", "reset")
-- Elimina todas las plataformas
for i,p in ipairs(self.spawns) do
go.delete(p)
end
self.spawns = {}
elseif message_id == hash("delete_spawn") then -- <3>
for i,p in ipairs(self.spawns) do
if p == message.id then
table.remove(self.spawns, i)
go.delete(p)
end
end
end
end
Abre platform.script y modifícalo para que, en lugar de simplemente eliminar una plataforma que alcanzó el borde más izquierdo, envíe un mensaje al controller del nivel pidiendo eliminar la plataforma:
-- platform.script
...
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
...

-- hero.script
...
go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
function()
msg.post("controller#controller", "reset")
end)
...

¡Y ahora el bucle principal de reiniciar-morir está en su lugar!
Lo siguiente: algo por lo que vivir: ¡monedas!
La idea es poner monedas en el nivel para que el jugador las recoja. La primera pregunta que hay que hacer es cómo ponerlas en el nivel. Podemos, por ejemplo, desarrollar un esquema de spawning que esté de algún modo en sintonía con el algoritmo de generación de plataformas. Sin embargo, al final elegimos un enfoque mucho más fácil: que las propias plataformas generen monedas:
Crea un nuevo archivo script coin.script (haz click derecho en level en el Assets pane y selecciona New ▸ Script File). Reemplaza el código de plantilla con lo siguiente:
-- coin.script
function init(self)
self.collected = false
end
function on_message(self, message_id, message, sender)
if self.collected == false and message_id == hash("collision_response") then
self.collected = true
msg.post("#sprite", "disable")
elseif message_id == hash("start_animation") then
pos = go.get_position()
go.animate(go.get_id(), "position.y", go.PLAYBACK_LOOP_PINGPONG, pos.y + 24, go.EASING_INOUTSINE, 0.75, message.delay)
end
end
Agrega el archivo script como componente Script al objeto coin (haz click derecho en la raíz en Outline y selecciona Add Component from File).

El plan es generar las monedas desde los objetos plataforma, así que pon factories para las monedas en platform.go y platform_long.go.

Ahora necesitamos modificar platform.script para que genere y elimine las monedas:
-- platform.script
function init(self)
self.speed = 540 -- Velocidad predeterminada en pixels/s
self.coins = {}
end
function final(self)
for i,p in ipairs(self.coins) do
go.delete(p)
end
end
function update(self, dt)
local pos = go.get_position()
if pos.x < -500 then
msg.post("/level/controller#controller", "delete_spawn", { id = go.get_id() })
end
pos.x = pos.x - self.speed * dt
go.set_position(pos)
end
function create_coins(self, params)
local spacing = 56
local pos = go.get_position()
local x = pos.x - params.coins * (spacing*0.5) - 24
for i = 1, params.coins do
local coin = factory.create("#coin_factory", vmath.vector3(x + i * spacing , pos.y + 64, 1))
msg.post(coin, "set_parent", { parent_id = go.get_id() }) -- <1>
msg.post(coin, "start_animation", { delay = i/10 }) -- <2>
table.insert(self.coins, coin)
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("set_speed") then
self.speed = message.speed
elseif message_id == hash("create_coins") then
create_coins(self, message)
end
end
Las relaciones padre-hijo son estrictamente una modificación del scene graph. Un hijo se transformará (moverá, escalará o rotará) junto con su padre. Si necesitas relaciones de “ownership” adicionales entre objetos de juego, debes rastrearlas específicamente en código.
El último paso de este tutorial es agregar un par de líneas a controller.script:
-- controller.script
...
local platform_heights = { 100, 200, 350 }
local coins = 3 -- <1>
...
-- controller.script
...
local coins = coins
if math.random() > 0.5 then
f = "#platform_long_factory"
coins = coins * 2 -- El doble de monedas en plataformas largas
end
...
-- controller.script
...
msg.post(p, "set_speed", { speed = self.speed })
msg.post(p, "create_coins", { coins = coins })
table.insert(self.spawns, p)
...

¡Y ahora tenemos un juego simple, pero funcional! Si llegas hasta aquí quizá quieras continuar por tu cuenta y agregar lo siguiente:
Descarga la versión completa del proyecto aquí
Esto concluye este tutorial introductorio. Ahora sigue adelante y sumérgete en Defold. Tenemos muchos manuales y tutoriales preparados para guiarte, y si te quedas atascado, eres bienvenido al foro.
¡Feliz Defolding!