La programmation orientée objet est l’une des bases de programmation moderne et se retrouve dans un grand nombre de langages de programmation. Elle permet de créer des « objets » mais aussi de nous simplifier grandement la vie.
Prenons un sketch simple de 200*200 dans lequel nous souhaitons dessiner une balle se déplaçant à une vitesse aléatoire et rebondissant sur les bords de notre scène. Nous écrirons :
//Variables de notre balles float x; //position x de la balle float y; //position y de la balle float vx; //vitesse x de la balle float vy; //vitesse y de la balle float t; // taille de la balle void setup() { size(200, 200, JAVA2D); smooth(); //initialisation des variables. t = random(5, 10); x = random(t, width-t); y = random(t, height-t); vx = random(-3, 3); vy = random(-3, 3); } void draw() { background(255); //mise à jour des variables x += vx; y += vy; //detection des murs if(x <= t || x >= width-t) { vx = -vx; } if(y <= t || y >= height-t) { vy = -vy; } ellipse(x, y, t*2, t*2); }
Mais que ce passe-t-il si nous voulons non plus une balle mais 10, 40 ou 100?
La première solution consisterait à créer autant de variables que nous avons de balles mais cela n’est pas envisageable. Notre code dépassera de loin le nombre de page de la constitution américaine et nous serons vite perdu.
La seconde solution serait d’utiliser des tableaux pour stocker chacune de nos variables mais là aussi nous allons avoir de nombreux tableaux et cela n’est toujours pas envisageable.
C’est là que la programmation orientée objet entre en jeux. Elle va nous permettre de créer aisément nos 10, 40 ou 100 balles en un minimum de temps.
Qu’est ce qu’un objet?
Un objet est directement inspiré du monde réel. En effet nous sommes entouré d’objets et chacun de ces objets à des propriétés propres. Par exemple nous avons les objets « téléphones ». Chacun de ses objets « téléphones » sont définis par le fait qu’ils ont tous des variables communes, à savoir le fait de pouvoir passer des communications vocales par le biais d’un réseau téléphonique. Mais ils possèdent aussi des variables qui leur sont propre comme le fait de pouvoir envoyer des sms ou non, d’être tactile ou pas, d’avoir des poids et des tailles qui diffèrent… Bref nos téléphones sont des objets appartenant à une même classe, la classe « Téléphone ».
Revenons maintenant à notre skecth, nous avons imaginé avoir un espace de 200*200 dans lequel des balles de tailles différentes se déplacent à des vitesses différentes. Nous avons donc des « objets » qui ont un comportement commun, à savoir le fait d’être des balles et de se déplacer, mais aussi différents puisque leur vitesses diffèrent. Nous avons l’exemple parfait pour créer une « classe » d’objets.
Notre première classe.
Précédemment nous avons parlé de « classe » sans même l’expliquer. Une classe est une matrice, un patron, un plan de construction… d’un objet. La classe d’un objet permet de créer et d’instancier les variables communes à tous nos objet, de développer leur comportements et de créer la méthode de construction de notre objet. D’un point de vu développement, elle est assez proche de la façon dont nous construisons un sketch.
Lorsque nous créons nos sketchs nous avons l’habitude de créer des variables, puis une méthode setup() qui nous permettra de créer notre sketch (taille, moteur de rendu…) et enfin une méthode draw() (notre boucle). Lorsque nous créons une classe nous obtenons à peu de choses près la même syntaxe :
class maClasse { //Ici mes variables //Mon constructeur, l'équivalent de mon setup() pour mon sketch Balle() { } //Mes méthode void display() { } }
Entrons maintenant dans le vif du sujet en créant notre classe « Balle ». Pour cela nous allons reprendre l’ensemble de nos variables, nous les initialiserons dans notre constructeur et enfin nous dessinerons notre balle dans une méthode display(). Cela nous donne donc :
class Balle { //variables globales float x; //position x de la balle float y; //position y de la balle float vx; //vitesse x de la balle float vy; //vitesse y de la balle float t; // taille de la balle //Mon constructeur, celui doit toujours avoir le même nom que la class afin d'être reconnu comme un constructeur. Balle() { //initialisation des variables. t = random(5, 10); x = random(t, width-t); y = random(t, height-t); vx = random(-3, 3); vy = random(-3, 3); } void display() { //mise à jour des variables x += vx; y += vy; //detection des murs if (x <= t || x >= width-t) { vx = -vx; } if (y <= t || y >= height-t) { vy = -vy; } fill(255); stroke(0); ellipse(x, y, t*2, t*2); } }
Nous venons d’écrire notre classe mais comme nous avons vu, il s’agit ici de la matrice qui nous permettra de créer nos objets.
Il nous faut maintenant créer nos objets et les instancier pour ensuite les afficher sur la scène. Dans notre sketch, nous allons créer un nouvel objet « maBalle » faisant partie de la classe « Balle ».
Balle maBalle;
Dans notre setup() nous instancierons notre objet.
maBalle = new Balle();
Enfin dans notre draw, nous appellerons la méthode display() de notre balle
maBalle.display();
Cela nous donne donc
Balle maBalle; void setup() { size(400, 200, JAVA2D); smooth(); maBalle = new Balle(); } void draw() { background(255); maBalle.display(); } class Balle { //variables globales float x; //position x de la balle float y; //position y de la balle float vx; //vitesse x de la balle float vy; //vitesse y de la balle float t; // taille de la balle //Mon constructeur, celui doit toujours avoir le même nom que la class afin d'être reconnu comme un constructeur. Balle() { //initialisation des variables. t = random(5, 10); x = random(t, width-t); y = random(t, height-t); vx = random(-3, 3); vy = random(-3, 3); } void display() { //mise à jour des variables x += vx; y += vy; //detection des murs if (x <= t || x >= width-t) { vx = -vx; } if (y <= t || y >= height-t) { vy = -vy; } fill(255); stroke(0); ellipse(x, y, t*2, t*2); } }
On remarque que pour appeler une méthode de notre classe nous procédons de la manière suivante objet.methode(). Il en va de même pour accéder aux variables. Ainsi println(maBalle.x) me renverra la valeur x de ma balle. On peut par la suite aisément modifier ces valeurs.
On avait pas parlé de plusieurs balles?
Nous venons de créer notre première classe mais nous obtenons 1 balle et non 100 comme promis précédemment. Pour cela rien de plus simple, et c’est là le grand pouvoir des classes, il nous suffit de créer un tableau d’objets et non plus un seul objet.
Balle[] maBalle; int nbBalle; void setup() { size(200, 200, JAVA2D); smooth(); nbBalle = 100; maBalle = new Balle[nbBalle]; for(int i = 0; i < maBalle.length; i++) { maBalle[i] = new Balle(); } } void draw() { background(255); for(int i = 0; i < maBalle.length; i++) { maBalle[i].display(); } }
Allons plus loin dans la possibilité qu’offrent les classes.
Nous savons maintenant comment créer une classe et instancier des objets mais allons plus loin dans les possibilités que nous offre la programmation orientée objet. Nous venons de créer 100 objets mais que ce passe-t-il dans le cas où nous voulions en créer à chaque clic?
Il faudrait que nous rajoutions des éléments à notre tableau.
Imaginons un sketch simple, de 400*200 dans lequel nous pourrions rajouter une balle à chaque clic dont la position de départ sera définie par la position de la souris.
Pour cela nous allons toujours utiliser un tableau mais d’un autre type puisque nous utiliserons un ArrayList(). À l’inverse d’un tableau classique maBalle[] qui possède une taille fixe, les ArrayList sont des tableaux dont la taille n’est pas définie et peut être en constante expansion. Cependant sa déclaration diffère d’un tableau classique. Ainsi pour passer notre classe balle en ArrayList pour devrons changer notre code comme il suit :
ArrayList<Balle> maBalle; int nbBalle; void setup() { size(200, 200, JAVA2D); smooth(); nbBalle = 100; maBalle = new ArrayList<Balle>(); }
De même la methode nous permettant d’ajouter des balles sur notre scène diffère d’un tableau classique
for (int i=0; i<nbBalle; i++) { maBalle.add(new Balle()); }
Afin la dernière différence avec le tableau classique est la façon nous nous parcourons notre ArrayList afin d’appeler la méthode display() de notre classe.
for(int i = 0; i < maBalle.size(); i++) { Balle b = maBalle.get(i); b.display(); }
Maintenant que nous avons vu comment créer et parcourir un ArrayList, il ne nous reste plus qu’à modifier notre code précédent afin de créer nos balles à chaque clic de la souris
ArrayList<Balle> maBalle; int nbBalle; void setup() { size(400, 200, JAVA2D); smooth(); nbBalle = 10; maBalle = new ArrayList<Balle>(); for (int i=0; i<nbBalle; i++) { maBalle.add(new Balle())); } } void draw() { background(255); if(mousePressed) { maBalle.add(new Balle()); } for(int i = 0; i < maBalle.size(); i++) { Balle b = maBalle.get(i); b.display(); } }
Le résultat est là mais cependant il nous manque une règle à respecter. Nous voulions que nos balles apparaissent à la position de la souris, hors pour le moment elle apparaissent à des positions aléatoire. En effet lorsque l’on retourne dans le constructeur de la classe nous remarquons que notre x et y sont définies de manière aléatoire.
Balle() { //initialisation des variables. t = random(5, 10); x = random(t, width-t); y = random(t, height-t); vx = random(-3, 3); vy = random(-3, 3); }
Nous pourrions changer cela par un mouseX mouseY mais en faisant cela nos 10 première balles présentes sur scène auront le même point d’origine. Nous allons donc légèrement changer notre constructeur en lui indiquant qu’à son appel il devra recevoir des variables. Nous pourrons alors par la suite définir aisément la position de chacune de nos balles à la création.
Balle(float x_, float y_) { //initialisation des variables. t = random(5, 10); x = x_; y = y_; vx = random(-3, 3); vy = random(-3, 3); }
Il nous faudra alors changer la façon dont nous instancions nos balles comme il suit
maBalle.add(new Balle(position x, position y));
Nous avons maintenant un tableau dynamique (ArrayList) et nous créons autant de balles que nous voulons.
Ajoutons maintenant une dernière possibilité, à savoir fixer une limite à notre tableau dynamique. En effet cela nous permettra d’économiser notre ordinateur qui risquerait, au bout d’un millions de particules, de ralentir.
Pour cela nous rajoutons une condition permettant de supprimer la première balle (la plus vieille) créée lorsque nous atteignons un maximum de 30 balles
if(maBalle.size() >= 30) { maBalle.remove(0); }
Nous voila donc avec notre première classe de particules.
ArrayList<Balle> maBalle; int nbBalle; void setup() { size(400, 200, JAVA2D); smooth(); nbBalle = 10; maBalle = new ArrayList<Balle>(); for (int i=0; i<nbBalle; i++) { maBalle.add(new Balle(random(width), random(height))); } } void draw() { background(255); if(mousePressed) { maBalle.add(new Balle(mouseX, mouseY)); } if(maBalle.size() >= 30) { maBalle.remove(0); } for(int i = 0; i < maBalle.size(); i++) { Balle b = maBalle.get(i); b.display(); } } class Balle { //variables globales float x; //position x de la balle float y; //position y de la balle float vx; //vitesse x de la balle float vy; //vitesse y de la balle float t; // taille de la balle //Mon constructeur, celui doit toujours avoir le même nom que la class afin d'être reconnu comme un constructeur. Balle(float x_, float y_) { //initialisation des variables. t = random(5, 10); x = x_; y = y_; vx = random(-3, 3); vy = random(-3, 3); } void display() { //mise à jour des variables x += vx; y += vy; //detection des murs if (x <= 0 || x >= width) { vx = -vx; } if (y <= 0 || y >= height) { vy = -vy; } fill(255); stroke(0); ellipse(x, y, t*2, t*2); } }
Nous venons de voir comment créer et utiliser des objets pour créer un premier générateur de particules. Cependant nous pouvons aller beaucoup plus loin avec les classes en créant des objets réagissant entre eux et ayant leur propres comportement.
Imaginons un sketch dans lequel se déplaceraient des atomes. Chaque atome est défini par une masse aléatoire qui influencera sa vitesse, plus sa masse sera grande plus il sera lent. Chacun d’entre eux possédera un nombre aléatoire d’électrons gravitant autour d’eux. Ces atomes se déplaceront sur la scène et rebondiront sur les « murs » de celle-ci. Leurs électrons tourneront autour en se rapprochant de l’atome puis en s’en éloignant jusqu’à une certaines limite définie par la masse de l’atome.
Lorsque deux atomes entrent en collision ceux-ci créaient alors un nouvel atome au design différent dont la masse sera l’addition de la masse des deux atomes d’origine. De même le nombre d’électrons gravitant autour de ce nouvel atome sera défini par l’addition du nombre d’électrons gravitant autour des deux atomes d’origine.
Enfin lorsque deux nouveaux atomes entrent en collision, ils se divisent pour recréer les deux atomes d’origines qui les composaient.
Un skecth comme celui ci est l’exemple parfait pour réaliser des classes. La première sera notre classe Atoms qui aura ses caractéristique propre :
• Position
• Masse
• Vitesse
• Nombre d’électrons
La seconde sera notre classe NewAtoms qui aura les caractéristiques suivante :
• Position (Position de la collision des deux atomes d’origine)
• Masse (Addition des deux masses)
• Vitesse
• Nombre d’électrons (Addition des électrons)
Nous obtenons donc les classe suivantes :
class Atom { //variables de l'atome float x, y; //position de l'atome float masse; //masse de l'atome, ici son diamètre float velocite, vx, vy; //velocité des atome, celles-ci seront dépandantent de la masse des atomes color cF, cS; //couleur de l'atome //variable des electrons int nbElectrons; float[] posX; //position des electrons float[] posY; //position des electrons float[] r; //rayon des electrons sur le cercle trigonométrique float[] angle; //angle des electrons sur le cercle trigonométrique float[] vr; //vitesse des electrons float[] vAngle; //vitesse de l'angle des electrons float[] origin; //rayon d'origine qui établir notre limite haute de mouvement //constructeur Atom(float _x, float _y, float _masse, int _nbElec) { this.x = _x; this.y = _y; this.masse = _masse; this.cF = color(random(160, 180), random(70, 100), random(2, 10), random(70, 100)); this.cS = color(random(160, 180), random(70, 100), random(10, 20), random(70, 100)); //calcul des velocités this.vx = this.x; this.vy = this.y; this.velocite = map(this.masse, 5, 30, 5, 1); this.vx = this.velocite*random(-1, 1); this.vy = this.velocite*random(-1, 1); //electrons createElectron(_nbElec); } //-------- //methodes //-------- //constructeur des Electrons void createElectron(int _nbElectrons) { nbElectrons = _nbElectrons; posX = new float[nbElectrons]; posY = new float[nbElectrons]; r = new float[nbElectrons]; angle = new float[nbElectrons]; vr = new float[nbElectrons]; vAngle = new float[nbElectrons]; origin = new float[nbElectrons]; for (int i=1; i<nbElectrons; i++) { angle[i] = ((2*PI)/nbElectrons)*i; //Ici nous divisons le cercle 2PI par le nombre de particule multiplier par i pour obtenir l'angle de chaque particule et les placer à équidistance les une des autres vr[i] = random(-0.5, -0.1); vAngle[i] =radians(1);// radians(random(-1, 1)); r[i] = random(this.masse/2, this.masse); origin[i] = r[i]; posX[i] = this.x+cos(angle[i])*r[i]; //position x sur le cercle x = cos(angle)*rayon posY[i] = this.y+sin(angle[i])*r[i]; //position y sur le cercle y = cos(angle)*rayon } } //deplacement void motion() { this.x += this.vx; this.y += this.vy; } void checkEdge() { if (this.x<0) { this.x = 1; this.vx *=-1; } else if(this.x>width) { this.x = width-1; this.vx *=-1; } if (this.y<0) { this.y = 1; this.vy *=-1; } else if(this.y>height) { this.y = height-1; this.vy *=-1; } } //dessin et mouvement des electrons void electrons() { for (int i=1; i<nbElectrons; i++) { r[i] += vr[i]; // reduction du rayon angle[i] += vAngle[i]; if (r[i]>origin[i]+1 || r[i]<this.masse/2-1) { vr[i] *=-1; } //position des partciules posX[i] = this.x+cos(angle[i])*r[i]; posY[i] = this.y+sin(angle[i])*r[i]; stroke(0, 0, 100, 30); noFill(); //dessin de la fleur ellipse(posX[i], posY[i], 3, 3); line(posX[i], posY[i], this.x, this.y); if (i != nbElectrons-1) { line(posX[i], posY[i], posX[i+1], posY[i+1]); } else { line(posX[i], posY[i], posX[1], posY[1]); } } } //affichages void display() { //dessin l'atome //noStroke(); electrons(); fill(0, 0, 100, 5);//this.cF); stroke(0, 0, 100, 20);//this.cS); ellipse(this.x, this.y, this.masse/2, this.masse/2); point(this.x, this.y); } }
class newAtom { //variable du nouvel atome float x, y; //position du nouvel atome float masse; //masse du nouvel atome float velocite, vx, vy; //velocité des atome, celles-ci seront dépandantent de la masse des atomes //valeur d'origine de l'atome float masse1, masse2; int nbElec1, nbElec2; //variable des electrons int nbElectrons; float[] posX; //position des electrons float[] posY; //position des electrons float[] r; //rayon des electrons sur le cercle trigonométrique float[] angle; //angle des electrons sur le cercle trigonométrique float[] vr; //vitesse des electrons float[] vAngle; //vitesse de l'angle des electrons float[] origin; //rayon d'origine qui établir notre limite haute de mouvement //constructeur newAtom(float _x, float _y, float _masse, int _nbElec, float _masse1, float _masse2, int _nbElec1, int _nbElec2) { this.x = _x; this.y = _y; this.masse = _masse; //origine de l'atome this.masse1 = _masse1; this.masse2 = _masse2; this.nbElec1 = _nbElec1; this.nbElec2 = _nbElec2; //vitesse this.velocite = map(this.masse, 10, 60, 3, 1); this.vx = this.velocite*random(-1, 1); this.vy = this.velocite*random(-1, 1); //electrons createElectron(_nbElec); } //-------- //methodes //-------- //constructeur des Electrons void createElectron(int _nbElectrons) { nbElectrons = _nbElectrons; posX = new float[nbElectrons]; posY = new float[nbElectrons]; r = new float[nbElectrons]; angle = new float[nbElectrons]; vr = new float[nbElectrons]; vAngle = new float[nbElectrons]; origin = new float[nbElectrons]; for (int i=1; i<nbElectrons; i++) { angle[i] = ((2*PI)/nbElectrons)*i; //Ici nous divisons le cercle 2PI par le nombre de particule multiplier par i pour obtenir l'angle de chaque particule et les placer à équidistance les une des autres vr[i] = random(-0.5, -0.1); vAngle[i] =radians(1);// radians(random(-1, 1)); r[i] = random(this.masse/2, this.masse); origin[i] = r[i]; posX[i] = this.x+cos(angle[i])*r[i]; //position x sur le cercle x = cos(angle)*rayon posY[i] = this.y+sin(angle[i])*r[i]; //position y sur le cercle y = cos(angle)*rayon } } //deplacement void motion() { this.x += this.vx; this.y += this.vy; } void checkEdge() { if (this.x<0) { this.x = 1; this.vx *=-1; } else if(this.x>width) { this.x = width-1; this.vx *=-1; } if (this.y<0) { this.y = 1; this.vy *=-1; } else if(this.y>height) { this.y = height-1; this.vy *=-1; } } //dessin et mouvement des electrons void electrons() { for (int i=1; i<nbElectrons; i++) { r[i] += vr[i]; // reduction du rayon angle[i] += vAngle[i]; if (r[i]>origin[i]+1 || r[i]<this.masse/2-1) { vr[i] *=-1; } stroke(0, 0, 100, 15); noFill(); posX[i] = this.x+cos(angle[i])*r[i]; posY[i] = this.y+sin(angle[i])*r[i]; ellipse(posX[i], posY[i], 4, 4); posX[i] = this.x+cos(angle[i])*(r[i]-4); posY[i] = this.y+sin(angle[i])*(r[i]-4); ellipse(posX[i], posY[i], 2, 2); posX[i] = this.x+cos(angle[i])*(r[i]+10); posY[i] = this.y+sin(angle[i])*(r[i]+10); line(posX[i], posY[i], this.x, this.y); } } void display() { electrons(); fill(0, 0, 100, 10); } }
Il nous faut ensuite créer nos objets, les instancier et calculer leur collisions. Pour ce dernier point nous utiliserons la méthode dist() nous permettant de calculer la distance entre nos objets. Lorsque cette distance sera égale ou inférieure à 0 nous définirons une collision, créerons nos nouveaux atomes et supprimerons les atomes d’origine. Enfin nous calculerons les collisions entre les nouveaux atomes pour faire l’inverse en les supprimant et en recréant nos atomes d’origine
/*"Fusion atome" est un skecth permettant de découvrir la programation orientée objet dans processing Dans ce skecth nous verrons comment créer un classe et gérer des comportements Nous allons créer un système de particule se deplacement à des vitesses et des sens différents et aléatoires. Lorsque deux particules se rencontre, une nouvelle se créer. Cette nouvelle particules est le resultat de la fusion des deux premières. Lorsque deux particules fusionnées se rencontre elles redeviennent des particules simples.*/ //--------------------------------------------------------------------------------------------------------// ArrayList<Atom> myAtom; //declaration de mon tableau dynamique, ici nous utilisons un tableau dynamique de sorte à gérer sa taille de manière dynamique. int nbAtomBegin; //nombre d'atome au départ du sketch ArrayList<newAtom> myNewAtom; //déclarationd d'un tableau dynamique de nouveau atoms void setup() { size(displayWidth/2, displayHeight/2, OPENGL); smooth(8); colorMode(HSB, 360, 100, 100, 100); nbAtomBegin = 10; // nombre d'atome au départ myAtom = new ArrayList<Atom>(); //création de mon tableau d'atome for (int i=0; i<nbAtomBegin; i++) { myAtom.add(new Atom(random(width), random(height), random(5, 40), int(random(5, 11)))); } myNewAtom = new ArrayList<newAtom>(); //création de mon tableau de nouveaux Atomes background(0); } void draw() { fill(200, 100, 15, 50); noStroke(); rect(0, 0, width, height); //rotate(radians(50), 1, 0, 0); for (int i=0; i<myAtom.size(); i++) { Atom pi = myAtom.get(i); for (int j=i+1; j<myAtom.size(); j++) { Atom pj = myAtom.get(j); float distanceBrute = dist(pi.x, pi.y, pj.x, pj.y); float distance = distanceBrute-(pi.masse/2+pj.masse/2); //float alphaP = map(d, 0, 50, 100, 0); if (distance<=0) { myNewAtom.add(new newAtom(pi.x, pi.y, pi.masse+pj.masse, pi.nbElectrons+pj.nbElectrons, pi.masse, pj.masse, pi.nbElectrons, pj.nbElectrons)); myAtom.remove(pi); myAtom.remove(pj); } } } for (int i=0; i<myNewAtom.size(); i++) { newAtom pi = myNewAtom.get(i); for (int j=i+1; j<myNewAtom.size(); j++) { newAtom pj = myNewAtom.get(j); float distanceBrute = dist(pi.x, pi.y, pj.x, pj.y); float distance = distanceBrute-(pi.masse/2+pj.masse/2); if (distance<=0) { myAtom.add(new Atom(pi.x-10, pi.y-10, pi.masse1, pi.nbElec1)); myAtom.add(new Atom(pi.x+10, pi.y+10, pi.masse2, pi.nbElec2)); myAtom.add(new Atom(pj.x-10, pj.y-10, pj.masse1, pj.nbElec1)); myAtom.add(new Atom(pj.x+10, pj.y+10, pj.masse2, pj.nbElec2)); myNewAtom.remove(pi); myNewAtom.remove(pj); } } } //affichage des atomes for (Atom a: myAtom) { a.display(); a.motion(); a.checkEdge(); } for (newAtom na: myNewAtom) { na.display(); na.motion(); na.checkEdge(); } } void mouseReleased() { myAtom.add(new Atom(mouseX, mouseY, random(5, 30), int(random(5, 11)))); }
Une réflexion sur « Programmation orientée objet & Générateur de Particules »
Les commentaires sont fermés.