Astra sait désormais très bien traduire ce que j’imagine en mécaniques de jeu et en choix artistiques. Je m’en sers dans Codex pour créer Void Explorer, un jeu d’exploration spatiale où l’on peut voyager jusqu’à des étoiles lointaines, puis descendre à la découverte de paysages extraterrestres.
Un montage de séquences de jeu de Void Explorer.
Le jeu compte 2 048 systèmes stellaires et plus de 10 000 planètes générées de manière procédurale, dont certaines ont la taille de la Terre. Chaque étoile visible fait partie de l’univers du jeu et peut être prise pour cible. Vous pouvez choisir un point lumineux au loin et vous y rendre.
Depuis l’espace, vous pouvez vous approcher d’une planète, traverser son atmosphère et poursuivre votre descente jusqu’à survoler le littoral. Vous pouvez vous poser, sortir du vaisseau, vous promener à pied et redécoller. Pour que tout ce trajet fonctionne, j’ai dû traiter ensemble les questions d’échelle, de terrain, de commandes et de rendu.
J’ai repris ici quelques prompts utilisés pendant le développement, en les raccourcissant et en les clarifiant. Ils montrent comment j’ai décrit ce que je voulais et comment le travail technique s’est construit à partir de là.
Partez de l’expérience de jeu
Dans ma demande initiale, j’ai décrit ce que le joueur devait pouvoir faire :
Je veux pouvoir atteindre tout ce que je vois. Conservez des distances réalistes, puis jouez sur l’échelle et la vitesse pour que les déplacements soient possibles. Je veux pouvoir quitter l’espace, entrer dans l’atmosphère d’une planète et descendre jusqu’au sol. Les planètes peuvent avoir la taille de la Terre. Il nous faudra donc un terrain procédural et un moteur de rendu qui le traite par blocs.
Astra avait ainsi des contraintes concrètes. Une étoile ne pouvait pas être un simple point peint sur le fond du décor. Une planète ne pouvait pas devenir un niveau distinct à mon approche. Les trajets devaient couvrir des distances réalistes, que des modes de propulsion fictifs, le vol par impulsion et l’hyperpropulsion, permettraient de parcourir en pratique.
J’ai aussi utilisé la génération d’images pour définir l’aspect du jeu alors que son développement débutait à peine. Les premières illustrations étaient trop réalistes. Les suivantes étaient trop simples. Voici l’une des corrections que j’ai demandées :
Cette version est un peu trop simpliste. Il nous faut un juste milieu, avec des couleurs mieux choisies, des éclairages au néon et des contrastes plus marqués qui évoquent l’espace profond. Montrez-moi ce que ça donne à grande vitesse, avec des étoiles et de la poussière autour du vaisseau.
Une fois satisfait des images, j’ai demandé à Astra de les enregistrer et d’en faire des références visuelles pour le jeu. Nous savions précisément à quoi devaient ressembler le vol orbital, le vol à grande vitesse, l’entrée dans l’atmosphère et l’atterrissage. Avec ces images, il était beaucoup plus facile de juger la version suivante.

Cette illustration générée a servi à définir la palette de couleurs et la direction artistique du jeu.
Laissez Astra proposer l’architecture
J’ai fixé les contraintes, puis Astra a proposé une façon de les mettre en œuvre. L’application utilise TypeScript et Vite, avec Three.js pour le rendu. Le code peut ainsi agir directement sur les maillages, les matériaux, l’éclairage et la géométrie procédurale, qui déterminent l’essentiel de l’aspect visuel du jeu.
Le moteur de rendu de la première version jouable utilisait WebGL2. Plus tard, j’ai demandé à Astra si Three.js limitait la qualité visuelle. Astra a recommandé de conserver la simulation et de faire passer le moteur de rendu à l’architecture WebGPU de Three.js. Nous avons gardé les systèmes qui gèrent l’univers, la navigation et le terrain, tout en remplaçant la couche de rendu.
Le moteur de rendu utilise les matériaux à base de nœuds de Three.js et le langage Three.js Shading Language (TSL) pour définir dans le code l’atmosphère, l’eau et les effets d’éclairage.
Des Web Workers génèrent le terrain. La géométrie peut ainsi être préparée en dehors du thread qui gère les entrées et l’affichage. Les tests Vitest vérifient entre autres que la génération est reproductible, que les calculs de coordonnées sont justes et que le terrain respecte les contrats prévus. Playwright teste le jeu dans un navigateur. Astra pouvait ainsi vérifier les systèmes sous-jacents pendant que je continuais à évaluer les sensations de jeu.
La direction artistique nous a guidés tout au long du projet. L’atmosphère qui enveloppe une planète, la lueur de ses anneaux, la lumière colorée des étoiles et l’effet Neon Phosphor contribuent tous à l’identité visuelle du jeu. Le filtre donne à l’image le grain d’un écran rétro tout en préservant la lisibilité des instruments de navigation.

