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
Dans ce tutoriel, nous partons d’un projet vide pour créer un jeu de course complet avec un personnage animé, des collisions physiques, des objets à ramasser et un score.
Il y a beaucoup à assimiler lorsque l’on découvre un nouveau moteur de jeu. Nous avons donc créé ce tutoriel pour vous aider à démarrer. Assez complet, il vous accompagne dans la découverte du fonctionnement du moteur et de l’éditeur. Nous supposons que vous avez quelques notions de programmation.
Si vous avez besoin d’une introduction à la programmation Lua, consultez notre manuel sur Lua dans Defold.
Si ce tutoriel vous semble un peu trop chargé pour commencer, consultez notre page de tutoriels, qui propose une sélection de tutoriels de différentes difficultés.
Si vous préférez les tutoriels vidéo, consultez la version vidéo sur Youtube.
Nous utilisons des ressources de jeu provenant de deux autres tutoriels, avec quelques petites modifications. Ce tutoriel est divisé en plusieurs étapes, chacune nous rapprochant sensiblement du jeu final.
Au terme de ce tutoriel, vous aurez un jeu dans lequel vous contrôlez un héros qui court dans un environnement, ramasse des pièces et évite des obstacles. Le héros court à une vitesse fixe et le joueur contrôle uniquement ses sauts, en appuyant sur un seul bouton (ou en touchant l’écran sur un appareil mobile). Le niveau se compose d’une succession infinie de plateformes sur lesquelles sauter et de pièces à ramasser.
Si vous êtes bloqué à un moment quelconque de ce tutoriel ou lors de la création de votre jeu, n’hésitez pas à nous demander de l’aide sur le forum Defold. Vous pouvez y discuter de Defold, demander de l’aide à l’équipe Defold, découvrir comment d’autres développeurs de jeux ont résolu leurs problèmes et trouver de nouvelles sources d’inspiration. Lancez-vous dès maintenant.
Tout au long du tutoriel, les descriptions détaillées de concepts et de certaines opérations sont présentées comme ce paragraphe. Si ces sections vous semblent trop détaillées, vous pouvez les passer.
Commençons donc. Nous espérons que vous prendrez beaucoup de plaisir à suivre ce tutoriel et qu’il vous aidera à démarrer avec Defold.
Téléchargez les ressources de ce tutoriel ici.
La première étape consiste à télécharger les fichiers suivants.
Si vous n’avez pas encore téléchargé et installé l’éditeur Defold, c’est le moment de le faire :
Rendez-vous sur la page de téléchargement de Defold, où vous trouverez des boutons Download pour macOS, Windows et Linux (Ubuntu) :

Pour démarrer l’éditeur, ouvrez votre dossier « Applications » et double-cliquez sur le fichier « Defold ».

D:\Defold). Vous ne devriez pas déplacer Defold dans C:\Program Files (x86)\ ou C:\Program Files\, car cela empêchera la mise à jour de l’éditeur.Pour démarrer l’éditeur, ouvrez le dossier « Defold » et double-cliquez sur le fichier « Defold.exe ».

Depuis un terminal, localisez le fichier d’archive « Defold-x86_64-linux.zip » et décompressez-le dans un dossier de destination nommé « Defold ».
$ unzip Defold-x86_64-linux.zip -d Defold
Pour démarrer l’éditeur, placez-vous dans le dossier où vous avez extrait l’application, puis lancez l’exécutable Defold, ou double-cliquez dessus sur votre bureau.
$ cd Defold
$ ./Defold
Le menu Help > Create Desktop Entry propose un assistant pour installer un lanceur d’application.
Si vous rencontrez des problèmes pour démarrer l’éditeur, ouvrir un projet ou exécuter un jeu Defold, consultez la section Linux de la FAQ.
Toutes les versions bêta et stables de Defold sont également disponibles sur GitHub.
Une fois l’éditeur installé et lancé, il est temps de créer un projet et de le préparer. Créez un nouveau projet à partir du modèle “Empty Project”.
Ce tutoriel utilise les fonctionnalités Spine de l’extension Spine. Ajoutez l’extension à la section des dépendances de game.project.
Au premier démarrage, l’éditeur s’affiche vide, sans projet ouvert. Choisissez donc Open Project dans le menu et sélectionnez le projet que vous venez de créer. Il vous sera également demandé de créer une « branche » pour le projet.
Le panneau Assets pane affiche maintenant tous les fichiers du projet. Si vous double-cliquez sur le fichier “main/main.collection”, il s’ouvre dans la vue de l’éditeur au centre :

