Ludum Dare 47 post-codem

Comme annoncé dans mon précédent article, j’ai participé au Ludum Dare. Vous pouvez jouer à ma contribution directement par ici.

La tradition dans les game jams est d’écrire un « post-mortem », c’est un petit (grand) texte que l’on écrit à tête reposée après avoir bossé comme un oufzor pendant tout un week-end à créer un jeu.

Je trouve l’expression « post-mortem » étrange. Je ne suis pas mort, même si ça a été assez intense et que j’en suis sorti fatigué. Donc : « post-codem ».

Fonctionnement du jeu

Les premiers retours m’indiquent que c’est difficile à prendre en main au départ. J’ai donc fait une petite vidéo de démo explicatoire. J’y parle anglais, c’est une catastrophe. Vous avez le droit de mettre en mute.

L’aire de jeu est une piscine qui se recouvre entièrement de boue et de lierre (les petits traits verts). Vous sélectionnez une première case, en appuyant sur le bouton « 1 », puis vous en sélectionnez une deuxième, et elles s’échangent. Le but est de construire, par échanges successifs, des boucles fermées de lierre, ce qui déclenche la suppression de la boue qui est à l’intérieur.

Il faut commencer par créer une boucle à côté de la fontaine d’eau en haut à gauche. L’eau avance lorsqu’elle est en contact avec une zone sans boue. Vous devez la propager petit à petit jusqu’à l’arrivée en bas à droite.

Vous ne pouvez pas échanger une case si elle n’est pas unie, c’est à dire ayant un mix de boue et d’eau. C’est gênant lorsque vous avez deux boucles proches : vous ne pouvez pas créer de mini-boucle entre les deux pour les fusionner. J’ai donc ajouté un pouvoir spécial, lorsque vous activez le bouton « 2 » sur une case comportant de la boue, un trait vert se rajoute, qui peut vous permettre de finaliser une boucle.

Par le pouvoir du lierre !!

Post-codem au sujet du jeu

Le thème était : « Stuck in a Loop », c’est à dire : « Coincé dans une boucle ».

Il y a deux modes de participation au Ludum Dare :

  • Jam : vous avez 3 jours pour créer un jeu vidéo, en équipe ou en solo. Vous pouvez réutiliser des images, des sons et des codes existants (en respectant les licences et autres fucking copyright, évidemment).
  • Compo : c’est le « hard mode ». Vous n’avez que 2 jours, en solo. Tous les éléments de votre jeu doivent être originaux et doivent avoir été créés durant ces 2 jours. Obligation de partager le code source. Mais vous pouvez utiliser des outils de création privatifs (Unreal Engine, par exemple).

J’ai choisi compo. C’est pas que je veuille me la péter en démarrant directement en hard mode. C’est juste que j’avais mon week-end de dispo, mais pas le lundi après. Et les autres contraintes de la compo me convenaient.

Bien entendu, je n’ai pas eu le temps de réaliser toutes les idées que j’avais. Ça arrive à chaque projet (artistique ou non). Comme c’est systématique, on sait à l’avance qu’on ne pourra pas tout faire, on peut donc diminuer ses prétentions dès le départ. Mais même ainsi, les prétentions restent trop grandes, et on finit toujours par finir à l’arrache.

Ouais, à la

Comme dirait Helmut : voilà un jeu de mot franco-allemand qui Kohl très bien à l’article.

Échec de la continuous delivery

J’ai tellement galéré à coder (j’explique pourquoi dans le dernier chapitre de cet article), que ce n’est que le dimanche à 12h que j’ai eu une version pre-pre-alpha, comportant uniquement la fonction d’échange et la détection des boucles.

Note pour les prochains Ludum Dare : se réserver des petites plages de temps pour tester et faire tester son jeu par des gens quelconques. Ça permet d’ajuster la difficulté et éventuellement de glaner de nouvelles idées.

Ce que j’avais imaginé au début : coder un petit bout de truc, le montrer aux gens qui vivent avec moi, même si ça ne constitue pas un vrai jeu, montrer le bout de truc suivant, et ainsi de suite. On aurait avancé tous ensemble durant le week-end, ces mini-démonstrations régulières auraient permis de me faire pardonner le fait que je me serait comporté en geek pendant 48 heures.