Une capture du jeu avec l’effet Neon Phosphor activé.
Donnez à Astra les moyens d’inspecter le jeu et d’y jouer
Je continuais à jouer moi-même, mais Astra avait aussi besoin de pouvoir examiner un problème au-delà de la description que j’en donnais. Le jeu expose une petite interface JavaScript, window.__VOID_EXPLORER__, que les tests dans le navigateur peuvent interroger pour connaître l’état du jeu : l’astre dont je m’approche, le mode de vol, l’état de préparation du terrain et la caméra active. Elle donne aussi accès à des compteurs de rendu et de chargement progressif, notamment le nombre d’appels de dessin, de triangles et de tâches de génération du terrain en attente, ainsi que la quantité de données de terrain en mémoire tampon.
Le jeu dispose de scènes de test nommées pour le vol orbital, le vol par impulsion, la descente dans l’atmosphère et l’atterrissage sur le littoral. Astra peut ainsi retrouver un point de départ adapté au test sans avoir à traverser l’univers à chaque fois. Playwright peut charger une scène, attendre que le terrain soit prêt, prendre une capture d’écran et vérifier l’état du jeu correspondant. D’autres tests parcourent les trajets avec les véritables commandes du jeu et enregistrent les positions et les changements d’état au fil de la partie. On teste ainsi la manœuvre d’atterrissage elle-même, en plus de pouvoir charger la scène correspondante.
Ces tests couvrent l’atterrissage, la sortie du vaisseau, la marche, la sauvegarde et le rechargement d’une partie, le retour à bord et le décollage. Ils peuvent détecter des problèmes qu’une capture d’écran seule ne révélerait pas, comme une surface de collision qui n’est pas encore prête ou un vaisseau qui saute d’une position à une autre pendant une transition.
Je pouvais aussi demander à Astra d’inspecter l’onglet du navigateur dans lequel je jouais. Quand j’ai demandé si les nuages d’Aurelia étaient toujours visibles, Astra a pris une capture de ce que je voyais et a lu les informations de vol à l’écran. Ces vérifications étaient ponctuelles. Astra ne suivait pas mon vol image par image en continu.
Je procédais par étapes : reproduire le problème, examiner les captures d’écran et l’état du jeu, suivre l’exécution du code concerné, apporter une modification, puis relancer la vérification. Si je créais un autre jeu, je mettrais ces outils en place dès le début : quelques scènes reproductibles, des indicateurs d’état et de performance pertinents et des tests dans le navigateur pour les principales interactions. Ils permettent à Astra de chercher la cause des problèmes et de tester les modifications de manière autonome pendant que je continue à évaluer l’aspect du jeu et la sensation que procurent les commandes.
Représentez l’univers à plusieurs échelles
Avoir des milliers de planètes ne signifie pas charger des milliers de maillages détaillés. Au départ, une planète se résume à quelques paramètres : son rayon, son orbite, son atmosphère et sa graine de génération. C’est cette graine qui permet de reproduire son terrain à l’identique. Le jeu génère la géométrie autour de la zone dont je m’approche et peut recréer le même paysage quand j’y retourne.
Les coordonnées demandent autant de soin. Mesurer des distances en années-lumière et situer une personne à côté d’un vaisseau sont deux problèmes d’échelle très différente. Si on envoyait directement au GPU des coordonnées aussi grandes, on perdrait la précision nécessaire près du sol.
Astra a distingué la position physique dans l’univers de celle qui sert au rendu. Dans l’univers, chaque position se décompose en une grande cellule repérée par des entiers et un décalage local de plus faible amplitude. Avant le rendu, le jeu soustrait la position de l’observateur. La caméra reste ainsi à l’origine du repère et les coordonnées des objets proches restent petites. Les distances affichées et les tailles relatives restent cohérentes.
Les éléments en mouvement doivent aussi partager la même référence temporelle. Les étoiles, les planètes et les lunes suivent des orbites calculées analytiquement. Le moteur de rendu évalue leur position au même instant de la simulation que celle de l’observateur. Une fois posé, le vaisseau reste solidaire de la planète en rotation. La position et la couleur des étoiles déterminent l’éclairage. Quand je vois deux soleils se coucher dans un système binaire, ce sont donc les mêmes étoiles que j’ai vues depuis l’orbite.
Assurez une descente continue
Vol depuis l’espace vers la surface d’une lune dans Void Explorer.
Il a fallu de nombreux essais pour réussir la transition entre l’espace et le sol. L’un de mes prompts est né d’une approche à faible angle :
Quand j’approche d’une planète en volant presque parallèlement à sa surface, je peux encore arriver trop vite, et la planète disparaît presque entièrement pendant que le terrain se charge. Il faut revoir ensemble la courbe de vitesse et le chargement progressif du terrain. L’atmosphère pourrait-elle adoucir la transition entre le niveau de détail utilisé à distance et le terrain détaillé ? La planète ne doit jamais disparaître pendant l’approche.
Le contrôleur de vol applique désormais une limite de vitesse spécifique aux approches à faible angle. Elle dépend de l’altitude et d’une estimation de la vitesse orbitale calculée à partir du rayon de la planète et de sa gravité. Plus près du sol, le contrôleur échantillonne aussi le terrain devant le vaisseau pour ajuster le freinage.
Le jeu utilise plusieurs niveaux de détail, ou LOD. De loin, une planète est représentée par une sphère à facettes peu coûteuse à afficher, appelée proxy. À mesure qu’elle occupe plus de place à l’écran, le moteur de rendu ajoute des détails. À l’approche de la surface, le jeu active un terrain construit à partir des six faces d’un cube projetées sur une sphère. Chaque face peut être divisée en quatre carrés plus petits, qui peuvent à leur tour être subdivisés de la même manière. Cette structure forme un arbre quaternaire.
Le jeu peut ainsi affiner le terrain dans la zone visible et dans la direction du vol sans générer toute la surface d’une planète de la taille de la Terre avec la précision nécessaire aux déplacements à pied. Près du sol, des portions de terrain locales fournissent une géométrie plus fine autour du vaisseau et du joueur.
Toutes ces représentations échantillonnent les mêmes données planétaires. Une fonction commune combine continents, crêtes montagneuses, cratères et reliefs de moindre ampleur. Elle fournit l’altitude, la présence d’eau, le biome et d’autres paramètres qui définissent le paysage. Les maillages grossiers et fins représentent ces données à différentes résolutions. Tous appliquent les mêmes règles de coloration pour préserver la cohérence visuelle de la planète.
Le passage d’une représentation à l’autre compte autant que la génération. La représentation lointaine de la planète reste visible jusqu’à ce que les six faces du terrain à basse résolution soient prêtes. Une tuile de terrain reste en place tant que les quatre tuiles issues de sa subdivision ne sont pas toutes prêtes. Pendant la transition, des masques de pixels complémentaires attribuent chaque pixel à l’ancienne ou à la nouvelle surface. On évite ainsi de rendre toute la planète transparente pendant le chargement de la surface qui la remplace.
L’atmosphère change en fonction de l’altitude réelle et les nuages se déplacent au-dessus des reliefs de la planète. Ces éléments assurent une continuité visuelle entre la vue depuis l’orbite et le paysage au sol. Le moteur de rendu ne retire le terrain à basse résolution que là où le terrain plus détaillé est prêt. La silhouette des montagnes peut encore changer lorsque la géométrie plus fine apparaît.
L’atterrissage impose une contrainte plus stricte. Pour une zone de contact donnée, le sol visible, les triangles de collision et les dangers liés aux liquides doivent tous provenir de la même génération et être prêts en même temps. Pour les déplacements à pied, Rapier simule la physique dans un petit espace local. De son côté, la logique d’atterrissage du vaisseau vérifie ses points d’appui, la pente et sa garde au sol par rapport au terrain.
J’ai délibérément posé une limite : les planètes utilisent des champs de hauteur, avec une seule altitude dans chaque direction depuis le centre. Cette représentation convient aux vastes paysages, mais ne permet pas de créer des grottes, des surplombs ou des tunnels destructibles.
Mesurez ce qui ralentit le rendu d’une image
Certains de mes retours portaient à la fois sur l’aspect visuel et sur les performances, car j’observais les deux problèmes au cours du même vol :
Pouvez-vous examiner la façon dont nous chargeons et affichons une planète pendant que je descends de l’espace vers le sol ? Le globe vu de loin et le terrain détaillé n’ont pas le même aspect. La transition est brutale, surtout quand les couleurs changent. J’aimerais comprendre d’où viennent ces ruptures visuelles et où nous pouvons réduire le travail nécessaire pour charger plus de détails, afin que la descente soit plus belle et plus fluide.
Astra a utilisé les compteurs du moteur de rendu de Three.js pour examiner les appels de dessin, les triangles, les géométries et les textures. Un test Playwright a mesuré 90 intervalles entre images et a fourni, en plus de ces compteurs, la durée moyenne et les percentiles de ces intervalles. Pour les problèmes visuels, les tests pouvaient aussi comparer les pixels obtenus en activant puis en désactivant un effet précis. On pouvait par exemple afficher le terrain avec et sans le masque qui cache les surfaces de sol superposées. Cette comparaison a permis d’isoler la cause de la disparition du sol, ce qu’une simple capture de la scène défectueuse ne permettait pas de faire.
Une version antérieure du vaisseau illustre l’intérêt de ces mesures. Le remplacement du vaisseau procédural par le premier modèle AURORA créé dans Blender a augmenté le nombre de triangles de la scène, mais réduit le nombre d’appels de dessin. Avant et après la modification, Astra a exécuté le même test de 90 images en orbite avec le moteur de rendu WebGPU :
| Mesure | Avant | Après |
|---|---|---|
| Nombre total d’appels de dessin dans la scène | 119 | 77 |
| Nombre total de triangles dans la scène | 48 809 | 61 092 |
| Intervalle moyen entre deux images | 251,48 ms | 199,26 ms |
Cette comparaison a été réalisée dans Chromium sans interface graphique, avec le rendu logiciel de SwiftShader, avant la création du vaisseau à quatre ailes présenté plus bas. Elle mesure l’effet de la modification dans cet environnement de test, et non la fréquence d’images sur mon GPU. Les outils fournissaient à Astra les mesures de temps du navigateur, les images produites et le nombre de ressources utilisées. Ils ne mesuraient pas la durée de chaque commande GPU ni le temps d’exécution des shaders.
Astra a examiné le travail effectué pendant le chargement et la descente : la quantité de géométrie générée, le volume de données échangées entre les workers et le moteur de rendu, ainsi que la fréquence à laquelle des tâches de génération du terrain étaient abandonnées avant d’avoir servi.
L’une des modifications a consisté à adapter le niveau de détail d’une planète lointaine à sa taille à l’écran. Une planète qui occupe tout le champ de vision doit immédiatement avoir une silhouette bien définie. Une planète plus petite qu’un pixel n’a pas besoin de la même géométrie. Lors d’une comparaison contrôlée sur la scène de départ, la nouvelle règle a utilisé 7 040 triangles pour les proxies, contre 51 200 auparavant. Comme les deux règles utilisaient le même générateur de géométrie, la comparaison isolait l’effet du choix des données à charger.
Une autre amélioration a conservé exactement le même nombre de triangles pour le terrain. Les maillages indexés réutilisent les sommets communs aux triangles voisins, ce qui évite de répéter les données de position, de couleur et de normale. Sur les six couches de sol du test, avec une qualité de terrain élevée, le volume des tampons transférés est passé d’environ 35 Mo à 15 Mo pour les mêmes 241 952 triangles. La génération du sol et celle du décor ont aussi été réparties entre des tâches distinctes. La géométrie du sol pouvait ainsi être prête sans attendre les éléments de décor.
Le système de sélection du terrain effectuait lui aussi du travail inutile. Il pouvait demander des blocs, revenir sur son choix, les abandonner, puis en demander d’autres. Astra a stabilisé les décisions de subdivision : une tuile n’était subdivisée que si le budget permettait de traiter ses quatre sous-tuiles. Dans une simulation contrôlée où la latence des workers était fixe, six secondes de descente suivies de trois secondes de stabilisation ont entraîné l’abandon de 13 tâches, contre 6 074 auparavant.

