Julien Jorge's Personal Website

Dev update du jeu Bim!

Thu May 23, 2024

Ce post a été publié sur LinuxFr.org. Vous pouvez le lire là bas, ainsi que les commentaires associés

La dernière fois je t’ai parlé de la mise en place du développement Bim!, un jeu mobile en PvP que je développe pour jouer avec les copains et aussi pour pratiquer l’approche « Entity Component System ».

Le projet a bien avancé depuis et j’ai envie de faire un petit point bilan, que voici ci-dessous.

Quoi de neuf dans Bim!

Quand nous nous sommes quittés à la fin du dernier journal nous avions un jeu fonctionnel en réseau dans un terminal, ainsi que les base d’une application Android. Ce n’était pas grand chose mais cela permettait de valider l’outillage, le cycle de dev, et les grandes lignes de l’implémentation. Depuis j’ai ajouté pas mal de nouveautés.

Dans le gameplay

Pour commencer, j’ai ajouté le fait d’éliminer les joueurs qui sont en collision avec une flamme. Ensuite j’ai ajouté l’arrêt du jeu quand il ne reste au plus qu’un seul joueur. Oui, c’est tout bête, mais si ce n’est pas codé ça n’arrive pas…

J’ai ensuite ajouté un joystick logiciel pour contrôler le joueur. Je n’aime pas trop cela mais pour l’instant je n’ai pas mieux. D’ailleurs figure-toi que ce n’est pas évident à régler ce truc et j’ai fait beaucoup d’allers-retours entre une simulation de stick analogique ou de croix directionnelle ; le déplacement unidirectionnel ou multidirectionnel quand on pointe plutôt en diagonale ; le contournement automatique des obstacles ou pas… C’est pas simple.

Lorsque je développais le prototype dans un terminal j’avais implémenté le déplacement du joueur case par case. Avec l’application graphique cela ne collait évidemment plus. J’ai donc implémenté le déplacement fractionnel pour cette dernière.

D’ailleurs, puisqu’on parle de cela, j’ai dû laisser de côté la version en ligne de commande. En effet, le joueur ne peut s’y déplacer d’une fraction d’une case, ce qui rendait les deux versions incompatibles. Soit le mouvement était saccadé dans l’application, soit il y avait une grosse latence dans le terminal. J’aurais pu isoler chaque mode (faire jouer les terminaux avec des terminaux) mais pour éviter d’avoir trop de cas à gérer j’ai préféré trancher.

J’ai ensuite ajouté la possibilité de déposer une bombe. Et oui, j’ai implémenté les flammes avant les bombes qui les créent…

Enfin, j’ai ajouté les power-ups de bombe et de flamme, ajoutant chacun une unité respective au joueur.

Et avec ça, on a suffisamment pour faire quelques parties :)

Réseau

Du côté du réseau j’ai eu quelques petites surprises. Déjà quelques messages arrivaient incomplets si je stressais le système, donc il a fallu mettre des gardes pour les ignorer.

J’ai aussi ajouté la connexion au serveur de jeu lorsqu’on lance l’appli. Sans ça, pas de jeu en réseau. Puis, cela en place, j’ai pu ensuite lancer une demande pour une nouvelle partie lorsque l’utilisateur clique sur le bouton « Play ». Ensuite j’ai ajouté la création de la partie du côté serveur, puis la proposition de la partie au joueur. Celui-ci doit la valider puis lorsque tous les joueurs ont validé le serveur donne le signal pour lancer la partie. Tout cela était assez simple puisque le protocole était déjà implémenté pour le client dans un terminal.

Concernant le regroupement des joueurs pour les matchs, j’avais initialement prévu de les réunir dans une salle nommée à leur discrétion. Il s’est avéré que c’était assez galère à gérer, et vu qu’en plus cela demandait un peu d’interface pour saisir le nom de la salle, j’ai finalement mis cela de côté en faveur d’un regroupement au premier venu. En gros, si tu lances une partie, tu joueras avec les une à trois autres personnes qui en lancent en même temps.

Autour du jeu