Au lieu de ça, j’ai passé les trois premiers quarts du week-end à grogner devant mon ordinateur. Lorsque quelqu’un venait me voir, je lui grognais que rien ne marchait. Pas très interactif.

Groumpf ! A marche pô !

Échec de l’explication du mécanisme du jeu

J’avais prévu de faire un petit tutoriel, qui s’est terminé en un pseudo-manuel écrit en 20 minutes, sous forme de docstring au début du code python. J’ai ensuite pseudo-copié-collé ce manuel pour le mettre dans la description Ludum Darienne.

Ça n’aide pas trop à comprendre le fonctionnement du jeu quand on le découvre. D’où la petite vidéo que j’ai faite après. Qui est elle-même créé à l’arrache, mais ça c’est parce que j’ai très peu d’expérience dans ce domaine. Je cause très mal anglais alors que d’habitude en vrai conversation je m’en sors potablement. De plus, on m’entend prendre de grandes inspirations avant chaque phrase. C’est malaisant.

Même la longueur de la vidéo est une erreur. J’explique tous les mécanismes du jeu durant les 4 premières minutes, puis je passe 6 minutes à terminer ma partie. Lorsque les ordispectateurtrices-joueureuses arrivent sur ma page Ludum Darienne, ces personnes voient une vidéo ayant une durée de 10 minutes. Elles ne la regardent pas forcément, parce que c’est trop long. Si la durée affichée avaient été plus courte, il y aurait eu plus de chances qu’elles cliquassent sur « play », parce que gâcher 4 minutes de sa vie dans notre monde actuel est quelque chose d’encore à peu près acceptable. Ce serait d’ailleurs intéressant de connaître la probabilité de clic sur un bouton « play » en fonction de la durée d’une vidéo, mais ce n’est pas le sujet.

Les graphismes sont manifestement beurkys. Les explications du jeu utilisent le terme « vines » (du lierre), il faut comprendre que ça correspond aux traits verts. Ces traits sont une représentation métaphysique aristotélicienne du lierre quand on n’a pas eu le temps de le dessiner correctement.

Possible échec de la promotion de Squarity

Cette participation au Ludum Dare, et les futures participations à d’autres game jams, n’ont pas pour but de créer un jeu génial qui sera premier au classement et restera dans la mémoire de l’humanité (même si ça me plairait bien). Le but est de promouvoir Squarity.fr, ma plate-forme de création et partage de jeux.

Je veux provoquer chez les Ludum Daristes une réaction de type :

« Voyons voir ce jeu… Moui bof. Pas génial.

Oh, mais quelle est donc ce site web étrange… Squarity ? Mais que vois-je dans la partie droite ? Une fenêtre de texte avec du code python dedans, et d’autres informations qui semblent définir le jeu auquel je suis en train de jouer !

Que va-t-il se passer si je change des trucs et que je clique sur le bouton de validation en-dessous ? Oh bon sang ! Le jeu se modifie instantanément ! Ce site est génial ! Je vais de ce pas m’en servir pour créer tous mes prochains jeux. Je participe au Ludum Dare, c’est bien que je veux créer des jeux pour le restant de ma vie !

Mon avenir sera squaritien ou ne sera pas ! »

Pour provoquer une telle réaction, il faut bien entendu que Squarity soit amélioré (ce qui se fera progressivement), mais la moindre des choses aurait été que je commente mon code python, afin d’aider le monde à comprendre son fonctionnement, et par là même le fonctionnement de Squarity.

J’ai échoué sur ce point, mon code est cradingue et très peu documenté. J’essayerais d’arranger ça dans les jours à venir. Et puis je voulais aussi créer un mini-jeu tutoriel de Squarity. Tant de choses à faire, comme d’habitude…

Les carrés sont l'avenir du monde

Succès de la fontaine !

J’avais prévu d’afficher la quantité de mana sous forme d’une jauge dans un coin de l’aire de jeu. Mais en me levant le dimanche matin, j’ai eu une idée géniale : Squarity ne permet que d’afficher des images, mais la transparence est gérée. Lorsque le mana est bas, je peux noircir la fontaine en superposant plus ou moins de pixels noirs transparent dessus !

