« Pygame/Guide du débutant » : différence entre les versions

Contenu supprimé Contenu ajouté
m Formatage, ajout de code
Ligne 29 :
* apprenez comment convertir les nombres en chaînes et inversement.
* Venez-en au point où la syntaxe d'utilisation des listes et des dictionnaires est une seconde nature : vous ne devez pas avoir besoin de lire la documentation à chaque fois que vous avez besoin de découper une liste ou de trier une série de clés de dictionnaire.
* Résistez à la tentation d'utiliser une mailing liste, <ttcode>comp.lang.python</ttcode>, irc ou un forum de discussion lorsque vous rencontrerez des problèmes.
* Au lieu de ça, saignez votre interpréteur et jouez avec le problème quelques heures.
* Imprimez le [http://rgruet.free.fr//#QuickRef guide de référence rapide] de Python et gardez-le près de votre ordinateur.
Ligne 41 :
== Règle 3 : comprenez ce qu'est une Surface ==
 
La partie la plus importante de Pygame concerne la manipulation de surfaces. Imaginez-vous qu'une surface n'est qu'un morceau de papier blanc : vous pouvez dessiner des lignes dessus, remplir certaines parties avec de la couleur, et y copier ou en extraire chaque valeur des pixels qui la constituent. Une surface peut être de n'importe quelle taille (dans la limite du raisonnable) et vous pouvez en manipuler autant que vous voulez (toujours dans la limite du raisonnable). Une seule surface est particulière : celle que vous créez avec la fonction <ttcode>pygame.display.set_mode()</ttcode>. Cette ''surface d'affichage'' représente l'écran : ce que vous y faites apparaîtra sur l'écran de l'utilisateur. Vous ne pouvez en avoir qu'une seule à la fois : c'est une limitation de la SDL, pas de Pygame.
 
Donc, comment créer des surfaces ? Comme mentionné ci-dessus, vous créez la surface spéciale ''surface d'affichage'' avec <ttcode>pygame.display.set_mode()</ttcode>. Vous pouvez créer une surface qui contient une image en utilisant <ttcode>image.load()</ttcode> ou du texte avec <ttcode>font.render()</ttcode>. Vous pouvez également créer une surface qui ne contient rien du tout avec <ttcode>Surface()</ttcode>.
 
La plupart des fonctions de manipulation de surface ne sont pas d'une utilité critique. Apprenez seulement <ttcode>blit()</ttcode>, <ttcode>fill()</ttcode>, <ttcode>set_at()</ttcode> et <ttcode>get_at()</ttcode> et tout ira bien.
 
== Règle 4 : utilisez ''surface.convert()'' ==
 
Quand j'ai commencé à lire la documentation de <ttcode>surface.convert()</ttcode>, je pensais ne pas en avoir besoin car j'utilisais exclusivement le format PNG pour ne pas avoir de problème de format d'image ; je n'avais pas besoin de <ttcode>convert()</ttcode>. J'ai réalisé que j'avais vraiment, vraiment tort.
 
Le ''format'' auquel <ttcode>convert()</ttcode> fait référence n'est pas un ''format'' de fichier (comme PNG, JPEG, GIF), c'est ce qui s'appelle l'[[w:Espace colorimétrique|espace colorimétrique]] ([[w:Rouge vert bleu|RGB]]/[[w:Teinte Saturation Valeur|HSV]]/[[w:YUV|YUV]]/...). Cela se réfère à la façon particulière qu'a une surface, d'enregistrer les différentes couleurs dans un pixel spécifique. Si le ''format'' de la surface n'est pas le même que le ''format'' d'affichage, SDL devra convertir à la volée chaque blit, ce qui est très coûteux en temps de calcul. Ne vous souciez pas plus que ça des explications : souvenez-vous seulement que <ttcode>convert()</ttcode> est nécessaire si vous ne voulez pas que votre affichage soit ralenti inutilement.
 
Comment devez-vous utiliser <ttcode>convert()</ttcode> ? Appelez-la après avoir créé une surface avec la fonction <ttcode>image.load()</ttcode>. Au lieu de faire :
 
<source lang="python">
Ligne 65 :
</source>
 
C'est simple, vous avez besoin de ne l'appeler qu'une seule fois par surface, lorsque vous chargez votre image depuis le disque et vous serez enchanté des résultats. J'ai remarqué un gain de performance sur les blits de l'ordre de 6x en utilisant la fonction <ttcode>convert()</ttcode>.
 
Les seules fois où vous ne voudrez pas utiliser la fonction <ttcode>convert()</ttcode>, est lorsque vous avez absolument besoin de garder un contrôle absolu sur le format interne de l'image - comme par exemple lorsque vous écrivez un logiciel de conversion d'image ou s'en approchant et que vous devez vous assurer que le fichier de sortie possède le même espace colorimétrique que le fichier d'entrée. Si vous écrivez un jeu, vous avez besoin de vitesse, donc utilisez la fonction <ttcode>convert()</ttcode>.
 
== Règle 5 : l'animation par ''dirty_rect'' ==
 
La cause principale d'un taux d'images inadéquate dans un programme Pygame résulte d'un malentendu sur la fonction <ttcode>pygame.display.update()</ttcode>. Avec Pygame, le seul tracé sur la ''surface d'affichage'' n'engendre pas son apparition sur l'écran : vous avez besoin d'appeler la fonction <ttcode>pygame.display.update()</ttcode>. Il existe trois manières d'appeler cette fonction :
 
;pygame.display.update()
Ligne 80 :
: Cette dernière actualise uniquement les zones rectangulaires de l'écran que vous avez spécifiées.
 
La plupart des personnes débutantes en programmation graphique utilisent la première : ils mettent à jour la totalité de l'écran à chaque image. Le problème est que c'est inacceptablement lent pour la plupart des personnes. Appeler <ttcode>update()</ttcode> prends 35 millisecondes sur ma machine, ce qui n'est pas énorme, jusqu'à ce que vous réalisiez que 1000 ms / 3 5ms = 28 images par secondes maximum. Et ceci sans la logique de jeu, sans blits, sans entrées, sans intelligence artificielle, sans rien. Je n'ai simplement fait qu'actualiser l'écran et 28 images par secondes est mon taux maximal. Hum...
 
La solution est appelée ''dirty rect animation''. Au lieu d'actualiser l'écran entier à chaque image, seule la partie qui a changé depuis la dernière image est actualisée. J'obtiens ceci en suivant ces rectangles dans une liste, ensuite j'appelle <ttcode>update(the_dirty_rectangles)</ttcode> à la fin de l'image. En détail, pour le déplacement d'un sprite, je :
 
# Blit une partie de l'arrière-plan sur l'emplacement actuel du sprite, ce qui l'efface.
# Ajoute le rectangle de l'emplacement actuel du sprite dans une liste appelée <ttcode>dirty_rects[]</ttcode>.
# Déplace le sprite.
# Dessine le sprite sur son nouvel emplacement.
# Ajoute le nouvel emplacement du sprite sur ma liste de <ttcode>dirty_rects</ttcode>.
# Appelle la fonction <ttcode>display.update(dirty_rects)</ttcode>.
 
La différence de vitesse est stupéfiante. En considérant que [http://www.pygame.org/shredwheat/solarwolf/ Solarwolf] possède des douzaines de sprites en mouvement mis à jour de façon fluide et qu'il lui reste encore assez de temps pour afficher un champ d'étoiles en ''parallax'' en arrière-plan et l'actualiser lui aussi.
Ligne 99 :
== Règle 6 : les surfaces matérielles engendrent plus de problèmes que d'avantages ==
 
Si vous avez étudié les différents drapeaux utilisables dans la fonction <ttcode>pygame.display.set_mode()</ttcode>, vous pouvez vous dire: ''"Aaah, HWSURFACE ! Cool, c'est ce dont j'ai besoin, qui n'utilise pas l'accélération matérielle ? Oooh DOUBLEBUF ! Ca m'a l'air rapide, je pense que je vais l'utiliser aussi !"''. Ce n'est pas votre faute, nous avons été habitués, par les jeux 3D, à croire que l'accélération matérielle est meilleure et que le rendu logiciel est lent.
 
Malheureusement, l'accélération matérielle engendre une longue liste d'inconvénients :
Ligne 121 :
== Règle 8 : les Rects sont vos amis ==
 
L'enveloppe de Pete Shinner (Pygame) peut fournir de beaux effets de transparence et de bonnes vitesse de blit mais je dois admettre que ma partie préférée de Pygame est la modeste classe <ttcode>Rect</ttcode>. Un <ttcode>rect</ttcode> est un simple rectangle, défini par la position de son coin supérieur gauche, sa largeur et sa hauteur. Beaucoup de fonctions de Pygame prennent des <ttcode>rects</ttcode> en arguments ou des styles de <ttcode>rects</ttcode>, ou encore des séquences qui ont les mêmes valeurs qu'un <ttcode>rect</ttcode>. Ainsi, si je veux un rectangle qui définit une zone entre 10, 20 et 40, 50, je peux faire une des choses suivantes :
 
<source lang="python">
Ligne 131 :
</source>
 
Si vous utilisez une des trois premières versions, quelle qu'elle soit, vous aurez accès aux fonctions utilitaires des <ttcode>Rects</ttcode>. Elles incluent les fonctions de déplacement, de diminution et d'agrandissement des <ttcode>rects</ttcode>, de recherche de l'union de deux <ttcode>rects</ttcode>, et d'une variété de fonctions de détection de collision.
 
Par exemple, je suppose que j'aimerais obtenir une liste de tous les sprites qui contiennent le point <ttcode>(x, y)</ttcode>, peut-être que le joueur a cliqué ici, ou peut-être est-ce l'emplacement actuel d'une balle. C'est très simple si chaque sprite possède un attribut <ttcode>rect</ttcode>, je n'ai qu'à faire :
 
<source lang="python">
Ligne 139 :
</source>
 
Les <ttcode>Rects</ttcode> n'ont avec les surfaces ou les fonctions graphiques aucune autre relation que le fait de les utiliser comme arguments. Vous pouvez les utiliser à des endroits qui n'ont rien à voir avec le graphisme mais que vous avez besoin de définir comme des rectangles. À chaque projet, je découvre de nouvelles façons d'utiliser des <ttcode>rects</ttcode>, là où je n'avais jamais pensé en avoir besoin.
 
== Règle 9 : ne vous tracassez pas avec une détection de collision au pixel près ==
Ligne 148 :
# Pour chaque pixel qui se chevauche avec un autre, voir si les pixels correspondant des deux sprites sont opaques. Si oui, il y a collision.
 
Il existe d'autres solutions, en ajoutant des masques de sprites, mais comme vous devez le faire dans Pygame, ce sera probablement trop lent. Pour la plupart des jeux, il sera préférable de tester une collision de ''sous-rect'' : en créant un <ttcode>rect</ttcode> pour chaque sprite qui sera un peu plus petit que l'image actuelle et l'utiliser pour les collisions. Ce sera bien plus rapide et dans la plupart des cas, le joueur ne vous tiendra pas rigueur de l'imprécision.
 
== Règle 10 : gestion du sous-système d'évènements ==
Ligne 154 :
Le système d'évènements de Pygame est quelque peu complexe. Il existe en fait deux manières différentes de savoir ce que fait un périphérique d'entrée (clavier, souris, joystick).
 
Le premier est de contrôler directement l'état du périphérique. Vous réalisez ceci en appelant <ttcode>pygame.mouse.get_pos()</ttcode> ou <ttcode>pygame.key.get_pressed()</ttcode>. Ceci vous donnera l'état du périphérique au moment de l'appel de la fonction.
 
La seconde méthode utilise la file d'évènement de la SDL. Cette file est une liste d'évènements : les évènements sont ajoutés à la suite de la file lorsqu'ils sont détectés et ils sont effacés de la file lorsqu'ils ont été consultés.
 
Il existe des avantages et des inconvénients pour chaque système. Le contrôle d'état (système 1) vous donne la précision : vous savez exactement quelle entrée a été effectuée ; si <ttcode>mouse.get_pressed([0])</ttcode> est vrai, cela signifie que le bouton gauche de la souris est actuellement enfoncé. La file d'évènements, elle, ne fait que rapporter que le bouton de la souris a été enfoncé à un certain moment dans le passé. Si vous vérifiez la file relativement souvent, ça fonctionnera, mais si vous tardez à la consulter, la latence peut s'agrandir. Un autre avantage du système de contrôle d'état est qu'il détecte facilement les ''accords'' de touches, qui sont plusieurs états au même moment. Si vous voulez savoir si les touches {{Touche|T}} et {{Touche|F}} sont pressé en même temps, il suffit de vérifier :
 
<source lang="python">
Ligne 167 :
Toutefois, dans le système de file, chaque pression de touche entre dans la file comme un évènement complètement séparé ; ainsi, vous devez vous rappeler que la touche {{Touche|T}} est enfoncée et n'a pas encore été relâchée lorsque vous contrôlez l'état de la touche {{Touche|F}}. Un peu plus complexe.
 
Le système d'état possède toutefois une grande faiblesse. Il rapporte seulement quel est l'état d'un périphérique au moment où il est appelé. Si l'utilisateur enfonce le bouton de la souris et qu'il le relâche juste avant que l'appel à <ttcode>mouse.get_pressed()</ttcode> soit fait, le bouton de la souris retournera 0. La fonction <ttcode>get_pressed()</ttcode> rate complètement la pression du bouton de la souris. Les deux évènements, <ttcode>MOUSEBUTTONDOWN</ttcode> et <ttcode>MOUSEBUTTONUP</ttcode> seront toutefois toujours dans la file d'évènements, attendant d'être retrouvés et mis en application.
 
La leçon à retenir est : choisissez le système qui convient à vos besoins. Si vous n'avez pas beaucoup de continuité dans votre boucle, c'est-à-dire que vous attendez une entrée, dans une boucle <ttcode>while 1:</ttcode>, utilisez la fonction <ttcode>get_pressed()</ttcode> ou une autre fonction d'état, la latence sera réduite. D'un autre coté, si toutes les touches enfoncées sont cruciales, mais que la latence n'est pas importante, comme par exemple si l'utilisateur est entrain d'écrire quelque chose dans une boite d'édition, utilisez la file d'évènements. Certaines pressions de touches pourront être un peu en retard mais au final, vous les aurez toutes.
 
Un mot à propos de la différence entre les fonctions <ttcode>event.poll()</ttcode> et <ttcode>event.wait()</ttcode> :
* <ttcode>poll()</ttcode> peut sembler meilleure puisqu'elle n'interdit pas votre programme de faire autre chose que d'attendre une entrée.
* <ttcode>wait()</ttcode> suspend la programme jusqu'à ce qu'un évènement soit reçu.
Toutefois, <ttcode>poll()</ttcode> utilisera 100% de la charge du processeur lors de son fonctionnement et il remplira la file d'évènements avec des <ttcode>NOEVENTS</ttcode>. Préférer l'utilisation de la fonction <ttcode>set_blocked()</ttcode> pour sélectionner uniquement les types d'évènements qui vous intéressent, votre file n'en sera que plus gérable.
 
== Règle 11 : ''Couleur Clé'' contre ''Transparence Alpha'' ==
Ligne 180 :
Il y existe de nombreuses confusions autour de ces deux techniques et beaucoup proviennent de la terminologie utilisée.
 
Le blit par ''Couleur Clé'' implique de dire à Pygame que, dans une certaine image, tous les pixels d'une certaine couleur (la ''Couleur Clé'' en question) apparaîtront comme transparents au lieu de s'afficher dans leur vraie couleur. C'est de cette façon que l'on crée un sprite qui n'apparait pas dans un rectangle. Il suffit d'appeler la fonction <ttcode>surface.set_colorkey(color)</ttcode>, où <ttcode>color</ttcode> est un 3-uplets RGB, comme par exemple (0,0,0). Ceci fera que tous les pixels noirs de l'image source apparaîtront comme transparents.
 
La ''Transparence Alpha'' est différente et implique deux gestions différentes. ''Image Alpha'' s'applique à toute l'image et correspond probablement à ce que vous désirez. Connu aussi sous le nom de ''translucidité'', le canal alpha applique à chaque pixel de l'image source une opacité partielle. Par exemple, si vous définissez le canal alpha d'une surface à 192, et que vous le blitez sur un arrière-plan, 3/4 de la couleur de chaque pixel proviendra de l'image source et 1/4 de l'arrière-plan. Le canal alpha se mesure de 255 à 0, où 0 est complètement transparent et 255 est complètement opaque. A noter que la ''Couleur Clé'' et le blit ''Transparence Alpha'' peuvent être combinés : cela produit une image complètement transparente sur certains pixels et semi-transparente sur d'autres.