Journal De beaux graphismes dans la version 4 de Bim!

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
33
1
fév.
2025

Sommaire

Bonjour'nal,

J'ai publié une nouvelle version de Bim! et c'est sans aucun doute la meilleure version sortie jusqu'ici. Vise un peu !

Nouveautés

  • De nouveaux assets de jeu par Aryeom: l'avatar du joueur, la bombe, les flammes, les caisses.
  • D'autres nouveaux assets de jeu: les murs et le sol.
  • La possibilité pour le joueur d'activer des fonctionnalités de jeu pour varier le fun dans les combats.

Rien que pour les assets ça vaut déjà le coup ! Côté fonctionnalités de jeu le joueur peut activer une chute de blocs à partir de deux minutes de combat pour réduire la taille de l'arène. Ça fait un pic d'adrénaline et permet de conclure les parties qui s'éternisent.

Capture d'une partie Recherche d'adversaire

Franchement le mieux c'est que tu l'essayes, soit en installant l'APK directement sur ton téléphone, soit en m'envoyant un mail à bim-game@gmx.com en indiquant l'e-mail que tu utilises pour te connecter au Play Store. Ainsi je pourrai te mettre dans la liste des testeurs, après quoi tu pourras installer le jeu directement depuis le Play Store (Choisis cette option, j'ai besoin de testeurs pour pouvoir publier l'appli).

Pendant que ça s'installe je te propose d'aller en détail dans le dev de cette version. Pour rappel, il s'agit d'un jeu libre, code et assets, de type « dernier debout ». Uniquement disponible sous Android. Les matchs ne se font qu'en ligne, il vaut donc mieux se synchroniser avec une connaissance avant de lancer un match ; sinon le jeu ne te trouvera pas d'adversaire.

Anecdotes de dev

À propos de nouveautés de game-play

J'ai implémenté l'activation des fonctionnalités optionnelles avec un « et » logique : il faut que tous les joueurs l'aient activé pour qu'elle s'applique. C'est une décision pratique car elle me permet d'ajouter des fonctionnalités dans les nouvelles versions tout en faisant jouer ensemble des joueurs qui n'ont pas la même version.

D'un autre côté je me dis qu'un « ou » logique permettrait aux joueurs de découvrir des trucs qui ne leur sont pas encore accessible, ce qui est motivant. J'hésite encore. Je pense que ça va être très frustrant pour les utilisateurs qui activent une option de se retrouver dans des parties sans ces fonctions.

En l'écrivant je me dis qu'il faudrait trois états : si le joueur a désactivé l'option, le jeu ne l'active pas, mais s'il ne l'a pas encore débloquée et qu'un autre joueur l'active, alors le jeu l'active.

Un mot sur la consommation mémoire.

Lors de la sortie de la précédente version on m'a remonté cet intriguant message:

Si on ne peut pas jouer, c'est qu'il n'y a personne en ligne ?

Alors l'affirmative me semble évidente, mais je me demande, que veut dire cet utilisateur par « on ne peut pas jouer » ? Je lance l'app, le bouton pour lancer un match reste gris. Mmh, ça ne sent pas bon, j'ouvre une fenêtre. De là je tente une connexion SSH au serveur. Rien. Ok, rien ne marche, je reboot et regarde les logs. Il semblerait que toute la RAM ait été consommée. Je passe en surveillance.

Après quelques parties l'évidence surgit : la conso mémoire est énorme. Heureusement j'ai bien appris ma leçon : Engineering time is expensive, memory is cheap. J'achète donc une instance plus grosse.

Je blague bien sûr, j'ai encore un peu de fierté. C'est parti pour l'analyse. J'exclus rapidement la possibilité d'une fuite car d'autres outils m'auraient déjà remonté le problème. Je mise plutôt sur une ressource qui croît sans cesse. Pour creuser cette piste je sors heaptrack et je scripte une succession de recherches d'adversaire suivie d'un match.

Chouette outil que Heaptrack. En explorant la vue consommation mémoire en fonction du temps je vois deux choses. D'une part ça croit de manière constante avant de chuter soudainement, ce qui est attendu. En effet, comme les échanges client/serveur se font en UDP, le serveur n'est pas au courant quand un client part. Pour compenser il y a une passe de nettoyage régulière pour supprimer les clients dont on n'a pas de nouvelles. Je remarque que chaque pic est un peu plus haut que le précédent.