Pas besoin de perdre de temps à dessiner une jauge de mana et à coder des choses compliquées pour l’afficher. Avec 4 lignes de code dégueux et un seul sprite supplémentaire (créé rapidement à partir du sprite de la fontaine, en mettant tous ses pixels en noirs-transparents), j’avais un indicateur de mana bien mieux intégré dans le jeu.

Pour fignoler le tout, j’ajoutais un petit log indiquant la quantité exacte de mana lorsqu’on sélectionne la fontaine.

À droite de la fontaine se trouve l’image correspondant à son « ombre ».
if self.pool_mana < 100:
    nb_fountain_darking = (100 - self.pool_mana) // 5
    for _ in range(nb_fountain_darking):
        gamobjs.append("darkfountain")

Je vois aussi cette fontaine comme un très très lointain clin d’œil à l’un de mes jeux préférés intemporels et intraspatial : Might and Magic – World of Xeen.

Alamar, you misguided mechanism ! You’ll destroy us all !

N’ayant pas peur de faire de la surenchère, je rajoutais un autre lointain clin d’œil à World of Xeen : dans le pseudo-scénario de mon jeu, le personnage de la « Countess du Swagging » est une référence au mot de passe secret « Count Du Money ».

Cette fontaine est un auto-clin d’œil, puisque c’est le logo de New World Computing

Si jamais je continuais le dev de ce jeu

Je note ici toutes les idées que je pourrais rajouter dans une hypothétique version post-ludum.

Faciliter le début en pré-plaçant du lierre

Si on n’a pas de bol, on commence comme ça :

Peu de lierre partent de la zone d’eau. Pour y coller des boucles, on risque d’être obligé d’utiliser le pouvoir d’ajout de lierre. Or c’est un pouvoir qui coûte assez cher en mana, car on ne devrait normalement l’utiliser qu’en dernier recours.

J’aurais dû ajouter automatiquement quelques connexions partant de la zone d’eau. Quelque chose comme ça :

C’est beaucoup plus facile de tracer un chemin depuis le trait vertical qui est sous la zone jusqu’au trait diagonal, ou bien depuis le trait horizontal à droite jusqu’au trait diagonal, et ainsi créer une première boucle sans s’arracher les cheveux.

Une « learning curve » moins violente

Il faudrait ajouter des niveaux. Les premiers seraient rendus plus facile par :

  • un tutoriel,
  • des maps plus petites,
  • des délimitation de cases plus marquées,
  • l’absence de traits diagonales.

J’ai eu beaucoup de remarques au sujet des délimitation de cases. Les gens analysent la map, repèrent une case qui leur convient, et s’aperçoivent ensuite que ce n’en est pas une mais que c’est 4 coins de 4 cases différentes. J’avais fait exprès de brouiller les limites entre les cases parce que je trouvais ça cool et j’aime créer des choses difficiles pour ensuite prendre un air condescendant auprès des gens qui n’y arrivent pas. Mais peut-être que j’ai overkillé.

Alors que les grands comédiens gomment les coupures entre vers lorsqu’ils déclament des alexandrins, je gomme les coupures entre cases lorsque je crée des jeux grid-2D.

J’embroche vos rimes, mon épée vous tru-Cid.

Un monde ouvert

Il y aurait une aire de jeu plus grande que ce que peut afficher l’écran. On ne pourrait pas scroller partout. On aurait un personnage et le scroll serait limité autour de lui. On pourrait déplacer ce personnage uniquement vers une case recouverte d’eau. Pour progresser dans le monde, il faudrait donc faire des boucles et propager l’eau petit à petit.

Par contre, ça gâcherait un peu si on permet de faire des boucles géantes, et surtout il y aurait le risque que le temps de calcul de vérifs des boucles soit trop long. Donc il faudrait limiter les tailles de boucle. Ce qui pourrait être sujet à des bonus : au début on ne peut faire que des boucles ayant une longueur de 5, puis 6, puis 7, etc.

Bien entendu, ce grand monde ouvert serait truffé de bonus, de mana, de sorts et de pouvoirs à gagner. Pour récupérer quelque chose, il faudrait amener son personnage dessus, donc l’entourer avec une boucle.

Et puis des passages secrets et des zones super compliquées à atteindre. Par exemple un grand couloir de 3 cases de large avec un super bonus au bout.