Dans une simulation contrôlée avec deux workers et une latence simulée de 100 ms par worker, le nombre de tâches planifiées puis abandonnées est passé de 6 074 à 13. Cette mesure porte sur la planification des tâches de terrain et ne renseigne pas sur la fréquence d’images.
Les workers ne suffisent pas à éviter tous les blocages. Lorsque la génération du terrain se repliait sur le thread principal, elle devait aussi lui rendre la main. Astra a découpé la génération en étapes que l’on peut suspendre puis reprendre, avec un budget d’environ 4 ms par image. Une grosse tâche de génération pouvait ainsi s’exécuter en plusieurs fois. Le moteur de rendu limite aussi la résolution de l’univers indépendamment de celle de l’interface, afin que les indications de navigation restent nettes quand la qualité visuelle diminue.
Ces mesures montrent que le jeu génère moins de géométrie, transfère moins de données et abandonne moins de tâches. Elles ne mesurent pas la fréquence d’images sur le matériel du joueur. Il reste important de jouer dans le navigateur pour observer la compilation des shaders, les transferts vers le GPU, la fluidité des mouvements et l’aspect réel de la transition.
De mon côté, je faisais des retours en jouant : ce qui me semblait lent, ce qui avait un aspect étrange et ce que je voulais améliorer. Astra a suivi l’exécution du code, écrit des scripts de test reproductibles pour le terrain et consigné les mesures. Une fois que j’ai validé l’approche, Astra a mis en œuvre les modifications et relancé les mêmes tests pour comparer les résultats. Je n’ai pas eu besoin de dicter chaque étape de l’enquête ou de la mise en œuvre et j’ai pu continuer à juger de l’aspect du jeu et des sensations qu’il procurait.
Transformez les illustrations préparatoires en ressources de jeu
Le code génère l’essentiel de l’univers : les planètes, le terrain, les nuages, les anneaux et les étoiles. Le vaisseau du joueur est le principal objet 3D modélisé sur mesure. Pour lui, j’ai suivi une démarche différente.
J’ai utilisé des illustrations générées et des rendus Blender pour examiner le vaisseau avant de l’intégrer au jeu. Comme les ailes ne correspondaient pas à ce que j’imaginais, j’ai demandé des références plus précises :
Générez des vues bien lisibles de ce vaisseau sous plusieurs angles. Accordez une attention particulière aux ailes avant et arrière. Nous utiliserons ces images pour refaire le modèle 3D. Les ailes arrondies qui se rejoignent sur le modèle actuel ne me plaisent pas.
J’ai choisi la référence qui présentait quatre ailes distinctes, une forme symétrique et des feux roses en bout d’aile. J’ai ensuite demandé à Astra de créer un nouveau modèle dans Blender en respectant le style visuel du jeu, puis de l’y intégrer.
Il a fallu modéliser le vaisseau à partir de cette référence, vérifier sa silhouette et ses matériaux, puis préparer un modèle utilisable en jeu. Le fichier source Blender contient 193 maillages modifiables. Le vaisseau exporté compte 14 968 triangles, répartis en huit lots de rendu par matériau opaque. Je pouvais demander un modèle plus détaillé tandis qu’Astra maîtrisait le coût du rendu de la version exportée.

