Maintenant que nous avons vu comment créer un système de particules, il serait intéressant de créer un petit jeu qui utilise ce système. Le programme asteroid est un petit vaisseau dirigeable par les flèches du clavier. Ajoutons à ce vaisseau un système de particule pour que à chaque fois que les thruster du vaisseau sont activés, ceux-ci émettent des particules.
- Un thruster de particules
https://github.com/Thibautomaton/AsteroidsParticles
On commence par créer un objet spaceship. Celui-ci a les mêmes attributs que l’objet mover. Le fait qu’il ait une accélération, une vitesse et un PVector de localisation permettent de lui appliquer des forces. Il a donc aussi une méthode applyForce(). Celle-ci appelé lorsque la pression d’une touche est détectée :
def keyPressed():
global sp
strength = 3
if key==CODED:
if keyCode==UP:
sp.applyForce(PVector(cos(sp.angle)*strength,sin(sp.angle)*strength))
elif keyCode==DOWN:
sp.applyForce(PVector(-cos(sp.angle)*strength,-sin(sp.angle)*strength))
if keyCode==LEFT:
sp.angle -=0.05
elif keyCode==RIGHT:
sp.angle +=0.05
CODED permet d’identifier les clés qui sont des caractères spéciaux comme les fleches du clavier. On applique ensuite une force qui est dans le prolongement de l’inclinaison du vaisseau proportionnelle à la variable strength qui correspond à la force de la propulsion. Dans le cas où DOWN est enclenché, on applique une force à 180 degre par rapport à l’orientation du vaisseau.
Dans spaceship on initialise deux systèmes de particule :
self.ps1 = ParticleSystem(self.location.x -15, self.location.y+5)
self.ps2 = ParticleSystem(self.location.x + 12, self.location.y+5)
Un système de particule a le même fonctionnement et même comportement que précédemment :
class ParticleSystem(object):
def __init__(self, x, y):
self.particles = []
self.location = PVector(x, y)
self.gravity = PVector(0, 0.1)
def addParticle(self, velocity):
self.particles.append(Particle(self.location.x, self.location.y, velocity))
def update_origin(self, x, y):
self.location = PVector(x, y)
def run(self):
for p in self.particles:
p.applyForce(self.gravity)
p.run()
self.particles = [p for p in self.particles if not p.isDead()]
On a une liste des particules elles-mêmes soumises à une force (la gravité) et la méthode run appelle les méthodes update et display de l’objet Particle, la liste des particules encore en activité est mise à jour.
Dans spaceship on retrouve la méthode display() qui utilise translate et rotate puis définit une forme avec beginShape pour dessiner un triangle. On a la méthode checkEdges qui change la localisation du vaisseau pour qu’il soit toujours dans la fenêtre. Dans applyForce on a le fonctionnement classique qui modifie l’accélération en fonction de la force appliquée. On a aussi addParticle des deux systèmes de particules qui est appelé, ainsi on ajoute des particules seulement lorsque la force de thrust est appliquée au vaisseau.
La fonction update() a le même fonctionnement que pour un objet Mover classique. On modifie la localisation et la vitesse en fonction de la vitesse et de l’accélération. Ce qui change est qu’il faut changer l’origine des systèmes de particule pour que l’émission se fasse à la position des thrusters du vaisseau. On doit donc modifier la position qui se fait en fonction de la localisation x et y du vaisseau (le centre du vaisseau) mais aussi l’angle qui donne l’orientation du vaisseau. Ensuite on appelle les deux systèmes de particules pour qu’ils s’update .
- Plus d’interactions avec les particules
https://github.com/Thibautomaton/RepellerParticles
Pour l’instant, notre système de particules est resté le même au fur et à mesure des exercices. Mais peut-être voulons nous plus de personnalisation, des particules ovales, rectangulaires ou protéiformes. Comment modifier notre code pour prendre en compte tous ces cas. De plus, notre système de particule basé sur l’objet Mover et capable de prendre en compte différentes forces dans l’environnement. Pour l’instant nous l’avons soumis simplement à la gravité, mais ajoutons dans l’environnement un objet repoussoir qui ajoute une force répulsive pour les particules.
Pour modifier l’apparence des particules on utilise l’héritage (la base de la programmation orienté objet) et le polymorphisme pour redéfinir l’expression de la méthode display de chaque type de particule. Ainsi on a plusieurs classes de particule, on ajoute rectParticle et wakyParticle qui héritent de Particle (qui a aussi une fonction display qui sera donc appelé si on ne précise pas le type de particule). Dans ParticleSystem la fonction addParticle est quelque peu modifiée. En fonction de la valeur d’un nombre généré aléatoirement on décide du type de particule à instancier. Le reste des fonctions ne change pas.
On ajoute une méthode applyRepeller qui calcule la valeur de force répulsive en fonction de la position de la particule et de l’objet repeller. Ensuite cette force est appliquée à la particule via la méthode applyForce.
- Briser un objet en particules
https://github.com/Thibautomaton/ShatterParticles
L’objectif est de transformer un objet à l’écran en somme de particules pour qu’il se brise sous forme de particules. Notre objet ParticleSystem est donc un petit peu différent. Ici au lieu d’appeler addParticle dans la fonction update , on initialise les particules dans le constructeur de particleSystem. On les positionne en ligne et colonne pour qu’elles soient toutes alignées et ne forme qu’un grand objet. Dans la fonction mousePressed, on trouve :
def mousePressed():
if mouseButton==LEFT and not ps.breaking:
ps.shatter()
elif mouseButton==RIGHT and ps.breaking:
ps.breaking = False
On appelle donc la fonction shatter() lorsque le bouton gauche de la souris est cliqué. Cette fonction met la variable breaking à True et pour chaque particule applique la gravité et une vitesse initiale.
Le click droit de la souris met la variable breaking à False. Cette fois dans update() c’est la fonction returnHome de Particle qui est appelée. Chaque particule garde en mémoire sa position à son initialisation via l’attribut home. La fonction returnHome() calcule une vitesse, pointant vers ce lieu de départ, la localisation de Particle est modifiée en fonction de cette vitesse. C’est cette fonction qui s’execute plutôt que update précedemment. Dans la fonction update de particleSystem, on vérifie que les particules sont à peu prêt arrivées à leur position de départ, puis on force la position pour que celle-ci soit exacte, ceci étant effectué à la toute fin ne compromettant pas l’effet visuel.
Est-il toujours intéressant dans ce cas de détruire les particules lorsque leur durée de vie est atteinte est une question que l’on peut se poser. On pourrait aussi améliorer le programme pour ne briser que les objets sur lesquels on clique.
CONCLUSION :
Ce qu’il est important de retenir de ces exemples c’est que le module initial n’a pas changé et s’appuie toujours sur une classe Particle et que le fonctionnement de ParticleSystem est à peu près le même. Ainsi la programmation orientée objet permet de définir des blocs de construction qui seront réutilisables tout au long de vos programmes. Nous sommes allés plus loin et avons vu la notion de polymorphisme. Je vous invite à vous référer au livre https://natureofcode.com/particles/#inheritance-and-polymorphism pour de plus amples explications. Briser l’objet en particules est l’occasion de s’interroger sur la représentation qu’on se fait d’un objet. En effet s’il est constitué de particules il sera possible de le briser, si chaque particule garde en mémoire sa localisation initiale on peut le reconstituer. C’est un premier problème concret et qui nous laissera songeur lorsque nous verrons les gardiens des sanctuaires dans Zelda Breath of The Wild disparaître en particules individuelles, comme quelque chose qui nous est familier.
