Retour au Blog

Defold passe à la 3D intégrale : Analyse technique et tutoriel d'architecture

Publié le 12 mai 2026
Defold passe à la 3D intégrale : Analyse technique et tutoriel d'architecture

En bref

Cet article analyse la transformation de Defold en un moteur 3D performant, idéal pour le développement cross-platform léger. Il propose un guide technique sur les pipelines de rendu personnalisés en Lua, l'implémentation de contrôleurs de caméra et l'optimisation des shaders GLSL. Le texte souligne également les enjeux de synchronisation réseau et les avantages d'intégrer une infrastructure backend comme horizOn pour scaler les projets multijoueurs.

Chaque développeur indépendant connaît ce moment où son moteur de jeu le trahit. Vous commencez avec un concept 2D léger, le scope creep introduit des éléments 3D, et soudainement la taille du build de votre moteur explose, vos temps de chargement s'allongent et vos builds web saturent la mémoire. Les moteurs massifs comme Unity et Unreal sont phénoménaux pour la fidélité AAA, mais pour les développeurs solo visant une distribution cross-platform fluide — en particulier sur WebGL et mobile — ils donnent souvent l'impression de conduire un char d'assaut pour aller faire ses courses.

Voici Defold. Historiquement reconnu comme un moteur 2D ultra-rapide et sans superflu utilisé par des studios pour atteindre toutes les plateformes, de HTML5 à la Nintendo Switch avec une codebase unique, Defold a officiellement franchi une étape. C'est désormais un moteur de jeu 3D complet. Bien qu'il ait toujours techniquement effectué son rendu dans un contexte 3D sous le capot (en projetant des plans plats via une caméra orthographique), les récentes mises à jour ont introduit des outils 3D dédiés, un support complet des meshes glTF et un workflow optimisé pour le véritable développement 3D.

C'est un changement massif pour l'écosystème. Si vous recherchez un moteur qui compile en quelques secondes, produit des binaires de quelques mégaoctets et vous donne tout de même la puissance nécessaire pour rendre des mondes 3D dynamiques, Defold est désormais un concurrent de premier plan. Dans ce tutoriel sur le moteur de jeu Defold en 3D et cette analyse architecturale, nous allons plonger dans le fonctionnement du Rendering Pipeline de Defold, comment scripter une caméra 3D personnalisée à partir de zéro, et les implications backend du networking d'un jeu 3D léger.

Sous le capot : la réalité du Pipeline 3D de Defold

Pour maîtriser Defold en 3D, vous devez comprendre sa philosophie de rendu. Defold ne vous livre pas un pipeline PBR (Physically Based Rendering) préconfiguré out of the box comme le fait Unreal Engine. À la place, il fournit un render script piloté par les données et hautement optimisé, écrit en Lua.

Tout ce qui est affiché à l'écran dans Defold est géré par un render_script. Par défaut, ce script est configuré pour la 2D. Il définit une matrice de projection orthographique, trie les sprites par leur valeur Z (profondeur) et les dessine de l'arrière vers l'avant. Pour débloquer les capacités 3D de Defold, nous devons réécrire ce script pour utiliser une matrice de projection en perspective, activer le depth testing matériel et définir des render predicates personnalisés pour nos modèles 3D.

Cet accès bas niveau est une épée à double tranchant. D'un côté, vous devez manipuler un peu de mathématiques matricielles. De l'autre, vous avez un contrôle absolu sur vos draw calls, vous permettant d'optimiser le rendu pour du matériel ultra-bas de gamme d'une manière que les moteurs monolithiques rendent incroyablement difficile.

Architecturer votre Render Script 3D personnalisé

Pour effectuer le rendu de véritables modèles 3D sans qu'ils ne se chevauchent incorrectement selon l'ordre d'affichage, nous devons activer le depth buffer (Z-buffer). Voici un render script 3D fondamental qui remplace le pipeline par défaut de Defold.