L’éditeur se compose des principales zones suivantes :
print() et pprint() de vos scripts. Si votre application ou votre jeu ne démarre pas, la console est le premier endroit à vérifier. Derrière la console se trouvent des onglets affichant des informations sur les erreurs, ainsi qu’un éditeur de courbes utilisé pour créer des effets de particules.Le modèle de projet “Empty” est effectivement complètement vide. Sélectionnez tout de même Project ▸ Build pour compiler le projet et lancer le jeu.

Un écran noir n’a peut-être rien de passionnant, mais il s’agit bien d’une application de jeu Defold en cours d’exécution, et nous pouvons facilement la transformer en quelque chose de plus intéressant. Faisons-le.
L’éditeur Defold travaille sur des fichiers. En double-cliquant sur un fichier dans le panneau Assets pane, vous l’ouvrez dans un éditeur adapté. Vous pouvez alors travailler sur son contenu.
Lorsque vous avez terminé de modifier un fichier, vous devez l’enregistrer. Sélectionnez File ▸ Save dans le menu principal. L’éditeur vous le rappelle en ajoutant un astérisque ‘*’ au nom du fichier dans l’onglet de chaque fichier qui contient des modifications non enregistrées.

Avant de commencer, définissons quelques paramètres du projet. Ouvrez la ressource game.project dans Assets Pane et faites défiler jusqu’à la section Display. Définissez les propriétés width et height du projet sur 1280 et 720 respectivement.
Vous devez également ajouter l’extension Spine au projet pour que nous puissions animer le héros. Ajoutez une version de l’extension Spine compatible avec la version de l’éditeur Defold que vous avez installée. Les versions disponibles de Spine sont indiquées ici :
https://github.com/defold/extension-spine/releases
Faites un clic droit sur le lien vers le fichier zip de la version que vous souhaitez utiliser :

Ajoutez le lien vers cette version à votre liste de dépendances de game.project. Une fois l’extension Spine ajoutée, vous devez également redémarrer l’éditeur pour activer l’intégration à l’éditeur incluse dans l’extension Spine.
Faisons nos premiers pas en créant un terrain pour notre personnage, ou plus exactement une portion de sol qui défile. Nous allons procéder en quelques étapes.
Un atlas est un fichier qui réunit plusieurs images distinctes dans un seul fichier image plus grand. Cela permet d’économiser de l’espace et d’améliorer les performances. Pour en savoir plus sur les atlas et les autres fonctionnalités graphiques 2D, consultez la documentation sur les graphismes 2D.


Pourquoi cela ne fonctionne-t-il pas !? L’oubli d’enregistrer est un problème fréquent lorsque l’on débute avec Defold ! Après avoir ajouté des images à un atlas, vous devez enregistrer le fichier pour pouvoir accéder à ces images.
Créez un fichier de collection ground.collection pour le sol et ajoutez-y sept objets de jeu (game objects) (faites un clic droit sur la racine de la collection dans la vue Outline et sélectionnez Add Game Object). Nommez les objets “ground0”, “ground1”, “ground2”, etc., en modifiant la propriété Id dans la vue Properties. Notez que Defold attribue automatiquement un identifiant unique aux nouveaux objets de jeu.
Dans chaque objet, ajoutez un composant sprite (faites un clic droit sur l’objet de jeu dans la vue Outline et sélectionnez Add Component, puis sélectionnez Sprite et cliquez sur OK), définissez la propriété Image du composant sprite sur l’atlas que vous venez de créer et choisissez l’une des deux images du sol comme animation par défaut du sprite. Définissez la position X du composant sprite (et non de l’objet de jeu) sur 190 et sa position Y sur 40. Comme l’image mesure 380 pixels de large et que nous la décalons horizontalement de la moitié de cette distance, le pivot de l’objet de jeu se trouve sur le bord gauche de l’image du sprite.