Je voulais aussi faire une super blague, avec un genre de quête annexe. Une map où l’objectif serait à côté du point de départ, mais il y aurait un grand mur entre les deux. Il faudrait faire tout un détour pour l’atteindre. À la fin, le personnage aurait dit : « j’ai du créer toutes ces boucles de lierre juste pour parcourir une grande boucle qui me ramène à mon point de départ ! WTF ? ». Haha, lolilol potentiel.

D’autres idées en vrac

L’échange entre une case de boue et une case d’eau ne coûterait pas de mana, mais ne ferait que déplacer les lierres de l’eau sur la case de boue. On perdrait les lierres de boue. Ça nettoie la piscine et ça encourage à étendre l’eau le plus possible, pour conquérir des lierres qu’on pourra ensuite placer gratuitement.

Des statistiques :

  • longueur & surface de la plus grande boucle réalisée,
  • nombre de boucle créées en une seule action,
  • maximum de mana atteint,
  • rentabilité (mana gagné / mana dépensé),
  • nombre de case d’eau créées,
  • etc.

Et bien sûr, des achievements et des bonus liés à ces stats.

Des robots

Quand on fait une boucle, ça ne supprime pas immédiatement la boue. Il faut poser des petits robots nettoyeurs qui enlèvent la boue autour d’eux. Ils fonctionnent pendant un temps limité. Donc si on les pose sur une boucle trop grande, ils ne nettoieront pas tout et la boue se repropagera dans la boucle. Si on les pose sur une boucle plus petite, ils nettoient tout, la boue ne se propage pas car elle ne peut pas traverser les lierres. On peut alors déclencher la propagation de l’eau vers la boucle nettoyée.

C’est une idée qui reste à finaliser. Parce qu’actuellement, l’eau peut passer à travers le lierre. Donc si on commence à nettoyer une boucle avec des robots, l’eau peut alors se propager tout de suite dans une boucle dont le nettoyage n’a pas été terminé, et ça peut donner n’importe quoi.

Ou alors on dit que l’eau ne passe pas le lierre. Et on doit enlever manuellement le lierre qui sépare l’eau de la boucle nettoyée. À réfléchir si on a envie.

Je laisse cette idée là où elle est, j’ai peur d’avoir embrouillé tout le monde en la décrivant.

Robot nettoyant la boue (à moins que ce soit l’inverse)

Post-codem concernant Squarity

Voici maintenant mes retours en tant que simple utilisateur/créateur de cette plate-forme de jeu.

Les messages d’erreur, rogntudjuu !

Comme l’a si bien dit 10kbis en commentaire de mon précédent article, il faut les messages d’erreur et les tracebacks !

Quand le code du jeu plante à l’initialisation, le message s’affiche comme il faut dans la console du navigateur. Mais quand ça plante pendant l’exécution d’une callback, on a que d’alle. On ne sait pas où ça a planté, ni pourquoi. Je n’ai pas encore pris le temps de régler ce problème.

Je pensais être capable de coder du python sans visibilité sur les messages d’erreur. Je m’étais dit : « je suis super fort, et au pire, je pourrais toujours débuguer à coups de print ».

Débuguer à l’aveugle, c’est ce que j’ai fait pendant tout le week-end. C’est la raison pour laquelle je n’ai eu une version pre-pre-alpha que très tard. Ça m’a aussi mis de sérieux doutes sur ma capacité à sortir quelque chose de jouable avant la fin.

J’avais prévu, après le Ludum Dare, de me poser un peu concernant le dev, faire un semblant de road-map, nettoyer et documenter un peu mon code. Mais là, nope. Avant de faire tout ça, il faut que je règle cette non-gestion des erreurs.

Bien. Il me reste 12h pour faire tout le reste et j’ai pas encore mangé.

Ajouter les événements de clics de souris

Je m’étais promis que pendant le Ludum Dare, je m’occuperais uniquement du jeu, sans mettre à jour le site Squarity. Je voulais prouver que la présente version est suffisamment aboutie pour créer un jeu, même très simple, même avec beaucoup de galère.

Puis j’ai réalisé que Loops in Pool serait beaucoup plus pratique si on pouvait directement cliquer sur les cases. Alors j’ai décidé que je ferais une entorse à ma promesse et que je rajouterais à la va-vite la gestion des clics dans Squarity.