-- main/3d_pipeline.render_script
function init(self)
    -- Define the background clear color (RGBA)
    self.clear_color = vmath.vector4(0.1, 0.1, 0.12, 1.0)
    self.clear_buffers = {
        [render.BUFFER_COLOR_BIT] = self.clear_color,
        [render.BUFFER_DEPTH_BIT] = 1.0,
        [render.BUFFER_STENCIL_BIT] = 0
    }

    -- Create render predicates. 'model' is the default tag for 3D meshes
    self.predicates = {
        model = render.predicate({"model"}),
        gui = render.predicate({"gui"}),
        text = render.predicate({"text"})
    }
end

function update(self)
    -- 1. Setup the rendering state for the frame
    render.set_depth_mask(true)
    render.set_stencil_mask(0xff)
    render.clear(self.clear_buffers)

    -- 2. Configure the 3D Camera Projection
    local window_width = render.get_window_width()
    local window_height = render.get_window_height()
    if window_width == 0 or window_height == 0 then return end
    
    local aspect_ratio = window_width / window_height
    local fov = math.rad(60) -- 60 degree Field of View
    local near_z = 0.1
    local far_z = 1000.0
    
    local proj_matrix = vmath.matrix4_perspective(fov, aspect_ratio, near_z, far_z)
    render.set_projection(proj_matrix)

    -- 3. Draw 3D Models with Depth Testing enabled
    render.set_depth_test(render.COMPARE_LEQUAL)
    render.set_cull_face(render.FACE_BACK)
    render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
    
    -- The view matrix is passed via messages from our camera script
    if self.view_matrix then
        render.set_view(self.view_matrix)
        render.draw(self.predicates.model)
    end

    -- 4. Draw GUI over the 3D scene (Orthographic)
    render.set_depth_mask(false)
    render.set_depth_test(render.COMPARE_ALWAYS)
    local gui_proj = vmath.matrix4_orthographic(0, window_width, 0, window_height, -1, 1)
    render.set_projection(gui_proj)
    render.set_view(vmath.matrix4())
    
    render.draw(self.predicates.gui)
    render.draw(self.predicates.text)
end

function on_message(self, message_id, message)
    if message_id == hash("set_view_matrix") then
        self.view_matrix = message.matrix
    end
end

Remarquez à quel point la gestion de l'état est explicite ici. Nous vidons le depth buffer, calculons la matrice de perspective basée sur les dimensions actuelles de la fenêtre, appliquons le back-face culling pour économiser des cycles de rastérisation, et enfin, basculons la matrice de projection en mode orthographique avant de rendre l'interface utilisateur. Cela vous donne une architecture de rendu robuste à pipeline séparé.

Créer un contrôleur de caméra 3D à la première personne

Un render script seul ne vous montrera pas grand-chose sans une caméra se déplaçant dans l'espace. Defold repose fortement sur une architecture de passage de messages. Contrairement aux moteurs orientés objet où la caméra pourrait appeler directement transform.Translate(), dans Defold, notre script de caméra calculera sa matrice de vue et l'enverra au render script que nous venons d'écrire.

Construisons une caméra standard à la première personne qui gère le mouse-look (pitch et yaw) et le mouvement au clavier (WASD).

-- scripts/camera_controller.script
go.property("mouse_sensitivity", 0.2)
go.property("move_speed", 10.0)

function init(self)
    msg.post(".", "acquire_input_focus")
    self.pitch = 0
    self.yaw = 0
    
    -- Hide and capture the mouse cursor
    window.set_mouse_lock(true)
    
    self.forward = vmath.vector3(0, 0, -1)
    self.right = vmath.vector3(1, 0, 0)
    self.up = vmath.vector3(0, 1, 0)
    self.velocity = vmath.vector3(0)
end