Le plus simple est probablement de créer un objet de jeu complet avec un composant sprite, de régler son échelle, puis de le copier. Sélectionnez-le dans la vue Outline, puis sélectionnez Edit ▸ Copy et ensuite Edit ▸ Paste.
Si vous souhaitez des tuiles plus grandes ou plus petites, il suffit de modifier leur échelle. Vous devrez toutefois aussi modifier les positions X de tous les objets de jeu du sol pour qu’elles soient des multiples de la nouvelle largeur.
Enregistrez le fichier, puis ajoutez ground.collection au fichier main.collection : double-cliquez d’abord sur main.collection, puis faites un clic droit sur l’objet racine dans la vue Outline et sélectionnez Add Collection From File. Dans la boîte de dialogue, sélectionnez ground.collection et cliquez sur OK. Veillez à placer ground.collection à la position 0, 0, 0, sinon elle sera décalée visuellement. Enregistrez le fichier.
Lancez le jeu (Project ▸ Build) pour vérifier que tout est en place.

Vous vous demandez peut-être à présent ce que sont exactement tous ces éléments que nous avons créés. Prenons donc un instant pour examiner les éléments de base de tout projet Defold :
Pour le moment, ces descriptions devraient suffire. Vous trouverez toutefois une présentation bien plus complète dans le manuel des éléments de base. Il est conseillé de consulter ce manuel plus tard pour mieux comprendre le fonctionnement de Defold.
Maintenant que toutes les portions de sol sont en place, il est assez simple de les mettre en mouvement. Le principe est le suivant : déplacer les portions de droite à gauche et, lorsqu’une portion atteint le bord gauche à l’extérieur de l’écran, la déplacer tout à droite. Un script Lua est nécessaire pour déplacer tous ces objets de jeu. Créons-en donc un :
-- ground.script
local pieces = { "ground0", "ground1", "ground2", "ground3",
"ground4", "ground5", "ground6" } -- <1>
function init(self) -- <2>
self.speed = 360 -- Speed in 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() est appelée lorsque l’objet de jeu prend vie dans le jeu. Nous initialisons une variable membre propre à l’objet qui contient la vitesse du sol.update() est appelée une fois par image, généralement 60 fois par seconde. dt contient le nombre de secondes écoulées depuis le dernier appel.dt pour obtenir une vitesse en pixels/s indépendante de la fréquence d’images.Defold est un cœur de moteur rapide qui gère vos données et vos objets de jeu. Toute la logique et tous les comportements nécessaires à votre jeu sont créés en langage Lua. Lua est un langage de programmation rapide et léger, particulièrement adapté à l’écriture de la logique des jeux. D’excellentes ressources permettent d’apprendre ce langage, comme le livre Programming in Lua et le manuel de référence officiel de Lua.
Defold ajoute un ensemble d’API à Lua, ainsi qu’un système d’échange de messages qui vous permet de programmer les communications entre objets de jeu. Consultez le manuel sur l’échange de messages pour en savoir plus sur son fonctionnement.
Vous pouvez afficher ou masquer les sections Assets Pane, Console et Outline de l’éditeur à l’aide des touches F6, F7 et F8 respectivement.
Maintenant que nous avons un fichier de script, nous devons ajouter une référence à ce fichier dans un composant d’un objet de jeu. Le script sera ainsi exécuté dans le cadre du cycle de vie de l’objet de jeu. Pour cela, nous créons un nouvel objet de jeu dans ground.collection et lui ajoutons un composant Script qui fait référence au fichier de script Lua que nous venons de créer :

