EN COURS DE REDACTION : c'est un devlog
Dernière mise à jour : 2026-04-10
Super Toboggan 3D
Après un jeu PS Vita me voilà à faire un jeu PlayDate ! Pour celleux qui ne savent pas la playdate c'est une petite console jaune, avec un écran noir et blanc et une manivelle (oui !). Ça fait un moment que j'en ai une, j'y ai joué un peu, j'ai fait le Lucas Pope sorti dessus, et puis après j'avoue elle est partie prendre la poussière. Néanmoins j'avais toujours dans un coin de la tête de faire un jeu dessus...
Lors du Lyon Game Dev de janvier, je discutais avec Ludo qui fait des jeux sur playdate justement (il a sorti Crank Stone recemment), et il me disait que la mode en ce moment c'était les jeux en 3D. Là mon cerveau s'est dit : et si j'essayai ????? J'ai très vite fait le lien avec une envie vague que j'avais dans un coin de la tête de créer un clone de SpeedX 3D.
L'étape 1 c'était de regarder comment on fait de la 3D sur playdate, j'ai trouvé un billet de blog trop bien qui justement récapitule toutes les façons de faire. Arrivé vers le bas de la page est listé Mini3D qui fait partie de l'API offcielle de la PlayDate.
Première 3D
Étape 1 : faire un cube. J'ai repris la démo 3D en lua qui est dans le SDK de la playdate, et je l'ai modifiée pour faire apparaitre un cube que je fais tourner avec la manivelle. Ça m'a permis de me remettre en têt les base de la 3D (notamment l'ordre des points d'un triangle!).
Étape 2 : réécrire ça en C, pour le moment je n'ai pas de problème de performance (heureusement, parce que je prévois d'afficher ien plus qu'un cube), mais je sais que ça n'allait pas durer Ça été interessant à faire et ça marche !
Le tunnel
Le player
J'hésite à représenter le joueur, dans SpeedX3D il n'y est pas, mais je me dis que quand même ça peut prendre plus clair. Du coup je voudrais bien un genre de vaisseau triangulaire proche de la caméra sur le tube. J'ai fait un premier test en en faisant un node à part (via mini3D), mais je ne comprend pas bien pourquoi ses coordonnées ne correspondaient pas à celle du tube, au final je fais un second essai en dessinant direct le player sur le même noeud que le tube. C'est interessat ça me permet de comprendre le code de génération du tunnel (que j'avoue j'ai copié collé de XScreenSaver 😇).
Je viens de découvrir l'option pour draw en wireframe, le rendu est tellement mieux. Seulement ça ne prend pas en compte orderTable donc je réécris la lib pour que ça le fasse. J'ai réussi, ça m'a permis de corriger le wireframe qui se faisait pas en noir, le rendu est bien stylé maintenant, je suis content.
Pour le player en lui même mes essais ne sont pas concluants, je voulais un genre de pyramide collé contre la paroi et j'arrive pas à avoir cet effet, j'ai envie de voir si faire comme SpeedX3D (la camera c'est le player) je m'en sortirai mieux.
J'ai réussi à faire en sorte que la camera soit sur le tube et avec la manivelle je la tourne. Par contre j'ai un soucis, les faces se clipent trop facilement quand un seul de leur vertex passe derrière la camera, je tente de bidouiller ça. Pas facile, je suis descndu jusque dans le draw fragment sans trouver qui était à l'origine de ce comportement. Bon déjà la camera comme joueur ça marche, par contre j'ai divisé par deux mon framerate sur playdate, faut que je vois comment optimiser.
Les obstacles
Dans un premier temps je sidetrack: j'ai trouvé comment optimiser, je rend les portions de tunnel lointaine (donc culled la plupart du temps) avec moins de faces, j'ai un 30 fps constant (ça me semble une bonne target). Ensuite j'ai regardé si je pouvais changer la couleur des sections du tube pour en faire des sombres (un peu comme dans les toboggan aquatique quand il fait noir !), pour la couleur c'était facile mais le wireframe il fallait le passer en blanc. J'ai donc encore touché à des choses directement dans le sdk, maintenant la couleur du wireframe est décidé dans la face et non plus dans la shape. Ça marche !
Bon, les obstacles... Je sais pas trop pas quel bout le prendre, va falloir que j'instantie de nouvelles shapes, je vais 100% avoir à nouveau des galères avec la mémoire... Déjà je vais essayer d'en draw 1, de me mettre au bon endroit dans la boucle de dessin du tunnel. Ok j'ai réussi un premier truc :
Avec un peu d'algorithmie j'ai réussi à trouver les bons vertex pour faire une pyramide. Puis plusieurs, mais j'ai galéré à trouver un bon moyen d'allouer la mémoire.
Bon, les obstacles fonctionnent lorsque ce sont toujours les mêmes par section, et par niveau. Mais bon ce n'est pas terrible, j'aimerai bien pouvoir varier, mais ça fait n'imp avec les triangles et ça crash. C'est lié à la façon dont je hack mini3D (je change chaque frame des valeurs qui sont prévues pour être fixes, genre les faces et les vertex). Après beaucoup d'essai erreur, j'ai trouvé l'astuce : je reset la liste de points chaque frame, en plus ça me permet de libérer régulièrement de la mémoire, c'est top. Ça marche, maintenant j'ai envie de me faire un éditeur de niveaux sur Godot.
Je viens de tester sur Playdate et... les allocations mémoire chaque frame c'est pas trop ça, mes FPS sont à 15 :/ Il va falloir que je trouve une autre idée.
Le clipping de la caméra
Un truc qui me gène depuis le début c'est que les faces du tunnel proches de la caméra disparaissent, du coup on voit à travers et notamment l'extérieur des sections suivantes du tunnel, et puis c'est moche.
Dans le README de Mini3D c'est en effet écrit que dessiner les faces qui ont au moins un vertex derrière la caméra n'est pas pris en charge. Je me suis dis bêtement que je pourrais dans un premier temps commenter la ligne qui s'occupe du skip, mais je ne l'ai pas trouvée... J'ai commenté / décommenté des trucs pour essayer de comprendre, mais sans succès. Alors je me suis rappelé que Mini3D+, une réécriture du Mini3D officiel, avait la fonction pour afficher les faces proches de la caméra, le l'avais lu dans mes recherches préliminaires.
En lisant le code sur leur dépot je comprend que ce qui est fait c'est de créer des plus petites faces qui elles sont entièrement visibles par la caméra. En désespoir de cause, je décipe de transitionner vers Mini3D+, j'aimais bien l'idée de rester sur le SDK officiel mais bon, ça me parait pas possible de résoudre mon problème simplement et Mini3D+ est là à me tendre les bras.
L'API c'est pensée pour être compatible donc j'ai fait une branche sur mon dépôt et j'ai remplacé le dossier de Mini3D, avec quelques ajustement ça a très vite fonctionné, donc c'est cool. Ensuite il a fallu que je reporte les modifications au rendu que j'avais faites, ça m'a pris un peu de temps puisque Mini3D+ a rajouté des fonctions qui changent des structures et des fonctions, mais j'y suis arrivé. Très content, le clipping c'était le dernier truc qui me fasait peur pour la viabilité du projet (j'imagine que Panic aurait pas aimé pour le publier sur le Catalog, moi ça m'aurait dérangé de toute façon). J'ai l'impression qu'avec Mini3D+ j'ai un framerate légèrement plus stable, donc j'y gagne sur tous les plans j'ai l'impression. Il y a des macro à configurer aussi faudra que je regarde s'il y a des choses qui peuvent m'intéresser.
J'ai un soucis avec la caméra, le UP a l'air géré différemment, en effet Mini3D+ a complètement changé la fonction SetCamera, flemme de comprendre les changements, je vais porter l'ancienne. Ça marche, maintenant je vais nettoyer les trucs qui me servent à rien dans Mini3D+ (genre le lua et les trucs de karts). Je supprime aussi les lib pour lire des textures, ça ne m'interesse pas et ça alourdi beaucoup la compilation. je voulais aussi supprimer les imposteurs, mais c'est trop lié au code de la scène donc je le laisse, ça va ça coute pas grand chose.
-
Bon en fait le UP de la caméra est cassé, je peux plus la tourner correctement en fonction de la manivelle, donc je décide de finalement passer sur le SetCamera de Mini3D+ et ça marche.
Un tool pour le level design
En C le tunnel est simplement un tableau des points par lesquels il doit passer, et le reste est calculé par un CatMull. C'est léger et pratique mais ça fait que c'est pas facile d'itérer sur la forme du tunnel. Je me suis donc attelé à la création d'un tool en Godot. J'avais déjà utilisé les Path3D et les CSGPolygon pour FediRacing et FUCK AI pour faire des formes le 3D qui suivent une courbe, c'est donc là dessus que c'est porté mon choix immédiatement. Mon besoin c'est juste de pouvoir donner des points en format texte (qui viennent du C) et que ça me fasse le tunnel associé dans Godot, puis à l'inverve que je puisse exporter un tunnel modifié dans Godot en liste de points pour le code en C. Ce n'était pas très compliqué à faire et à marche, surtout que je ne m'embête pas refaire le CatMull, la forme générale du tunnel me convient très bien.
-
J'ai ajouté une option qui recalcule les points pour qu'ils soient toujours à peu près à la même distance sinon cela faisait varier la vitesse du joueur.
Ne plus être dependant du framerate
Actuellement le jeu va plus vite lorsque lancé en simulateur qu'en réel sur la Playdate, il faut que j'arrive à avoir un delta time pour pouvoir le multiplier par ma vitesse. Apparement y'a aucun moyen de l'avoir directement, donc il faut diviser 1 par le FPS courant. Ça marche c'est bon pour ça !
Rollback
Régulièrement dans le tunnel la caméra semble être téléportée en arrière, mais je ne vois pas de quoi ça vient. Ce n'est pas vraiment à un changement de section. Quand je met le jeu au ralenti ça se voit bien.
Changement de font
Lorsque j'ai fix le chargement des images pour le logo ça aaussi fix le chargement des fonts, je peux en tester d'autres que celui que je drawais moi-même. Il s'agissait de Blit Font, un font entièrement en header C, c'était stylé mais très hacky (je dessinais des rectangle de taille 1 pour chaque pixel). Je découvre que la playdate peut pas lire de format classique de font (ttf, woff...), il faut des .fnt. J'ai trouvé deux trucs : un site avec plein de fonts en CC0 et l'outils Caps de Panic pour dessiner ses fonts. J'ai donc choisi une font du premier et je l'ai modifiée dans le second. Je suis parti de Afterburner et j'en ai fait des versions 16 et 32, j'ai aussi ajouté des charactères, genre le point ou le pourcent (pour l'affichage du score.
Cool utilisation du gyroscope
Depuis le début du projet j'ai envie de faire un truc: créer un effet de paralaxe avec le gyroscope. Il faut que ce soit léger et subtil. Quand on récupère les valeurs d'accéléromètres dans le SDK ça nous donne une valeur de rotation sur trois axes, est-ce qu'en vrai ce serait pas un gyroscope????
- 9 avril
Je reprend cette idée, j'aimerai bien l'avoir dans les menus (mais pas in-game, ça pourrait induire en erreur). Il faudrait que j'arrive à transposer le vecteur de l'accéléromètre dans la base du forward de la caméra, ça doit pas être compliqué. En fait si c'est compliqué... Je tente une approche plus simple en me limitant à un axe.
J'ai réussi !
Faire de meilleurs niveaux
J'essaye d'avoir des niveaux plus intéressants, mais j'ai un soucis avec le tunnel qui parfois fait n'importe quoi (il s'éloigne de la caméra, voir capture ci-après). Pour moi c'est parce que les faces sont trop grandes donc clippées, mais c'est bizarre parce que désormais j'exporte depuis Godot mes points avec une distance identique entre eux. J'ai essayé changer mon interpolation en linéaire (elle est en Catmull-Rom normalement), mais ça n'a rien changé.
Ok j'ai trouvé le problème, c'est la façon dont le tunnel est construit dans le code de atunnel.c, pour le tracer il suppose que l'axe Z sera toujours celui perpendiculaire au tunnel, soit je prends ça en compte dans ma génération, soit je fais en sorte que la construction du tunnel soit plpus générique (je vais essayer le deuxième).
J'ai réussi, il m'a fallu un moment pour me rendre compte qu'avoir le vecteur up en tout point de la courbe c'était facile, j'ai regardé dans le code du PathFollower de Godot 0:)
Redesign de l'UI
Je procrastine la génération des niveaux en revoyant mon UI. Je voudrais qu'elle soit cohérente et minimliste, je vais généraliser la barre noire en bas de l'écran. Aussi je me suis rendu compte qu'on pouvait tricher avec le menu pause (le mettre afin de prévoir les obstacles qui arrivent) alors je vais bloquer la vue. Surtout que j'aimais pas trop mon ancien logo PAUSE.
Pour l'affichage du score au moment du game over j'aimerai faire une police moi-même avec des chiffres en 3D, comme pour le titre je vais tenter un truc avec les fontworks de LibreOffice. Après je les passe dans Dither it!. Bon c'est compliqué de rendre bien en 32x32 pixels en fait, je laisse ça de côté et je reste avec ma police 32 Afterburner.
LE LEVEL DESIGN
Ok c'est bon je m'y met.
Il faut que rationalise un niveau sinon c'est dur de réfléchir. J'opte pour une sépration en 10, qui correspond au pourcentage de complétion (je l'annonçait déjà auditivement tous les 10), et dans chaque partie il y aura un pattern d'obstacle différent. Ce que j'appelle pattern, ce sont des ensemble d'obstacles : des barres de trois, un couloir, un moitié de tunnel en obstacle. Je m'en fait un enum comme ça je peux facilement composer mon niveau à ma main.
static const int PATTERNS_NORMAL[] =
{
P_BARS_3,
P_PILLAR_1,
P_HALF_3,
P_BARS_3,
P_PILLAR_2,
P_HALF_3,
P_BARS_5,
P_HALF_5,
P_PILLAR_3,
P_CORRIDOR1,
};
- 25 mars
J'ai créé une nouvelle forme d'obstacle : les piliers. Pour l'instant c'est simplement deux obstacles qui se font face, mais il faut que je fasse en sorte que le visuel suive.
- 30 mars
J'ai fait des niveau qui bouclent pour HARD et INSANE, pour le deuxième c'est même un cercle.
Optimisiation
- 23 mars
Depuis la mise en place des obstacle, je suis tombé à 20fps en début de partie et 10 vers le milieu du niveau. Ça ne va pas du tout, il faut que je trouve des astuces. Déjà je vais draw une face de moins sur les obstacles (celle de l'arrière, on ne la voit jamais).
- 24 mars
J'ai trouvé en faisant du commentage/décommentage : le nombre de points est trop élevé. Pour les obstacles contrairement au tunnel je ne remplace pas mes points mais j'en ajoute des nouveaux. Sans les obstacles ma shape fait 420 points, mais avec 9 obstacles par sections on monte en fin de niveau à 5937 points. Et la fonction addPoint cherche à chaque fois si le point n'existe pas déjà, ce qui est de plus en plus long. Je touche vraiment la contrainte de ce genre de matériel tout petit : une boucle for sur 5000 éléments fait ramer ^^'
Comment je vais résoudre ça... Il faudrait que je remplace mes points et pas que j'en ajoute des nouveaux, ça demande à réfléchir l'algorithmie...
J'ai un premier truc:
J'ai un problème lorsque qu'on a fait un tour complet (+ de 100%), je regarde. Par contre ça me donne une idée d'obstacle, le fait que plusieurs obstacles partagent le même sommet.
C'est bon j'ai factoré, tout fonctionne sans accro, j'ai un framerate à 20 constant !
Les collisions
- 25 mars
J'ai réglé un dernier problème avec les collisions, lié au changement de section du tunnel. Gloabalement pour détecter si le joueur est dans un obstacle je regarde si ça progression dans la section tunnel (un float entre 0 et 1) est à peu près celle de l'obstacle. Sauf qu'au changement du section ça marche tout de suite moins bien, il me suffisait juste de convertir les valeurs pour qu'elles soit toutes relatives à la même section et puis voilà !
L'audio
Je voulais une musique pour chaque niveau ainsi qu'un son d'ambiance pour me menu principal.
- 2 avril
Il faut que j'arrive à trouver une balance audio correcte, je vais me fixer des db pour chque truc, genre les sound effects je vais les harmoniser en -12db perçus via Audacity. Je me rend compte que je peux optimiser pour n'avoir d'un seul canal mono vu que la playdate n'a qu'un haut-parleur.
- 3 avril
Je fais un test pour reprendre la musique pas au début lorsqu'on retry comme Super Hexagon.
Build target
J'approche de la fin du projet et je voudrait pouvoir build en DEBUG (avec mes outils de triche) ou en RELEASE. Je vais me base sur une #define, déjà je vais le rajouter partout dans le code.
Bon je comprend pas les makefile, j'ai beau ajouter un -DDEBUG à ma commande ça ne defini pas DEBUG dans le code. Je vais juste faire à la main je crois (le define DEBUG), et le commenter/décommenter suivant la build.
Metadonnées
- 2 avril 2026
Il me faut une image de couverture, ainsi qu'une anim stylée lorsque le jeu est selectionné sur l'écran principal. Tout ça ça se met dans le pdxinfo puis il faut préciser le dossier des images pour le launcher. Sauf que ça marche pas... Ok bon, j'avais juste mal nommé mon fichier card.png. Maintenant j'aimerai mettre une anim, j'ai exporté un GIF du jeu qui fait 600 frames (total: 2Mo), on va voir si ça passe. C'est très long à transférer ^^' Mais ça fonctionne! Juste j'ai nuéroté mes frames à l'envers. Maintenant il faut que je mette le logo par dessus.
Petite commande imagemagik:
mogrify -gravity south -draw "image over 0,0 0,0 logo_dithered.png" card-highlighted/*
C'est cool y'a un mécanisme qui fait que ça retransfère pas tout à chaque fois que j'upload sur le playdate.
Ok je viens de voir que Spellcorked a de la transparence et du coup un radius sur ces coins j'ai envie de tester un truc avec la transparence.
Playtests
- 31 mars 2026
J'ai fait tester le jeu à la rencontre hedbomadaire des solodevs lyonnais.
- du 31 mars au 6 avril
Il y avait un festival de cinéma pendant une semaine, j'y suis allé avec un ami et avant chaque séance je lui faisait tester. Une fois rentré je faisais des modifications pour rééquilibrer (notamment éviter ce que les niveaux soit frustrants). C'était très bien, ça permettait de voir aussi comment il s'améliorait dans le temps. Ce n'est pas facile à équilibrer un jeu difficile.
————————————————————————————————————————
Notes diverses
Mettre à jour le SDK
Il faut rajouter set -gx PLAYDATE_SDK_PATH /run/media/paul/Developpement/Gamedev/playdate/PlaydateSDK-3.XX/ dans la config du shell (moi c'est fish, donc dans .config/fish/config.fish), il faut aussi que je change mon run.sh avec le nouveau chemin (peut-être que je pourrais aller le lire dans le path si j'étais malin).