function update(self, dt)
    local pos = go.get_position()
    
    -- Apply movement velocity
    if vmath.length_sqr(self.velocity) > 0 then
        local move_dir = vmath.normalize(self.velocity)
        pos = pos + move_dir * self.move_speed * dt
        go.set_position(pos)
    end

    -- Calculate the view matrix
    local rotation = go.get_rotation()
    self.forward = vmath.rotate(rotation, vmath.vector3(0, 0, -1))
    local target = pos + self.forward
    
    local view_matrix = vmath.matrix4_look_at(pos, target, self.up)
    
    -- Send the calculated view matrix to the render pipeline
    msg.post("@render:", "set_view_matrix", { matrix = view_matrix })
    
    -- Reset velocity for the next frame
    self.velocity = vmath.vector3(0)
end

function on_input(self, action_id, action)
    if action_id == hash("mouse_moved") then
        self.yaw = self.yaw - action.dx * self.mouse_sensitivity
        self.pitch = self.pitch + action.dy * self.mouse_sensitivity
        
        -- Clamp pitch to prevent the camera from flipping over
        self.pitch = math.max(-89, math.min(89, self.pitch))
        
        -- Convert Euler angles to a Quaternion
        local rot_y = vmath.quat_rotation_y(math.rad(self.yaw))
        local rot_x = vmath.quat_rotation_x(math.rad(self.pitch))
        go.set_rotation(rot_y * rot_x)
        
    elseif action_id == hash("move_forward") then
        self.velocity = self.velocity + self.forward
    elseif action_id == hash("move_backward") then
        self.velocity = self.velocity - self.forward
    elseif action_id == hash("move_left") then
        self.right = vmath.cross(self.forward, self.up)
        self.velocity = self.velocity - self.right
    elseif action_id == hash("move_right") then
        self.right = vmath.cross(self.forward, self.up)
        self.velocity = self.velocity + self.right
    end
end

Ce script capture les deltas de mouvement de la souris pour ajuster le pitch et le yaw, convertissant ces angles d'Euler en une rotation Quaternion robuste. Il dérive ensuite les vecteurs forward et right directement de cette rotation pour garantir que l'appui sur « W » vous déplace toujours dans la direction que vous regardez.

Intégration d'assets 3D et Shaders personnalisés

Avec les récentes mises à jour de Defold, importer des assets 3D dans votre projet est trivial. Le moteur supporte nativement les formats .gltf et .glb, devenus le standard de l'industrie pour le web et le développement de jeux légers.

Cependant, le rendu d'un mesh nécessite un matériau, et les matériaux nécessitent des shaders. Par défaut, Defold inclut des matériaux de base, mais écrire vos propres shaders GLSL vous donne la distinction visuelle nécessaire pour sortir du lot. Écrivons un shader texturé unlit rapide, parfaitement optimisé pour les cibles mobile ou HTML5.

Le Vertex Shader (model.vp)

// model.vp
uniform highp mat4 view_proj;
uniform highp mat4 world;

attribute highp vec4 position;
attribute mediump vec2 texcoord0;

varying mediump vec2 var_texcoord0;

void main()
{
    // Calculate the final screen-space position of the vertex
    vec4 p = view_proj * world * vec4(position.xyz, 1.0);
    var_texcoord0 = texcoord0;
    gl_Position = p;
}

Le Fragment Shader (model.fp)

// model.fp
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

void main()
{
    // Sample the texture and multiply by a color tint
    lowp vec4 tex_color = texture2D(texture_sampler, var_texcoord0.xy);
    gl_FragColor = tex_color * tint;
}

Dans votre fichier de matériau Defold, vous mappez l'uniform view_proj à la matrice view-projection intégrée au moteur, et texture_sampler à la texture diffuse de votre mesh. Comme ces shaders ne calculent pas d'éclairage dynamique ni de shadow maps, ils s'exécutent incroyablement vite, vous permettant de maintenir facilement 60 FPS sur du matériel modeste.