L’illustration générée que j’ai retenue pour le vaisseau.

Un rendu Blender du modèle utilisé dans le jeu.
Expérimentez avec l’eau procédurale
J’ai aussi beaucoup expérimenté avec l’eau dans Sunwake, un jeu où l’on pilote un petit bateau sur un océan procédural. Je voulais des vagues à facettes, des mouvements fluides et les couleurs d’un vitrail. Astra a créé dans Three.js un moteur de rendu de l’eau sur mesure. Le même modèle de vagues détermine l’aspect de la mer et la flottabilité du bateau. La coque se soulève, tangue et roule au gré des vagues. Une simulation locale propage les rides et l’écume, tandis que le bateau creuse son sillage et projette des embruns. J’ai ensuite demandé à Astra de modéliser un bateau plus détaillé dans Blender et de l’intégrer au jeu. C’est la même démarche que pour Void Explorer : un environnement procédural et un véhicule modélisé sur mesure, qui interagissent grâce à la simulation.

Sunwake associe un océan procédural à un bateau modélisé sur mesure dans Blender.
Avec Hollowflux, j’ai exploré une autre piste : un petit jeu de rôle d’action en 2D qui s’articule autour d’une rivière souterraine lumineuse. Le code dessine les grottes, les personnages et l’équipement, sans planches de sprites importées. Au fil des essais, j’ai demandé à Astra de rendre plus convaincante la réaction de l’eau aux pas, aux ruées et aux attaques. Une grille de cellules suit la hauteur de l’eau, les courants, l’écume et la charge électrique. Chaque pas crée des rides à la surface, les coups de lance tracent des sillages étroits et les coups de marteau propagent des ondes circulaires. Les courants tourbillonnent le long des berges et emportent l’écume vers l’aval. Ce même mouvement de l’eau entraîne le joueur et ses ennemis. L’eau joue aussi un rôle dans les combats : la décharge d’une anguille se propage partout où l’eau forme une étendue continue. En montant sur une pierre sèche, le joueur échappe à cette décharge.