Finalement, je n’ai pas du tout eu le temps de faire ça. Je suis donc parvenu à être suffisamment à l’arrache pour tenir ma promesse. Youpi !

Ayez l’amabilité de bien vouloir gérer ce mulot, mon brave.

Ça peut être très lent

Exécuter du python dans un navigateur web, c’est lent. Je m’en doutais un peu, mais je ne pensais pas que ça se révélerait dès maintenant.

Lorsqu’on échange deux tiles, il faut attendre une ou deux secondes avant de pouvoir faire autre chose. C’est le temps pour vérifier si l’échange a créé une boucle ou pas. On exécute pour cela un algo pourri de Dijkstra sur 2240 pauvres petits nodes. Ça devrait se faire instantanément, or ce n’est pas le cas.

C’est pour ça que la boue se remplit progressivement au début. Si je calculais toute la propagation dès le départ, ça prendrait vachement de temps et on pourrait croire que le jeu ne marche pas.

Heureusement, quand on fait des jeux simples nécessitant peu de traitement, il n’y a aucun problème, le jeu du magicien et H2O en sont des preuves. Mais il ne faut pas trop abuser.

Je n’ai pas de solution miracle pour ce problème, juste des pistes :

  • Faire des tests de performances, en déduire des manières de coder plus rapide que d’autres et les documenter dans des bonnes pratiques. Je suppose que le python dans un navigateur se code et s’optimise différemment par rapport au python normal.
  • Essayer de faire des traitements parallèles ou asynchrones. Je ne sais même pas si on peut faire ça proprement dans un navigateur web, que ce soit en javascript ou en python.
  • Mesurer en live les performances du code, pour repérer les parties de code les plus ralentisseuses.
  • Optimiser l’affichage. Au lieu de redessiner toutes les tiles à chaque fois, on en marque certaines comme « dirty ». Seules celles-là seront redessinées. Mais, je ne suis pas sûr que ça améliore grandement la vitesse.

D’autre part, j’ai fait une modif qui me semble cool : j’ai redirigé les print. Normalement, ils vont dans la console du navigateur. Moi je les affiche dans la zone de texte en bas à gauche. Figurez-vous que ça aussi, ça ralentit tout. Faites une dizaine de print à chaque appui de bouton : ça devient horrible, même dans un jeu simple.

Je ne sais pas pourquoi. Est-ce que le navigateur doit recalculer tout le DOM à chaque fois qu’on écrit dans un élément <textarea> ? Ce serait embarrassant. En attendant, l’effet de bord est très amusant : le seul moyen actuel de débuguer est de faire des prints, mais les prints ralentissent tout. Bon courage !!!

Je ne parviendrais peut-être pas à régler ce gros défaut de lenteur, ce qui risque de bloquer des créateurtrices potentieleulleux dans la réalisation de leurs jeux.

Ça ne me fera pas abandonner ce projet, ni déroger de mon idée principale : créer et partager des jeux 2D en python dans un navigateur web. C’est ce que je veux faire, et plus que ça, c’est aussi ce que j’ai envie d’avoir. Je veux utiliser mon propre outil pour créer une foisonnance de jeux bizarres/amusants/pulpesque/dérangeants/moches/etc. Bref, des jeux que je veux voir exister.

Un jeu « pulpe-zinesque » créé par Anna Anthropy, dont je vous parlerai à l’occasion

Autres trucs-en-vrac

Il faut des sons et de la musique. C’est prévu, mais les conseils que j’ai pu récolter à l’occasion du Ludum Dare me confirment que c’est très important pour se démarquer. Si j’avais pas un tas de choses déjà prioritaires, je prioriserai les sons.

Les images des objets ne peuvent pas déborder de leur tile. On peut faire sans, mais pour dessiner un trait qui rejoint deux tiles en diagonale, il faudrait pouvoir placer quelques pixels sur les tiles diago-adjacentes. Ça mériterait un petit dessin pour vous expliquer, mais là, pfouf, cet article est déjà bien assez long.