Lorsque vous exécutez le jeu, l’objet de jeu “controller” exécute maintenant le script de son composant Script, ce qui fait défiler le sol de façon fluide à travers l’écran.
Le héros sera un objet de jeu constitué des composants suivants :
Commencez par importer les images des parties du corps, puis ajoutez-les à un nouvel atlas que nous appelons hero.atlas :

Nous devons également importer les données d’animation Spine et préparer une Spine Scene pour les utiliser :

Le fichier hero.spinejson a été exporté au format Spine JSON. Vous aurez besoin du logiciel d’animation Spine pour créer de tels fichiers. Si vous souhaitez utiliser un autre logiciel d’animation, vous pouvez exporter vos animations sous forme de feuilles de sprites et les utiliser comme animations image par image à partir de ressources Tile Source ou Atlas. Consultez le manuel sur l’animation pour en savoir plus.
Nous pouvons maintenant commencer à construire l’objet de jeu du héros :

Il est maintenant temps d’ajouter la physique pour que les collisions fonctionnent :
Le type de collision “Kinematic” signifie que nous voulons détecter les collisions, mais que le moteur physique ne les résoudra pas automatiquement et ne simulera pas les objets. Le moteur physique prend en charge plusieurs types d’objets de collision. Vous pouvez en savoir plus dans la documentation sur la physique.
Il est essentiel de préciser avec quoi l’objet de collision doit interagir :
Enfin, créez un fichier hero.script et ajoutez-le à l’objet de jeu.
handle_geometry_contact().)
Nous gérons nous-mêmes les collisions parce que, si nous définissions plutôt le type de l’objet de collision du personnage comme dynamique, le moteur effectuerait une simulation newtonienne des corps concernés. Pour un jeu comme celui-ci, une telle simulation est loin d’être optimale. Nous prenons donc le contrôle complet au lieu de lutter contre le moteur physique en appliquant diverses forces.
Cela nécessite toutefois un peu de calcul vectoriel pour gérer correctement les collisions. Vous trouverez une explication détaillée de la résolution des collisions cinématiques dans la documentation sur la physique.
-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20
-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900
function init(self)
-- this tells the engine to send input to on_input() in this script
msg.post(".", "acquire_input_focus")
-- save the starting position
self.position = go.get_position()
-- keep track of movement vector and if there is ground contact
self.velocity = vmath.vector3(0, 0, 0)
self.ground_contact = false
end
function final(self)
-- Return input focus when the object is deleted
msg.post(".", "release_input_focus")
end
function update(self, dt)
local gravity = vmath.vector3(0, gravity, 0)
if not self.ground_contact then
-- Apply gravity if there's no ground contact
self.velocity = self.velocity + gravity
end
-- apply velocity to the player character
go.set_position(go.get_position() + self.velocity * dt)
-- reset volatile state
self.correction = vmath.vector3()
self.ground_contact = false
end
local function handle_geometry_contact(self, normal, distance)
-- project the correction vector onto the contact normal
-- (the correction vector is the 0-vector for the first contact point)
local proj = vmath.dot(self.correction, normal)
-- calculate the compensation we need to make for this contact point
local comp = (distance - proj) * normal
-- add it to the correction vector
self.correction = self.correction + comp
-- apply the compensation to the player character
go.set_position(go.get_position() + comp)
-- check if the normal points enough up to consider the player standing on the ground
-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
if normal.y > 0.7 then
self.ground_contact = true
end
-- project the velocity onto the normal
proj = vmath.dot(self.velocity, normal)
-- if the projection is negative, it means that some of the velocity points towards the contact point
if proj < 0 then
-- remove that component in that case
self.velocity = self.velocity - proj * normal
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- check if we received a contact point message. One message for each contact point
if message.group == hash("geometry") then
handle_geometry_contact(self, message.normal, message.distance)
end
end
end
local function jump(self)
-- only allow jump from ground
if self.ground_contact then
-- set take-off speed
self.velocity.y = jump_takeoff_speed
end
end
local function abort_jump(self)
-- cut the jump short if we are still going up
if self.velocity.y > 0 then
-- scale down the upwards speed
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 vous le souhaitez, vous pouvez maintenant ajouter temporairement le héros à la collection principale et lancer le jeu pour le voir tomber à travers le monde.
Le dernier élément nécessaire pour que le héros soit fonctionnel est la gestion des entrées. Le script ci-dessus contient déjà une fonction on_input() qui réagit aux actions “jump” et “touch” (pour les écrans tactiles). Ajoutons des liaisons d’entrée pour ces actions.

