Sommaire
Bonjour'nal,
J'ai publié une nouvelle version de Bim! et 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 GuieA_7 (site web personnel) . Évalué à 3 (+1/-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'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).
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.