Dans Hollowflux, le code dessine les éléments du jeu et l’eau réagit aux déplacements et aux combats.
Créez et partagez vos jeux
Ce qui me plaît avec Astra, c’est que je peux partir de l’expérience que j’ai en tête, préciser la direction artistique à l’aide d’images et faire des retours après avoir réellement joué. Signaler qu’une planète disparaît peut conduire à revoir le chargement progressif, la géométrie et les déplacements. Demander une eau plus convaincante peut aboutir à une simulation commune au rendu et aux mécaniques de jeu. C’est toujours à moi de juger si le résultat procure les sensations recherchées, mais Astra peut appliquer mes choix au code, aux ressources du jeu et aux tests.
Il est aussi très simple de partager un jeu sur navigateur. Avec le plugin Sites, vous pouvez demander à Astra :
Publiez le jeu en accès public pour que tout le monde puisse y jouer directement dans son navigateur.
Vous pouvez jouer à Void Explorer, à Sunwake et à Hollowflux dans votre navigateur.
Si vous avez une idée de jeu en tête, essayez d’en créer une petite version jouable avec Astra. Choisissez d’abord un élément pour lequel la sensation de jeu vous importe, donnez quelques références visuelles à Astra et testez le résultat. Un bateau, une pièce ou une seule interaction suffit pour commencer. Jouez, décrivez précisément ce que vous aimeriez changer et poursuivez le développement à partir de là.