Maintenant que notre héros est configuré avec ses collisions et tout le nécessaire, nous devons aussi ajouter des collisions au sol pour qu’il ait quelque chose à heurter (ou sur quoi courir). Nous allons le faire dans un instant, mais commençons par une petite réorganisation : plaçons tous les éléments du niveau dans une collection distincte et rangeons un peu la structure des fichiers :
Comme vous l’avez peut-être remarqué, la hiérarchie des fichiers affichée dans le panneau Assets pane est indépendante de la structure du contenu que vous construisez dans vos collections. Les fichiers individuels sont référencés dans les fichiers de collection et d’objet de jeu, mais leur emplacement est totalement libre.
Si vous souhaitez déplacer un fichier, Defold vous aide en mettant automatiquement à jour les références à ce fichier (refactorisation). Lorsque vous créez un logiciel complexe, comme un jeu, il est extrêmement utile de pouvoir modifier la structure du projet à mesure qu’il grandit et évolue. Defold encourage cette pratique et facilite l’opération : n’ayez donc pas peur de déplacer vos fichiers !
Nous devons également ajouter à la collection du niveau un objet de jeu contrôleur doté d’un composant script :
Ouvrez le fichier de script, copiez-y le code suivant et enregistrez-le :
-- controller.script
go.property("speed", 360) -- <1>
function init(self)
msg.post("ground/controller#ground", "set_speed", { speed = self.speed })
end

L’objet de jeu “controller” n’existe pas dans un fichier distinct : il est créé directement dans la collection du niveau. Son instance est donc créée à partir des données définies sur place. Cela convient aux objets de jeu à usage unique comme celui-ci. Si vous avez besoin de plusieurs instances d’un objet de jeu et souhaitez pouvoir modifier le prototype ou modèle utilisé pour créer chaque instance, créez simplement un fichier d’objet de jeu et ajoutez l’objet à la collection à partir de ce fichier. Vous obtenez ainsi un objet de jeu qui référence le fichier comme prototype ou modèle.
Cet objet de jeu “controller” a pour rôle de contrôler tout ce qui concerne le niveau en cours d’exécution. Bientôt, ce script sera chargé de faire apparaître des plateformes et des pièces avec lesquelles le héros pourra interagir, mais pour l’instant, il se contente de définir la vitesse du niveau.
Dans sa fonction init(), le script du contrôleur du niveau envoie un message au composant script de l’objet contrôleur du sol, en l’adressant par son identifiant :
msg.post("ground/controller#controller", "set_speed", { speed = self.speed })
L’identifiant de l’objet de jeu contrôleur est "ground/controller", car il se trouve dans la collection “ground”. Nous ajoutons ensuite l’identifiant du composant, "controller", après le caractère dièse "#", qui sépare l’identifiant de l’objet de celui du composant. Notez que le script du sol ne contient pas encore de code pour réagir au message set_speed : nous devons donc ajouter une fonction on_message() à ground.script et la logique nécessaire.
-- 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

À ce stade, nous devons ajouter les collisions physiques du sol :