D'autre part je vois dans cette vue de nombreuses allocations liées à l'ordonnanceur du serveur. Beaucoup plus que ce que j'attendais. Après analyse ça s'explique parfaitement : j'avais implémenté la gestion de la déconnexion des clients en programmant un rappel 30 secondes après un message du client. Autrement dit, à chaque message du client je programmais un rappel, et la file d'appels grandissait alors à toute vitesse en empilant des fonctions obsolètes (dont l'appel était finalement omis). C'est pas malin !

J'ai réécrit tout cela de manière plus économe. Je maintiens pour chaque client une date à partir de laquelle on peut le supprimer. Quand un client donne des nouvelles, je mets à jour la date. D'autre part je programme une suppression des clients déconnectés à intervalle fixe. Comme cela ça ne s'accumule plus.

Et puis pendant que j'étais dans la conso mémoire j'en ai profité pour faire une passe sur les allocations temporaires. Il y en a tellement ! Beaucoup étaient liées à l'ordonnanceur, ce qui s'est plus ou moins résolu dans le paragraphe précédent. Ensuite viennent les messages échangés entre le client et le serveur. Pour ça c'est simple, je recycle maintenant les messages. Et enfin il y avait bien sûr des opérations sur des std::string. Classique. Heureusement cela ne concerne que les logs affichés dans le terminal, qui sont désactivés en prod, donc pas trop de problème. Mais j'ai quand même réécrit deux-trois trucs pour qu'il y en ait moins.

Répartition des bonus

Depuis la toute première version jouable en ligne j'entends toujours la même remarque, comme quoi les bonus seraient tous en haut de la carte. Alors déjà c'est faux, puisqu'on voit bien qu'il y a des bonus en bas aussi, et puis ça va bien, je sais que j'ai implémenté une répartition aléatoire. Pour prouver aux perdants que tout va bien j'ai généré 1000 niveaux pour construire une heatmap de répartition des bonus. Plus c'est clair, plus fréquemment il y a un bonus dans la case :

Eeeeeet galère…

Bon ok, allons voir ce code de génération des niveaux. Ouch, un random avec un modulo… et un swap qui va causer la remise en jeu de cellules déjà considérées ! Et bien, c'est pas jojo.

Maintenant je me souviens que lorsque j'avais implémenté cela j'avais pris le chemin le plus court en me disant qu'il faudra que je le remplace par cet-algo-là-tu-sais (Fisher-Yates). Clairement, cela m'est sorti de la tête. Soit. Après implémentation ça sort mieux :

Mouais, il reste quand même un problème dans les cases en haut. Là encore c'est une histoire de raccourcis. Pour la génération de nombres aléatoires j'utilise xoshiro256plusplus, dont l'état est constitué de quatre valeurs. J'initialise la première avec la graine du jeu puis je laisse les trois autres à zéro. Très mauvaise idée ! xoshiro256plusplus n'aime pas les zéros. Après avoir correctement initialisé le générateur j'obtiens ceci :

Ah c'est beaucoup mieux !

Une petite parenthèse. Il y a quelques semaines je regardais une présentation très intéressante de Mike Acton. Il a des propos assez radicaux et à un moment il a dit un truc qui m'a intrigué. Il était question de priorisation dans l'implémentation, qu'il fallait éviter le future-proofing et que quoi qu'on code aujourd'hui en anticipation d'un problème de demain, un problème qui n'a aucune réalité aujourd'hui, on va très certainement trouver cela mauvais quand le problème se présentera ; si jamais il se présente. En effet le temps que le problème arrive on aura évolué en tant que développeur et sûrement qu'on le traiterait différemment si on devait partir d'une feuille blanche. Et en attendant qu'il se présente, tout le code le concernant est un handicap. Je suis plutôt en phase avec tout ça. Et puis il ajoute quelque chose de ce genre : commence par implémenter au plus direct, comme si tu devais livrer demain, et seulement ensuite, si tu as le temps, tu peux retravailler ton code.

J'ai du mal à trancher sur ce conseil. Ça m'a l'air d'être une bonne idée, mais en même temps si la contrainte est de livrer le lendemain je m'attends à voir arriver des variables globales, du gros couplage, des allocations dans tout les sens, et d'autres trucs pas cool. Ça ne colle pas trop avec le reste de son discours. Je pense qu'une grande partie de la difficulté du dev est justement de trouver un bon compromis en fonction des ressources disponibles, sans trop en faire mais sans bâcler pour autant.

À cause de cette présentation je me demande si j'ai bien ou mal fait en implémentant la génération de niveaux ainsi. D'un côté ça m'a permis d'avancer rapidement, de l'autre côté c'était clairement une solution insatisfaisante.

Fin de la parenthèse, revenons à nos niveaux. La répartition est bonne, j'annonce la nouvelle à la famille et on lance une partie. Première bombe, en haut. Deuxième bombe, en haut aussi. Troisième bombe, pareil… Quant aux flammes, elles apparaissent en bas. Rhâââ, le souvenir du code me saute au visage, aucun doute sur le problème. Je prends bien des cases au hasard mais elles sont ordonnées lorsque je leur affecte les bonus, en séquence !

Les bombes Les flammes

J'implémente un mélange des cases avant d'y mettre les bonii, et hop :

Les bombes Les flammes

Conception graphique des nouveaux murs

Tiens comme j'ai dû dessiner un peu pour cette version je vais te partager un peu le processus. N'étant ni graphiste ni illustrateur je galère toujours à faire mes dessins.

L'idée était donc ici de produire des sprites pour les murs afin de remplacer les cases grises des versions précédentes. Je veux une perspective de jeu vu de haut, à la manière des RPGs des années 90, donc je commence par une recherche d'images pour m'imprégner du style, puis je fais quelques croquis.

Ensuite je me documente un peu pour créer des idées de textures, puis je dessine les sprites.

Bon, ça fonctionne, mais… j'sais pas, ça ne me plaît pas. Trop réaliste, trop sombre, trop chargé. Ce n'est pas ce que je veux. Je refais un peu de veille, je regarde ce qui se fait ailleurs, puis retour sur le carnet pour une petite étude. Quand est-ce trop chargé ? Quand est-ce trop vide ? Comment exprimer l'intention avec peu de traits ?

Sur cette planche je cherche les éléments clés. Un seul bloc en profondeur, deux en largeur, c'est suffisant. J'aime bien le décalage en hauteur entre les blocs du haut aussi. Cette fois je pars sur des couleurs plus vives et plus contrastées.

À voilà, c'est beaucoup mieux !

À Propos de la livraison sur les stores.

Côté F-Droid il ne me reste plus qu'à trouver le temps et la motivation. J'ai finalement pu me créer un compte sur GitLab mais ça m'énerve. J'ai dû utiliser une vieille adresse que je n'utilisais plus et que je souhaitais supprimer tout ça parce que le site n'accepte que les e-mails « entreprise ». C'est idiot, laissez-moi me connecter en tant que particulier, comme ça je pourrai pousser pour utiliser GitLab au boulot ! J'aurais acquis de l'expérience avec l'outil sur mon temps libre et ça sera un bon argument pour faciliter la migration. Ça sert à rien de nous forcer à nous contorsionner pour nous inscrire. Je suis inscrit et je ne suis pas plus une entreprise qu'avec mon compte perso. En plus F-Droid est un store pour tous, ça rime à rien d'empêcher les contributions de particuliers.

Côté Play Store c'est en cours. J'ai passé deux heures à remplir leurs formulaires, c'était hyper pénible. J'ai dû pondre une politique de confidentialité et des illustrations à pas d'heure pour finir de remplir tout leur truc (j'aurais pu remettre au lendemain cela dit). J'ai aussi eu plein d'avertissements lors de l'upload du bundle. Déjà je n'avais pas de bundle mais un APK, donc il a fallu chercher comment sortir un bundle. Puis j'ai eu des problèmes de générations des symboles de debug parce que le build Gradle ne détectait pas la bonne version du NDK.

