...  18 août 2023

Omegamoulinette

Comme décrit dans l'article Une collision... physique, Omeganaut est passé des boites de collision internes de ZGameEditor aux boites de collision gérées par la librairie externe Bullet. Parfait, sauf qu'il est compliqué, voire impossible, de visualiser ces nouvelles boites de collision dans l'éditeur, ou même dans le jeu.

Donc ça fonctionne mieux, mais ajouter une boite de collision prend beaucoup de temps, et il est aisé de commettre une erreur, invisible pour le coup. Pas ouf, quoi. Alors pour cette nouvelle version d'Omeganaut, j'ai créé une moulinette ! Et je vais expliquer ma démarche pour convertir un modèle 3D visible en une ligne de code illisible, parce que créer des outils, c'est tout aussi rigolo que les utiliser.

C'est un jeu de shoot. Pas une simulation de collision. On n'est pas au triangle près, alors j'ai décidé de n'utiliser que les primitives de collision, à savoir :

3D primitives


  • Sphere : une simple sphère définie par un rayon.
  • Scalable-Sphere : une sphère qui peut être étirée sur les trois axes.
  • Box : un simple cube qui peut être étiré sur les trois axes pour obtenir de jolis pavés.
  • Cone : un cornet de glace qui peut être modifié en hauteur et en rayon.
  • Cylinder : pareil, modifiable en hauteur et en rayon.
  • Compound : ce n'est pas une primitive, mais un groupe qui peut contenir les primitives précédentes tout en conservant leur rotation et leur position.


J'ai commencé par un script Ruby pour Sketchup. Je ne connaissais pas ce langage qui ressemble à Python, mais ça a été rapidement plié avec l'aide de ChatGPT. D'un simple clic, le script récupère les primitives du groupe sélectionné et les convertit en lignes de texte :


Export Omeganaut collision models from Sketchup

##### Just copy/paste that in Google Sheets #####

2_SCASPHERE S_SHPERE_133x100x441 zbtCreateScalableSphereShape(1.0); zbtSetShapeLocalScaling(S_SHPERE_133x100x441,0.665,0.5,2.205);
3_BOX S_BOX_60x43x296 zbtCreateBoxShape(0.3,0.215,1.48);
9_COMPOUND S_SHIP zbtCreateCompoundShape();zbtAddChildShape(S_SHIP,S_BOX_60x43x296,-0.98,-0.17,1.01,0,-0.086,0);zbtAddChildShape(S_SHIP,S_SHPERE_133x100x441,0,-0.1,-0.24,-0.016,0,0);zbtAddChildShape(S_SHIP,S_BOX_60x43x296,0.98,-0.17,1.01,0,0.086,0);

Dans cet exemple, le groupe est constitué d'une Scalable-Sphere, d'une seule Box utilisée deux fois, et d'un Compound qui contient leur position et rotation par rapport au centre du groupe. Les primitives sont nommées automatiquement en fonction de leur forme et des transformations qui leur ont été appliquées. Le Compound prend le nom du groupe. Ainsi, pas de problèmes de doublons par la suite. Je n'ai qu'à coller ce résultat à la fin de l'onglet Shapes du Google Sheet contenant les données du jeu. Comme les éléments sont séparés par des tabulations, ils se distribuent automatiquement dans les bonnes colonnes du tableau :


Export Omeganaut collision models from Sketchup


Puis je copie/colle le Name qui m'intéresse dans l'onglet Models. Dans notre exemple, le modèle M_SHIP utilisera le shape Compound S_SHIP. Vient ensuite Google App Script, qui ressemble à javascript, toujours avec l'aide de ChatGPT :

  • Clean Shapes : permet de supprimer les doublons et les shapes inutilisées, puis de faire une réorganisation alphabétique. Cette réorganisation est importante, car le nom des primitives doit être déclaré avant d'être utilisé dans un Compound.
  • Export Shapes : me sort le code à copier/coller dans ZGameEditor : une longue ligne que je n'aurais pas besoin de vérifier. Pour l'exemple, je n'ai exporté que les données traitées ci-dessus :

xptr S_SHPERE_133x100x441,S_BOX_60x43x296,S_SHIP;void initBulletShapes(){S_SHPERE_133x100x441=zbtCreateScalableSphereShape(1.0);zbtSetShapeLocalScaling(S_SHPERE_133x100x441,0.665,0.5,2.205);S_BOX_60x43x296=zbtCreateBoxShape(0.3,0.215,1.48);S_SHIP=zbtCreateCompoundShape();zbtAddChildShape(S_SHIP,S_BOX_60x43x296,-0.98,-0.17,1.01,0,-0.086,0);zbtAddChildShape(S_SHIP,S_SHPERE_133x100x441,0,-0.1,-0.24,-0.016,0,0);zbtAddChildShape(S_SHIP,S_BOX_60x43x296,0.98,-0.17,1.01,0,0.086,0);}xptr GetBodyShape(int type){switch(type){default:trace("ERROR: No body for " + ObjectNames[type]);return Shape_Sphere_100;case M_SHIP:return S_SHIP;}}

Le script déclare d'abord les pointers avec leurs noms uniques S_SHPERE_133x100x441, S_BOX_60x43x296 et S_SHIP. Puis, il déclare la fonction initBulletShapes() qui va initialiser la primitive correspondant au pointer Shapes en y appliquant les transformations de rotation et de scale nécessaires. Enfin, il déclare la fonction GetBodyShape(int type) qui permet de faire correspondre le shape au model. Ici, quand on fait apparaitre un model M_SHIP, cette fonction lui attribue le shape S_SHIP. Le tout est concaténé en une seule ligne. Nul pour la lisibilité, mais pratique pour copier/coller rapidement avec un triple clic souris.


En plus de cette moulinette, j'ai corrigé des petits bugs, optimisé toutes les fonctions et ajouté quelques surprises. Il ne me reste plus qu'à créer du contenu !



À vous de jouer :