Vous devriez maintenant pouvoir exécuter le jeu (Project ▸ Build). Le héros devrait courir sur le sol et pouvoir sauter avec la touche Space. Si vous exécutez le jeu sur un appareil mobile, vous pouvez sauter en touchant l’écran.
Pour rendre la vie un peu moins monotone dans notre monde de jeu (game world), nous devons ajouter des plateformes sur lesquelles sauter.
Créez un fichier Script nommé platform.script (faites un clic droit dans le panneau Assets pane, puis sélectionnez New ▸ Script File), insérez le code suivant dans le fichier et enregistrez-le :
-- platform.script
function init(self)
self.speed = 540 -- Default speed in 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

Notez que platform.go et platform_long.go possèdent tous deux des composants Script qui font référence au même fichier de script. C’est un avantage : toute modification de ce fichier influencera le comportement des plateformes normales comme des plateformes longues.
L’idée est de créer un jeu de course sans fin simple. Les objets de jeu des plateformes ne peuvent donc pas être placés dans une collection dans l’éditeur. Nous devons les faire apparaître dynamiquement :
-- 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
-- Maybe spawn a platform at random height
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, {}, vmath.vector3(0.6, 0.6, 1))
msg.post(p, "set_speed", { speed = self.speed })
end
end
end
update() est appelée une fois par image. Nous nous en servons pour décider de faire apparaître une plateforme normale ou longue à certains intervalles (pour éviter les chevauchements) et à certaines hauteurs. Il est facile d’expérimenter différents algorithmes d’apparition pour varier la jouabilité.Exécutez maintenant le jeu (Project ▸ Build).
Voilà, cela commence à devenir (presque) jouable…

Nous allons commencer par donner vie au héros. Pour le moment, le pauvre est bloqué dans une boucle de course et ne réagit correctement ni aux sauts ni au reste. Le fichier Spine que nous avons ajouté depuis le lot de ressources contient justement un ensemble d’animations prévues pour cela.
update() existante : -- hero.script
local function play_animation(self, anim)
-- only play animations which are not already playing
if self.anim ~= anim then
-- tell the spine model to play the animation
local anim_props = { blend_duration = 0.15 }
spine.play_anim("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, anim_props)
-- remember which animation is playing
self.anim = anim
end
end
local function update_animation(self)
-- make sure the right animation is playing
if self.ground_contact then
play_animation(self, hash("run"))
else
play_animation(self, hash("jump"))
end
end
update() et ajoutez un appel à update_animation : ...
-- apply it to the player character
go.set_position(go.get_position() + self.velocity * dt)
update_animation(self)
...

En Lua, les variables locales ont une « portée lexicale », et l’ordre dans lequel vous placez les fonctions local est significatif. La fonction update() appelle les fonctions locales update_animation() et play_animation(), ce qui signifie que l’environnement d’exécution doit avoir rencontré ces fonctions locales pour pouvoir les appeler. C’est pourquoi nous devons les placer avant update(). Si vous inversez l’ordre des fonctions, vous obtiendrez une erreur. Notez que cela s’applique uniquement aux variables local. Vous pouvez en savoir plus sur les règles de portée et les fonctions locales de Lua à l’adresse http://www.lua.org/pil/6.2.html
Cela suffit pour ajouter des animations de saut et de chute au héros. Si vous exécutez le jeu, vous remarquerez qu’il est beaucoup plus agréable à jouer. Vous constaterez peut-être aussi que les plateformes peuvent malheureusement pousser le héros hors de l’écran. C’est un effet secondaire de la gestion des collisions, mais le remède est simple : ajoutons un peu de violence et rendons les bords des plateformes dangereux !
Enregistrez le fichier.

Ouvrez hero.go, sélectionnez le Collision Object et ajoutez le nom “danger” à la propriété Mask. Enregistrez ensuite le fichier.

Ouvrez hero.script et modifiez la fonction on_message() pour obtenir une réaction lorsque le héros entre en collision avec un bord “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
-- check if we received a contact point message
if message.group == hash("danger") then
-- Die and restart
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
Modifiez la fonction init() pour qu’elle envoie un message “reset” afin d’initialiser l’objet, puis enregistrez le fichier :
-- hero.script
function init(self)
-- this lets us handle input in this script
msg.post(".", "acquire_input_focus")
-- save position
self.position = go.get_position()
msg.post("#", "reset")
end
Si vous essayez le jeu maintenant, vous constaterez rapidement que le mécanisme de réinitialisation ne fonctionne pas. La réinitialisation du héros se passe bien, mais vous pouvez facilement réapparaître dans une situation où vous retombez immédiatement sur le bord d’une plateforme et mourez de nouveau. Nous voulons réinitialiser correctement tout le niveau à chaque mort. Comme le niveau n’est qu’une série de plateformes créées dynamiquement, il suffit de garder la trace de toutes ces plateformes, puis de les supprimer lors de la réinitialisation :
Ouvrez le fichier controller.script et modifiez le code pour stocker les identifiants de toutes les plateformes créées :
-- 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
-- Maybe spawn a platform at random height
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, {}, vmath.vector3(0.6, 0.6, 1))
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>
-- Tell the hero to reset.
msg.post("hero#hero", "reset")
-- Delete all platforms
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
Ouvrez platform.script et modifiez-le pour qu’au lieu de simplement supprimer une plateforme qui a atteint le bord gauche, il envoie un message au contrôleur du niveau lui demandant de retirer la plateforme :
-- 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)
...