Ensuite le store me dit de remplir d'autres formulaires pour justifier l'autorisation READ_PHONE_STATE. Mmmh je n'ai pas besoin de connaître l'état du téléphone… d'où ça sort ? J'ai découvert que le build Android sortait un fichier avec les causes des permissions implicites, bien pratique. Il s'est avéré que certaines dépendances ne déclaraient pas de minTargetSdk, ou le déclarait mal, ce qui faisait que ça ciblait de vieux SDK pours lesquels la permission était automatique. Une fois cela corrigé, c'est passé.

La dernière étape avant de pouvoir rendre l'application publique sur le Play Store est d'exécuter un test fermé avec au moins 12 testeurs pour au moins 14 jours. Il me manque des testeurs ! Si tu veux bien filer un coup de main il me faut juste l'e-mail que tu utilises pour le Play Store. Je le mettrai dans la liste des testeurs et tu pourras installer l'application via ce lien. Évidemment je ne ferai rien d'autre avec cette adresse, mais il faut me faire confiance. Quant à Google, je crois qu'ils ont déjà cette adresse :) Si t'es chaud envoie-moi les infos à bim-game@gmx.com. Merci !

  • # GG

    Posté par  (site web personnel) . Évalué à 7 (+5/-0).

    Très beau journal ! Du jeu, du graphisme, du code, c'est très sympa à lire.

    Tu t'en es vraiment bien sorti avec tes pierres (le style cartoon marche bien). Les assets de Aryeom sont jolis aussi (mais qui en aurait douté).

    J'ai du mal à trancher sur ce conseil. Ça m'a l'air d'être une bonne idée, mais en même temps si la contrainte est de livrer le lendemain je m'attends à voir arriver des variables globales, du gros couplage, des allocations dans tout les sens, et d'autres trucs pas cool. Ça ne colle pas trop avec le reste de son discours. Je pense qu'une grande partie de la difficulté du dev est justement de trouver un bon compromis en fonction des ressources disponibles, sans trop en faire mais sans bâcler pour autant.

    J'imagine que ce conseil s'applique déjà plus à des devs expérimentés (pour qu'ils se cantonnent à du code naïf et propre, plutôt que complexe et propre) qu'à des débutants (qui feront simpliste plus que simple).

    Personnellement mon code est truffé de commentaires "TODO" (qui explique que le code pourrait faire mieux dans tel ou tel cas), et d'expérience :
    - une infime partie des problèmes que j'avais anticipés finissent par réellement apparaître.
    - une grosse partie de ces potentiels problèmes ne gênent personne, et ces "TODO" existent toujours 10 ans après.
    - pas mal de ces "TODO" finissent par disparaître purement et simplement avec le code qu'ils accompagnent (ex: le code passe en obsolète puis est remplacé par complètement autre chose).

    • [^] # Re: GG

      Posté par  . Évalué à 4 (+3/-0).

      Même sentiment
      Sur la partie "prévoir le futur" avec des systèmes modulables ou génériques, je considère que ce qu'on a prévu comme extension possible en codant le premier cas d'usage s'avère généralement insuffisant quand le second arrive et qu'on ne peut valider que tout est correct qu'avec le troisième. Donc inutile de prévoir trop en amont c'est du temps perdu, de la complexité inutile et en plus y a de grandes chances que ce soit fait pour rien.
      Ça n'empêche pas d'avoir une première version propre et modulaire avec ce qu'il faut d'abstraction mais en gardant en tête que de toute façon ça ne sert à rien d'en faire trop puisqu'on a aucune garantie que ça servira sous cette forme

    • [^] # Re: GG

      Posté par  (site web personnel, Mastodon) . Évalué à 6 (+4/-0).

      Et puis il ajoute quelque chose de ce genre : commence par implémenter au plus direct, comme si tu devais livrer demain, et seulement ensuite, si tu as le temps, tu peux retravailler ton code.

      La deuxième partie est importante. J'ai entendu cette approche dans le cadre du TDD (développement dirigé par les tests) dans un talk de Ian Cooper. L'idée est d'implémenter une fonctionalité en 3 étapes:

      • étape "rouge": écrier un testpour la fonctionalité qui ne passe pas
      • étape "verte": faire en sorte que le test passe. À cette étape, il faut aller au plus simple et faire juste en sorte que le test passe. Dans cette étape il y adroit aux variables globales et autres méthodes moches. Il appelle ça "duct tape programming".
      • étape "refactoring": une fois que tu as réussi à faire fonctionner le truc, tu as maintenant une bonne vision de ce qu'il faut changer exactement dans ton architecture pour cette fonctionalité, juste assez pour rendre ton code propre. Cette étape évite d'accumuler de la dette technique, mais comme elle est faite après avoir un truc fonctionnel, elle évite de se lancer dans de l'architecture qui ne sert à rien pour l'instant.
      • [^] # Re: GG

        Posté par  (site web personnel) . Évalué à 3 (+1/-0).

        Tout à fait ; je pratique le TDD comme ça depuis 2007. Ça permet d'avoir confiance dans son travail de refactoring (vu qu'on a les tests qui permettent de détecter la majeur partie des régressions qu'on pourrait introduire) et donc d'avoir un code propre.

        Ça permet de limiter la dette technique (on évite le côté "le code est immonde, je ne comprends pas comment ça marche, je ne touche à rien") mais ça ne la supprime évidemment pas (tu veux devoir réécrire des composants dont la conception a largement montré ses limites mais sans avoir les ressources pour le faire).

  • # à passer en dépêche

    Posté par  . Évalué à 7 (+5/-0).

    J'ai l'impression de me répéter souvent : ce journal devrait être passé en dépêche, ça le rendrait plus visible. Parce que c'est super intéressant.

    • [^] # Re: à passer en dépêche

      Posté par  (site web personnel) . Évalué à 4 (+2/-0).

      Merci ! Perso j'ai choisi un journal parce que ce n'est pas encore tout à fait prêt. Il faut encore habiller l'interface pendant les parties, et il faudrait que l'app soit dispo sur les stores pour éviter de la frustration, mais on n'est plus très loin de ça. Cela dit la licence du journal est là pour permettre sa diffusion :)

  • # Testeurice

    Posté par  (Mastodon) . Évalué à 3 (+1/-0).

    Comme tu le sais, j'ai l'honneur d'être un de tes testeurs. Pour que cela soit utile, je dois jouer régulièrement, tous les jours où on s'en fout

    J'ai joué avec 13 et 9. On s'est bien amusé.

    On est d'accord que pour le moment, comme ingrédient supplémentaire optionnel, il n'y a que les blocs qui tombent ? Les trois autres, je peux jouer autant que je veux, je ne les débloquerai pas vu qu'ils n'existent pas encore

    Surtout, ne pas tout prendre au sérieux !

    • [^] # Re: Testeurice

      Posté par  (Mastodon) . Évalué à 3 (+1/-0).

      On a encore joué. On a du mal à s'arrêter :-)

      Je crois qu'il y a un petit bug mais j'arrive pas à le reproduire à la demande. Parfois, le personnage dépose une bombe 💣 dès qu'il apparaît ou quand il commence à se déplacer (pas eu assez le bug pour savoir si option 1 ou option 2)
      Une fois que cela arrive cela arrive à toutes les parties suivantes. Il faut quitter le jeu et revenir et tout s'arrange

      Sinon, je trouve bizarre que le son de je dépose une bombe se produit même quand on ne peut pas en déposer (et donc qu'on en dépose pas)

      Surtout, ne pas tout prendre au sérieux !

      • [^] # Re: Testeurice

        Posté par  (site web personnel) . Évalué à 2 (+0/-0).

        Comme tu le sais, j'ai l'honneur d'être un de tes testeurs.

        Je t'en remercie grandement :)

        Pour que cela soit utile, je dois jouer régulièrement, tous les jours où on s'en fout

        On s'en fiche, joue quand tu as envie :) Le plus dur est de trouver des adversaires quand il n'y a personne autour.

        On est d'accord que pour le moment, comme ingrédient supplémentaire optionnel, il n'y a que les blocs qui tombent ? Les trois autres, je peux jouer autant que je veux, je ne les débloquerai pas vu qu'ils n'existent pas encore

        Tout à fait ! J'ai mis en place les boutons mais pour l'instant il n'y a rien derrière.

        Parfois, le personnage dépose une bombe 💣 dès qu'il apparaît ou quand il commence à se déplacer (pas eu assez le bug pour savoir si option 1 ou option 2)
        Une fois que cela arrive cela arrive à toutes les parties suivantes. Il faut quitter le jeu et revenir et tout s'arrange

        Intéressant… Je vais voir si je trouve un truc. Aurais-tu l'heure approximative vers laquelle c'est arrivé, que je regarde la partie ?

        Sinon, je trouve bizarre que le son de je dépose une bombe se produit même quand on ne peut pas en déposer (et donc qu'on en dépose pas)

        Alors il me semble que je n'ai pas encore mis de son pour ça :) Ne serait-ce pas le bruit de l'appui du bouton ? C'est le même sur tous les boutons.

        • [^] # Re: Testeurice

          Posté par  (Mastodon) . Évalué à 4 (+2/-0).

          Le plus dur est de trouver des adversaires quand il n'y a personne autour.

          J'ai des enfants qui sont déjà accro au jeu, cela aide ;-)

          Aurais-tu l'heure approximative vers laquelle c'est arrivé, que je regarde la partie ?

          Sans certitude, je dirais aux alentours de 21h. On jouait à trois.

          Ne serait-ce pas le bruit de l'appui du bouton ?

          J'imagine que oui même si je n'ai pas remarqué que les autres boutons faisaient le même bruit. Cela ne modifie pas mon humble opinion, je trouverais plus intuitif que l'appui du bouton dépose bombe ne fasse du bruit que quand il fonctionne aka qu'une bombe peut-être et est déposée

          Ah, je crois que je ne l'ai pas encore dit, les rares fois qu'on est arrivé jusqu'à la chute des pierres, on a apprécié la feature

          Surtout, ne pas tout prendre au sérieux !

          • [^] # Re: Testeurice

            Posté par  (Mastodon) . Évalué à 3 (+1/-0).

            Coucou

            des trucs bizars ce soir

            1. on est deux a lancer le jeux mais on reste tous les deux sur l'ecran d'attente avec l'info que l'on est seul

            2. le jeu me dit que j'ai gagne mais l'autre personne n'a pas perdu et continue de jouer seul dans le jeu

            entre 20:43 et 20:54

            Surtout, ne pas tout prendre au sérieux !

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.