Gestion du Multiplayer 3D et synchronisation d'état

Lorsque vous passez d'un jeu 2D basé sur une grille à un environnement 3D complet, la complexité de votre architecture réseau augmente de manière exponentielle. Passer de la 2D à la 3D signifie que votre synchronisation d'état doit désormais tenir compte de la profondeur sur l'axe Z, des imprécisions de calcul en virgule flottante entre les moteurs de physique, et des rotations quaternioniques complètes. Si vous gérez mal cela, vous finirez par obtenir un stuttering visuel sévère — un problème courant que nous avons exploré dans notre analyse sur comment corriger la désynchronisation de localisation des joueurs dans le Multiplayer UEFN et Unreal Engine.

Parce que Defold est un moteur léger, il n'inclut pas de système de réplication lourd comme les RPCs d'Unreal Engine. Vous êtes responsable de l'empaquetage efficace de vos données d'état.

S'appuyer sur des APIs REST pour synchroniser des positions 3D créera instantanément un goulot d'étranglement dans votre boucle de jeu. À la place, vous avez besoin de connexions persistantes et bidirectionnelles. Bien que notre guide précédent expliquait pourquoi abandonner le polling HTTP au profit des WebSockets Unreal Engine pour les backends temps réel, les mêmes principes architecturaux s'appliquent directement aux extensions WebSocket basées sur Lua de Defold.

Voici un exemple de la manière dont vous devriez sérialiser les données de transform 3D pour minimiser la taille du payload sur les WebSockets :

-- scripts/network_sync.lua
local json = require "builtins.scripts.json"

function serialize_transform(go_id)
    local pos = go.get_position(go_id)
    local rot = go.get_rotation(go_id)
    
    -- Compress the payload to the absolute minimum required data
    -- We round position to 2 decimal places to save bandwidth
    local payload = {
        id = hash_to_hex(go_id),
        x = math.floor(pos.x * 100) / 100,
        y = math.floor(pos.y * 100) / 100,
        z = math.floor(pos.z * 100) / 100,
        -- Quaternions require all 4 components for accurate reconstruction
        qx = rot.x,
        qy = rot.y,
        qz = rot.z,
        qw = rot.w
    }
    
    return json.encode(payload)
end

En arrondissant les flottants et en n'emballant que les données essentielles, vous évitez que vos buffers réseau ne débordent lors de mises à jour de mouvements rapides. La gestion de ce flux est la clé d'une expérience cross-platform réactive.

Le fardeau des infrastructures 3D Cross-Platform

Écrire un client de jeu 3D hautement optimisé dans Defold est immensément satisfaisant. Le moteur ne vous gêne pas, compile en quelques millisecondes et vous permet de vous concentrer strictement sur la logique.

Cependant, dès que vous décidez de rendre ce jeu 3D Multiplayer, d'ajouter des sauvegardes cloud ou d'implémenter des leaderboards cross-platform, votre attention se déplace immédiatement du développement de jeu vers l'orchestration serveur. Vous vous retrouvez soudainement à écrire des Dockerfiles, configurer des clusters Kubernetes, lutter avec des instances Redis pour l'état des sessions, et tenter de sécuriser vos passerelles WebSocket contre les attaques DDoS.

Construire cela soi-même nécessite de mettre en place des load balancers, du sharding de base de données et la gestion des certificats SSL — facilement 4 à 6 semaines de travail juste pour faire tourner un prototype fiable.

Avec horizOn, ces services Backend sont préconfigurés, vous permettant de livrer votre jeu plutôt que votre infrastructure. horizOn fournit des intégrations natives pour l'authentification utilisateur, la synchronisation de base de données en temps réel et la logique faisant autorité sur le serveur, comblant parfaitement le fossé pour les moteurs légers comme Defold qui ne sont pas livrés avec des écosystèmes backend propriétaires. Vous conservez la vitesse d'un moteur client léger tout en externalisant le travail lourd de l'architecture serveur.