J’ai ajouté de quoi faire de la localisation en utilisant Gettext. Ce n’était pas hyper simple car je voulais que le fichier .pot ainsi que les .po associés soient mis à jour lors du build. Il y a bien quelques commandes dispos pour utiliser Gettext via CMake mais rien qui ne collait avec ce que je voulais. J’ai donc du bricoler implémenter magistralement, en CMakeScript, une collecte des sources à partir des targets déclarés dans les CMakeLists.txt, puis créer un nouveau target générant le .pot à partir de ces sources. Une fois cela fait le chemin du .pot au fichier de traductions compilé est assez trivial.

Bien évidemment ça aurait été trop simple s’il n’y avait eu que cela. Malheureusement j’ai aussi quelques chaînes à traduire depuis des fichiers de ressources. Pour que cela se mette bien avec xgettext j’ai ajouté une moulinette en Python pour extraire ces textes et les mettre dans un fichier C que xgettext saura lire, tout cela avec les dépendances de build correctes, ça donne:

  ressources
      |
      v
  moulinette    code source
      |              |
      v              |
  xgettext <---------+
      |
      v
    .pot            .po
      |              |
      v              |
   msgmerge <--------+
      |
      v
    msgfmt
      |
      v
     .mo

Et encore, je passe sur le fait que, sans contournement, la génération du .pot déclenchait toute la chaîne de génération des fichiers de traduction car il embarque la date de création du fichier…

Petite surprise avec les tests ThreadSanitizer qui échouent de temps en temps au lancement. Il s’avère que cet outil ainsi que AddressSanitizer n’aiment pas trop ASLR. Si jamais tu rencontres un crash au lancement d’un programme compilé avec ces outils tu sauras que ça se résout avec un sysctl vm.mmap_rnd_bits=28 (cf. le bug #1716 de ces outils).

Enfin, grosse étape, j’ai ajouté un conteneur Docker et des scripts pour le déploiement du serveur.

En dehors de ces gros morceaux, j’ai ajouté quelques bricoles de rigueur comme la compilation avec -Werror et l’écriture des fichiers .gdb lors du build release,

Note que je n’ai pas eu à retoucher l’environnement de build ni la CI, ce qui est assez chouette.

Pour la suite

Il me reste encore quelques étapes à compléter avant de faire un build public. Déjà il n’y a pas de possibilité de rejouer ; une fois le match terminé il faut relancer l’appli en faire un second. Ensuite il y a quelques ajustements de gameplay à faire : les bombes explosent trop tôt, le ressenti du stick n’est pas très agréable, le joueur devrait être bloqué par les bombes, et deux-trois autres trucs.

Idéalement il faudrait que j’ajoute un peu de latence sur les inputs aussi pour éviter de voir l’autre joueur sauter d’une position à l’autre. Ce n’est pas indispensable pour jouer mais ça serait plus agréable.

La dernière fois une personne très intelligente a laissé ce commentaire :

il y aurait sans doute pas mal de boulot pour savoir ce qu’est un bon test automatique d’un jeu.

Et bien je dois dire que les quelques tests que j’ai mis m’ont pas mal aidé dans le dev en validant le fonctionnel avant de gérer l’affichage. Ce sont des tests assez simples, du genre « flamme + joueur = élimination du joueur », ou encore « flamme + bombe = explosion de la bombe ». J’ai aussi quelques tests qui lancent un serveur et des demandes de parties, pour valider le protocole. De plus, ECS est vraiment confortable pour cela : pas besoin de créer tout un monde pour tester un truc, il suffit de créer les entités avec les bons composants et on peut faire tourner des systèmes indépendamment.

Ces derniers temps j’ai un peu ignoré les tests sur les nouveautés et je dois dire que ça manque rapidement. Je sais que dès que j’aurai à toucher par exemple aux power-ups, je vais craindre de casser quelque chose. Les tests, c’est bien.

Enfin un truc qui manque grandement ce sont de bons graphismes. Là dessus j’aimerais bien laisser la main à quelqu’un qui sait faire !

Garanti dessiné au pad.

Ça se trouve où

Pour l’instant il n’y a pas de build public mais si tu veux tu peux cloner le dépôt et taper ./setup.sh --help. Tu devrais trouver un moyen de sortir un build Linux ou un build Android en debug (pour la release il faut configurer la signature). L’application se connectera alors directement au serveur actuellement déployé.