L’image de tileset devrait pouvoir être uploadée depuis le disque en local, dans le navigateur (en local aussi, du coup). Actuellement, il faut publier l’image sur un site d’hébergement, et ce à chaque changement. Même si on ne souhaite pas publier son jeu. C’est relou. En ce qui me concerne, je met l’image dans un repo github. Mais les push d’images dans github ne sont pas instantanément mis à jour dans leur site web.

Tôt ou tard, il me faudra un lieu d’échange et de création de contenus autour de Squarity (articles, documentation, tilesets, jeux, …). Je me permettrais de commencer par un tout petit truc. Mais il me le faut vraiment ce petit truc, car actuellement j’ai rien du tout, à part des comptes sur des rézosociaux « annexes », comme ce blog. Or, ces comptes n’ont pas pour utilité principale de décrire Squarity. Il va falloir que je me lance dans un mini-CMS Django, et/ou une instance Mastodon, ou autre chose de mieux si vous avez une idée.

Rappelons que l’utilité principale de ce blog est de vous faire découvrir des images de femmes rondes. Ceci ne va pas changer de sitôt. À ce sujet je vous présente llindaa23

À la prochaine, je me remet sur mon code et mes gestion d’erreurs.

Vulture repellent doesn’t work !!!

Edit 2015-11-27 : en réalité, les DirtySprites de pygame fonctionnent. La taille de la zone à nettoyer est définie par la taille de l’attribut rect, et non pas par la taille de l’image du sprite. Ce n’est pas le même comportement qu’avec les RenderUpdates, et c’est documenté nul part. D’où ma non-compréhension. Tout est expliqué plus en détail dans une question que j’ai posée sur le site IndexError (http:// indexerror.net/79/pygame-groupe-sprite-layereddirty-renvoie-bons-rects-refresh).

Maintenant, il ne vous reste plus qu’à lire cet article devenu obsolète !

 

Haha, ouais ! « Vulture repellent doesn’t work ». Ça kille, no ? C’est une private joke rien que pour moi.

Durant mes folles années, je jouais au jeu-fusion « Might and Magic 4 & 5 : World of Xeen ». Je l’ai terminé. Je vous en parlerais un jour.

Comme dans tout bon jeu de rôles, on peut discuter avec des gens dans des tavernes. Enfin disons qu’on les écoute déblatérer leurs conneries d’ivrognes, sans rien pouvoir répondre. Le jeu n’est pas non plus un modèle d’interactivité. (Hey ! Depuis quand les ivrognes sont interactifs ?)

hé, Yörgl ! Un ch'ti canon ?

hé, Yörgl ! Un ch’ti canon ?

Bref, à un moment, y’en a un qui beugle « Vulture repellent doesn’t work !! » Ah c’était quelque chose. J’avais la version CD-collector du jeu, avec les voies digitalisées de tous les dialogues. Elles étaient très bien faite, beaucoup d’accents variés et prononcés, des phrasés super bizarres : « Ayt’s alwèïze eu plésure teu see you, Ghôôsslayèrze », « The MejjjaDragon » … On sentait bien qu’ils s’étaient lachés sur les voix, et ça faisait chaud à la partie de mon cerveau « esthète du jeu vidéo ».

dans les RPG, on sauve des princesses

dans les RPG, on sauve des princesses.

Bref-bref, un ivrogne générique me gueule ça à la gueule, et je comprends pas du tout. Ça c’est éclairci un peu plus tard, quand je suis tombé sur des monstres appelés « Vulture Roc ». Pas évident à tuer ces saletés, du moins au niveau où j’étais. Or, dans la même ville que l’ivrogne, résidait un clampin vendant du « vulture repellent », à only 250,000 gold. Et il doesn’t workait, bien entendu.

Mais, euh.. Osef complètement ?

Stupidement, cette phrase m’a marqué. Le cri du cœur de ce personnage, sa sentence complètement insolite, l’intonation de sa voie… J’ai décidé d’en faire mon « internet meme » personnel. Maintenant, à chaque fois que je tombe sur un truc quelconque qui fonctionne pas, ce meme fait dzoing dans ma tête.

might and magic gardian tower schtroumpfs

Donc, acte :

pygame

dirty

sprites

doesn’t

work !!!

Comme vous ne le savez pas, en ce moment je fais joujou avec la libraire pygame, qui permet de faire des jeux rigolos en python. On y trouve une classe, fort adéquatement appelée pygame.sprite.Sprite. Celle-ci contient un attribut « rect », pour les coordonnées, et un attribut « image », pour haha l’image.

On peut mettre ces sprites dans des classes « Group », pour les afficher/rafraîchir en un seul appel de fonction. La classe RenderUpdates est un type de Group particulier, et est trop cool. Comment ça marche ?

  • Tu fous tes sprites dans ton RenderUpdates.
  • Quand il faut les effacer, tu lances RenderUpdates.clear(), en spécifiant l’image de background pour l’effaçage. Hop, à ce moment là, le RenderUpdates, il enregistre les positions et tailles de tes sprites. Où stocke-t-il ça ? J’en sais rien. Mais ça marche tout seul, c’est magique.
  • Tu fais toutes les conneries que tu veux avec tes sprites. (déplacement, changement d’image, sexe avec des écureuils…)
  • Quand faut les réafficher, tu lances RenderUpdates.draw().
  • Arrivée à cette étape, si t’es un bourrin de plâtrier, tu prends toute la grosse image que tu viens de créer (background + sprites) et tu blit le tout à l’écran. Plaf !!
  • Mais ne serait-ce pas plus économique et plus subtil de ne remettre à jour que les zones qui ont changées ? C’est à dire, les zones où se trouvaient les sprites avant, ainsi que les zones où ils se trouvent maintenant ? Oh si alors !
  • Noël !! Noël !! Cet opération est automatisée par le RenderUpdates. Quand tu fais un draw, il te renvoie une liste de rectangles sales (dirtyRects), indiquant les zones à réafficher.
  • Plus qu’à faire un petit display.update(), en filant directos la liste de dirtyRects. (Surtout pas de display.flip() de plâtrier bourrin). Et le tour est jouay !!! Ça marche, c’est simple, c’est optimisé, on est content.

Petit exemple de RenderUpdates

Le script python est à récupérer ici : dirtyno.py

Le fond de l’écran est vert. Il n’y a qu’un sprite : un carré rouge. Au début il est immobile, puis il se déplace vers la droite. C’est moche et saccadé. Et j’ai même l’impression que ça clignote, (ou alors c’est mes yeux). Mais on s’en fout.

Ca nous y fait ceci :

Là vous le voyez pas bouger, car c’est qu’une image. Pour avoir une émulation à peu près fiable du logiciel, Faites de petits mouvement de tête, comme un pigeon.

Comme je suis d’un naturel curieux…

Haha, quelle titre de merde !! J’ai toujours rêvé de dire ça un jour, en restant stoïque et avec un petit sourire en coin. Comme les gars qui se la pètent, mais sans en avoir l’air. Genre « hu-hu-hu je suis un type normal et tout à fait modeste, mais je suis d’un naturel curieux. Oooohhh, aimez-moi, regardez-moi comme je suis un mec bien, je suis un gentleman avec une petite moustache fine, je ne veux pas donner l’impression de me vanter mais je le fais quand même avec mes gros sabots dégueulasse pleins de boue. Je brâme ma vacuité dans vos oreilles. Brâââââhhhâââââ. Mon sperme est à vendre ! Une occasion unique de faire quelque chose de bien de votre vie ! Fabriquez un enfant ayant mon patrimoine génétique et admirez-le !! Pââââmez-vous, pââââmez-vous !! »

Et merde, encore raté.

Bref, moi et ma curiosité, on voulait juste voir le contenu de ces fameux dirtyRects. J’ai mis un print dans mon programme d’exemple, ça me donne ceci :

coucou !!!
mode normal. liste des rect a repeinturer :
[<rect(10, 100, 50, 50)>]
mode normal. liste des rect a repeinturer :
[<rect(10, 100, 50, 50)>]
mode normal. liste des rect a repeinturer :
[<rect(10, 100, 80, 50)>]
mode normal. liste des rect a repeinturer :
[<rect(40, 100, 80, 50)>]
mode normal. liste des rect a repeinturer :
[<rect(70, 100, 80, 50)>]
mode normal. liste des rect a repeinturer :
[<rect(100, 100, 80, 50)>]
mode normal. liste des rect a repeinturer :
[<rect(130, 100, 80, 50)>]

Le coucou du début, faites y pas gaffe. C’est mon init.

A chaque fois, la liste contient un seul dirty rect. Les valeurs numériques indiquent (coord_X, coord_Y, largeur, hauteur). Au début, le sprite ne bouge pas, donc le dirtyrect à rafraîchir lui correspond exactement. C’est normal.

Ensuite, la coordonnée X du dirtyRect augmente de 30 en 30. Normal, c’est la distance de déplacement de mon sprite, à chaque cycle.

On remarque aussi que la largeur du dirty rect passe à 80. Normal aussi. Le sprite a une largeur de 50. Le RenderUpdates a fusionné la position initiale (le sprite à effacer), avec la position finale (le sprite à afficher). 50 + 30 = 80. Toute la zone nécessaire est rafraîchie (toute ? oui). Ça se gère automatiquement, c’est super.

Donc ça marche.

Dirty Sprite. Ce n’est pas sale.

Quand on est un gars bien, avec une moustache fine, on sait qu’un niveau d’optimisation supplémentaire est possible. En effet, tous les sprites n’ont pas toujours besoin d’être rafraîchis à chaque cycle : si ils ne bougent pas et ne changent pas, pas la peine de les redessiner. C’est là qu’interviennent les dirtySprites ! Tadzam !

C’est comme les sprites normaux, sauf qu’un attribut supplémentaire est ajouté : « dirty ».

  • dirty = 0. Le sprite n’est jamais redessiné.
  • dirty = 1. Le sprite est redessiné une seule fois, puis l’attribut est automatiquement fixé à 0.
  • dirty = 2. Le sprite est toujours redessiné.

Pour avoir ce fonctionnement, il faut utiliser le Group « LayeredDirty », au lieu de « RenderUpdates ». OK c’est cool.

Comme je suis d’un naturel essayiste, j’ai repris mon programme tout moche, j’ai remplacé le group et le sprite, et j’ai fixé dirty à 2. Concrètement, quel est l’intérêt ? Aucun. Le sprite sera redessiné à chaque cycle, comme avant. Mais c’est juste pour tester.

Petit exemple de LayeredDirty

Voici le programme modifié : dirtyyes.py
Et à l’écran, ça donne quoi ?

But ! But !! It’s all fucked up !!! THIS IS AN OUTRAGE !!!

Quand au résultat du print, c’est pas mieux :

coucou !!!
mode dirty. liste des rect a repeinturer :
[<rect(0, 0, 400, 300)>]
mode dirty. liste des rect a repeinturer :
[<rect(10, 100, 50, 50)>]
mode dirty. liste des rect a repeinturer :
[<rect(10, 100, 50, 50)>]
mode dirty. liste des rect a repeinturer :
[<rect(40, 100, 50, 50)>]
mode dirty. liste des rect a repeinturer :
[<rect(70, 100, 50, 50)>]
mode dirty. liste des rect a repeinturer :
[<rect(100, 100, 50, 50)>]
mode dirty. liste des rect a repeinturer :
[<rect(130, 100, 50, 50)>]

Le premier dirty rect fait tout l’écran. Ça a l’air normal, car au début il faut tout rafraîchir. En vertu de quelle lubie pygame se décide à gérer ça maintenant, alors qu’il le faisait pas avant ? De toutes façons m’en fout, je fais un flip global à l’init. Pas besoin de leur connerie. Passons.

On retrouve les deux premiers dirtyRects du début, qui ne bougent pas. Jusque là OK. Ensuite, on se déplace de 30 pixels vers la droite. Mais la largeur reste toujours 50 ! What the fuck ?

Je ne vois que deux explications possibles : soit je suis idiot, et j’ai pas compris comment ça fonctionnait. Soit leur truc est buggé.

J’ai cherché de l’aide par-ci-par-là-lulé sur internet. Que d’alle. Et comme j’ai pas le courage de traduire tout mon blabla en anglais pour poser la question sur un forum ou sur leur IRC-mailing-list, je me contente de râler dans mon coin et d’en faire un article. Voilà.

Je terminerais avec bonnet-de-nuit-man, qui n’est pas très très content de toute cette gabegie :

bonnet de nuit man

M’en fout, je vais continuer de coder mes trucs sans utiliser cette connerie de dirtySprite. Bien fait. Et pour finir, une dernière image de notre plumpprincess :

plump princess