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