Licence CC BY-NC-ND, Thierry Parmentelat
c’est quoi les boids ?¶
une simulation ludique où on tente de reproduire le comportement d’animaux en groupes - typiquement oiseaux et poissons
intérêts¶
c’est intéressant parce que
c’est une illustration du modèle de programmation “par acteurs” chaque entité dans l’univers adapte son comportement en fonction de ses voisins immédiats
on y découvre arcade, une autre bibliothèque de jeux pour Python et c’est intéressant pour nous car cette librairie se programme exclusivement par spécialisation de classes
introduction¶
dans ce TP nous allons
découvrir (très superficiellement) la librairie
arcadeet l’utiliser pour implémenter un début de simulation de boids
notre sujet¶
dans sa version originale chaque animal décide de sa trajectoire, en fonction uniquement de ses voisins proches, et cela en suivant trois règles:
séparation (typiquement pour éviter les obstacles)
alignement (tout le monde va, en gros, dans la même direction)
cohésion (rester groupés)
pour la partie imposée de ce TP, on va se contenter d’implémenter la règle de séparation
c’est-à-dire ce qui permet aux boids d’éviter de heurter les autres boids, et les obstacles
les élèves rapides et/ou motivés pourront poursuivre jusqu’à réaliser une simulation complète s’ils le souhaitent
en vidéo¶
voici d’abord une vidéo très courte pour vous montrer les différentes étapes du TP
https://
l’enjeu principal ici est d’être exposé à ce style de programmation par spécialisation de classes
aussi ce chemin est uniquement indicatif, et surtout à destination des élèves qui en ressentiront le besoin
si vous vous sentez, vous pouvez très bien vous y prendre complètement autrement,
le contrat est rempli dès que vous avez: des obstacles fixes, et des boids qui évoluent spontanément en évitant les autres objets
contenu¶
le zip contient
le starter code dans
boids-01.pyles figures dans le dossier
media/
pas à pas¶
v01: starter code¶
"""
display a single object, inert, at (100, 100)
"""
import arcade
BACKGROUND = arcade.color.ALMOND
IMAGE = "media/arrow-resized.png"
class Window(arcade.Window):
def __init__(self):
super().__init__(800, 800, "My first boid")
arcade.set_background_color(BACKGROUND)
self.boid = None
def setup(self):
boid = arcade.Sprite(IMAGE)
boid.center_x = 100
boid.center_y = 100
self.boid = boid
def on_draw(self):
arcade.start_render()
self.boid.draw()
def on_update(self, delta_time):
self.boid.update()
window = Window()
window.setup()
arcade.run()installez la librairie
vérifiez que vous avez le fichier
media/arrow-resized.png(il est dans le zip)copiez et lancez le code ci-dessus
vous devez voir un seul boid immobile, dans le coin en bas à gauche
installez la librairie, copiez le code ci-dessus dans par exemple boid.py, et lancez-le
vous devez voir un seul boid dans le coin en bas à gauche, il est immobile
commentaire¶
observez la différence de style de programmation par rapport à pygame
comment aurait-on écrit la même chose en pygame ?
l’implicite¶
dans ce code, on utilise - et ce n’est pas du tout explicite - le fait que
le module
arcadevient avec une mainloop -arcade.run()
qui se charge de faire ‘avancer’ le jeu - par défaut à plusieurs dizaines de Hzcette mainloop va appeler les méthodes
on_draw()eton_update()
sur chaque instance deWindow- c’est là que nous pouvons programmer
la logique de notre “jeu”la classe
Spritepermet d’ajouter des objets qui savent s’afficher
ici on a juste créé l’objet à partir d’un png, puis fixéboid.center_xetboid.center_y
pour les forts: dans le même registre, mais plus subtil:
il a suffi qu’on crée une instance de
Window
pour qu’elle soit prise en compte par cette boucle;
comment c’est possible d’après vous ?
v02: de la place pour plusieurs boids¶
on veut garder exactement le même comportement (un seul boid fixe pour l’instant)
mais on va modifier le code pour pouvoir (plus tard) en créer plusieurs
pour cela, on pourrait penser à utiliser une liste standard Python,
mais je vous invite à utiliser plutôt la classe SpriteList que fournit arcade
debugging¶
en plus de ça, je vous recommande de:
prévoir le cas du debug, par exemple avec une variable globale DEBUG
et dans ce cas afficher un message à chaque fois que le boid est mis à jour
(surtout pour mettre en évidence la mainloop à ce stade)et aussi de regarder la méthode
set_update_rate()
pour ralentir la cadence, ce qui sera sûrement utile à un moment donné pour débuggerpourquoi ne pas aussi imprimer un message pour voir à quel rythme sont faits les
draw()etupdate()
v03: le boid avance¶
maintenant on va faire bouger notre objet, et lui donner un mouvement rectiligne
et pour ça on va cesser d’utiliser directement la classe Sprite,
et on va au contraire la spécialiser pour créer notre propre classe Boid
(c-à-d que la classe Boid hérite de la classe Sprite)
à savoir¶
pour chaque cycle de la mainloop, l’objet
Windowutilisesa méthode
on_update()pour mettre à jour l’état des objetset sa méthode
on_draw()pour les redessiner c’est pour cela que celle-ci commence parstart_render()qui efface tout, et remet simplement le fond d’écranon peut donc faire avancer le boid simplement en redéfinissant quelques méthodes dans le code
par défaut les deux méthodes (
draw()etupdate()) se produisent à la même fréquence (60Hz),
mais avecset_update_rate()on ralentit uniquementupdate()
c’est pourquoion_update(time_delta)reçoit le temps (en ms)
qui s’est écoulé depuis le dernier appel
et aussi, mais moins crucial à ce stade:
on n’en a pas besoin tout de suite, mais dans la classe
Sprite
il y a un attributanglequi sert à dire de combien on veut tourner l’imageet naturellement dans notre cas, on va vouloir que l’objet avance dans la direction de la flêche
(même si ce stade on ne peut pas encore faire tourner le boid)aussi il serait habile de considérer que la vitesse du boid
est un de ses attributs (et non pas une constante)
même si là encore on n’en a pas besoin tout de suitepensez à exprimer les vitesses en pixels par seconde
v04: en circuit fermé¶
on va faire en sorte que le plateau devienne infini en le refermant sur lui-même
un objet sortant à gauche réapparait à droite, et idem dans tous les sens
ça peut valoir le coup de “sortir” la taille du jeu pour en faire des constantes
et pour que ce soit plus facile à tester on va orienter le boid vers le coin en bas à gauche
si vous vous y êtes bien pris pour le faire avancer, c’est juste une question de changer son .angle
v05: du bruit sur le cap¶
faites en sorte que le trajet du boid ne soit plus strictement rectiligne
en ajoutant à chaque pas un léger bruit sur la direction
v06: les touches ← et →¶
faites en sorte de pouvoir contrôler la direction du boid avec les touches du clavier
à savoir¶
pour intercepter les événements clavier, vous pouvez définir dans la classe
Windowles méthodes
def on_key_press(self, key, modifiers):
pass
def on_key_release(self, key, modifiers):
passla librairie expose par exemple la constante
arcade.key.LEFT
attention¶
ceci est beaucoup plus facile à utiliser si le boid continue de tourner pendant tout le temps où la touche est enfoncée - plutôt que de devoir appuyer plein de fois sur la touche
v07: ajouter un obstacle¶
ajoutez un unique obstacle, immobile, par exemple au centre du jeu
vous trouverez une image media/obstacle-resized.png pour le matérialiser
2 options
on pourrait facilement s’en sortir avec juste la classe
Spritemais je vous demande, pour vous exercer à la spécialisation de classes, de créer une classe
Obstacle
v08: créez une grille d’obstacles¶
remplacez l’unique obstacle par une grille d’obstacles
mettez en 10 x 10, ils sont donc espacés de 80px
v09: détecter les voisins¶
faites en sorte d’animer le boid, pour montrer quand il est proche d’un obstacle ou pas
prenez 20 pixels comme rayon de détection
pour ma part j’ai rendu le boid semi-transparent, en jouant sur l’attribut alpha des Sprite
(255 = opaque, 0 = transparent)
discussion¶
ici on a le choix d’utiliser
soit une variable globale (la liste de tous les obstacles)
ou de rajouter dans la classe
Boidune liste d’obstacles
quels sont les avantages et les inconvénients des 2 approches ?
en termes de vitesse de programmation
en termes de réutilisabilité
je vous recommande la deuxième option
v10: la règle de séparation¶
implémentez la règle de séparation
on peut la résumer comme ceci:

c’est-à-dire que quand on s’intéresse au boid b, on va
ignorer tous les objets qui se trouvent plus loin qu’un certain rayon ‘r’
et pour tous ceux qui sont proches, par exemple o, on calcule un vecteur qui va permettre d’écarter l’objet b de o
je n’ai pas trouvé de formule dans la littérature pour calculer , j’ai sorti de mon chapeau la formule suivantede façon à ce que:
les objets sur le cercle ont une influence nulle,
et si o est tout proche de b, il est repoussé à un rayon r/2
on fait la somme de tous ces vecteurs, et on l’ajoute au déplacement de b
i.e. dans l’exemple de la figure, lorsqu’on traite le boid en b:
on ignore les boids aux endroits en rouge
et on fait la somme des 3 vecteurs (en vert)
qu’on ajoute naturellement au déplacement dû à la vitesse du boid
à noter¶
dans cette version, en cas de collision, on se contente de modifier la position du boid, et pas son orientation; on arrangera ca plus tard
v11: on essaie d’orienter correctement¶
dans cette version, on va essayer de répercuter les collisions sur l’orientation du boid
et pour ça on va mettre à jour brutalement l’attribut angle uniquement sur le mouvement qui est fait à l’instant t
i.e, pour être clair, que si on passe de (x1, y1) à (x2, y2), on oriente le boid pour s’aligner sur le vecteur (x2-x1, y2-y1)
l’effet n’est pas forcément très réussi, vous devez voir l’orientation sauter brutalement d’un angle un autre
à savoir
voyez la fonction math.atan2 qui est pratique ici
v12: on lisse les changements d’orientation¶
comment pourrait-on obtenir quelque chose d’un peu plus élégant, en ce qui concerne les changements de direction ?
v13: les touches ↑ et ↓¶
faire en sorte qu’on puisse contrôler aussi la vitesse avec les touches
essayez le comportement de l’évitement d’obstacles à plusieurs vitesses
v14: plusieurs boids¶
remplacez l’unique boid par un ensemble de 20 boids
sans changer la séparation, qui est calculée seulement à partir des obstacles
v15: éviter les obstacles et les autres boids¶
assurez-vous que la séparation permet d’éviter tous les objets (obstacles et boids)
en option...¶
vous pouvez ensuite améliorer dans les directions suivantes :
implémenter les deux autres règles (alignement et cohésion)
faire en sorte qu’on puisse interactivement ajouter/enlever des obstacles
faire des raccourcis clavier pour activer/désactiver les 3 règles, et le bruit
bien tenir compte de la vitesse de rafraichissement du jeu
i.e., est-ce que la simulation continue de fonctionner si on ralentit la logique du jeu avecWindow.set_update_rate()etc...
pour information voici une synthèse des 3 règles, à vous de chercher pour préciser comment interpréter ces figures

pour aller plus loin¶
pour plus de détails sur les boids, voir la page de Craig Reynolds, l’auteur original, et la page wikipedia
je vous signale un tutoriel intéressant sur la librairie arcade:
https://realpython .com /arcade -python -game -framework/ enfin d’autres vidéos qui peuvent vous inspirer également
https://www .youtube .com /watch ?v = QbUPfMXXQIY
https://www .youtube .com /watch ?v = bqtqltqcQhw