Bonnes pratiques pour le développement 3D avec Defold

Si vous prévoyez de construire un projet 3D commercialement viable dans Defold, respectez strictement ces directives architecturales :

  1. Optimisez massivement la géométrie : Defold est conçu pour la vitesse. Pour maintenir son avantage de légèreté, gardez la géométrie de vos niveaux sous les 100 000 polygones au total par scène, surtout si vous ciblez HTML5/WebGL. Utilisez des normal maps bakées plutôt que des meshes haute densité pour simuler le détail.
  2. Utilisez les Render Predicates pour le Frustum Culling : Defold ne supprime pas automatiquement les objets situés hors du champ de vision de la caméra en 3D par défaut. Vous devez écrire votre propre logique de Frustum Culling en Lua, en désactivant dynamiquement les composants modèles des objets hors limites pour économiser du temps de rastérisation.
  3. Consolidez les Draw Calls via l'Atlasing : Chaque matériau et texture unique nécessite un draw call séparé envoyé au GPU. Combinez vos textures dans de grands atlas de textures. Si 10 modèles 3D différents partagent exactement le même matériau et atlas, Defold peut les traiter par lots (batching) beaucoup plus efficacement.
  4. Pré-calculez les mathématiques complexes : Les multiplications de matrices et les conversions de quaternions sont des opérations coûteuses en Lua. Cachez vos vecteurs forward et right et ne les recalculez que lorsque la rotation du joueur change réellement, plutôt que de faire les calculs lourds à chaque frame.
  5. Déscouplez la logique de la fréquence de rendu : Votre logique de jeu (update) peut tourner à 60 FPS, mais votre physique personnalisée ou vos étapes de networking peuvent tourner à 30 FPS. Interpolez les positions visuelles 3D en fonction de la vélocité plutôt que de les caler directement sur le dernier état pour garantir un rendu fluide sur des moniteurs à taux de rafraîchissement variables.
  6. Gérez la Garbage Collection Lua : Dans un environnement 3D dynamique, vous créez et détruisez fréquemment des objets vecteurs et des matrices. Le Garbage Collector de Lua peut causer des pics de frame visibles s'il n'est pas géré. Réutilisez les instances vmath.vector3 et vmath.matrix4 dès que possible en mettant à jour leurs valeurs internes directement, au lieu d'instancier de nouvelles variables locales dans votre boucle update. Pré-allouez des memory pools pour les projectiles et les entités.
  7. Bakez votre éclairage en externe : Comme l'éclairage dynamique dans les shaders GLSL personnalisés consommera rapidement votre budget de performance sur mobile, bakez votre Global illumination et votre Ambient occlusion directement dans vos textures à l'aide de Blender ou Maya avant d'exporter vos modèles glTF. Un simple shader unlit avec un éclairage magnifiquement baké surpassera toujours un shader dynamique complexe sur les navigateurs web mobiles.

Conclusion

L'évolution de Defold vers un moteur de jeu 3D robuste est une victoire massive pour les développeurs indépendants. Il conserve avec succès ses temps de compilation ultra-rapides et ses binaires incroyablement petits tout en offrant les fondations mathématiques et les outils nécessaires pour construire des mondes 3D vastes et engageants. En maîtrisant les render scripts personnalisés, en comprenant les opérations matricielles et en sérialisant efficacement vos données réseau, vous pouvez créer des titres cross-platform qui rivalisent techniquement avec des moteurs beaucoup plus lourds.

Lorsque vous serez prêt à mettre votre client 3D optimisé en ligne et à scaler votre Backend multijoueur sans le casse-tête de la gestion d'infrastructure brute, essayez horizOn gratuitement ou consultez la documentation API pour voir avec quelle rapidité vous pouvez intégrer des services temps réel dans votre prochain projet Defold.


Source : Defold is Now a Full 3D Game Engine