La boucle principale de redémarrage et de mort est maintenant en place !
Passons à une raison de rester en vie : les pièces !
L’idée est de placer dans le niveau des pièces que le joueur pourra ramasser. La première question est de savoir comment les y placer. Nous pourrions, par exemple, développer un mécanisme d’apparition coordonné d’une manière ou d’une autre avec l’algorithme d’apparition des plateformes. Nous avons finalement choisi une approche beaucoup plus simple : laisser les plateformes elles-mêmes faire apparaître les pièces :
Créez un fichier de script coin.script (faites un clic droit sur level dans le panneau Assets pane et sélectionnez New ▸ Script File). Remplacez le code du modèle par le suivant :
-- 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
Ajoutez le fichier de script à l’objet de la pièce en tant que composant Script (faites un clic droit sur la racine dans Outline et sélectionnez Add Component from File).

Nous prévoyons de faire apparaître les pièces à partir des objets de plateforme. Ajoutez donc des factories pour les pièces dans platform.go et platform_long.go.

Nous devons maintenant modifier platform.script pour qu’il fasse apparaître les pièces et les supprime :
-- platform.script
function init(self)
self.speed = 540 -- Default speed in 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
Les relations parent-enfant modifient uniquement le graphe de scène. Un enfant subit les transformations (déplacement, mise à l’échelle ou rotation) de son parent. Si vous avez besoin de relations d’« appartenance » supplémentaires entre les objets de jeu, vous devez les gérer explicitement dans le code.
La dernière étape de ce tutoriel consiste à ajouter quelques lignes à 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 -- Twice the number of coins on long platforms
end
...
-- controller.script
...
msg.post(p, "set_speed", { speed = self.speed })
msg.post(p, "create_coins", { coins = coins })
table.insert(self.spawns, p)
...

Nous avons maintenant un jeu simple, mais fonctionnel ! Si vous êtes arrivé jusqu’ici, vous aurez peut-être envie de poursuivre par vous-même et d’ajouter les éléments suivants :
Téléchargez la version terminée du projet ici.
Ce tutoriel d’introduction est terminé. À vous maintenant de vous plonger dans Defold. Nous avons préparé de nombreux manuels et tutoriels pour vous guider et, si vous êtes bloqué, vous êtes le bienvenu sur le forum.
Amusez-vous bien avec Defold !