-
Notifications
You must be signed in to change notification settings - Fork 2
cours16
Et oui ! Après tout cela nous n'avons pas encore répondu à la question que nous nous avons posée au début du cours précédent : comment faire un programme qui dessine 100 objets voiture.
L'une des caractéristiques les plus intéressantes lorsqu'on combine l'approche orienté objet et les tableaux est la simplicité avec laquelle on fait passer un programme de 10 objets à 100 ou même à 10000. En fait, si nous avons été soigneux, nous n'aurons pas à changer la classe Voiture
. Une classe ne se soucie pas du nombre d'objets créés à partir d'elle. Donc en considérant que nous conservons le code de la classe Voiture
tel quel, regardons comment nous pourrions étendre notre programme principal pour utiliser plus de voitures. Nous allons nous baser sur l'exemple 13.3. Utilisez le code suivant dans l'onglet principal, :
Voiture maVoiture;
void setup() {
size(400, 400);
maVoiture = new Voiture(100, height / 2, 1, color(255, 0, 0), 40);
}
void draw() {
background(0);
maVoiture.dessiner();
maVoiture.bouger();
}
Et la classe Voiture
dans un onglet approprié :
class Voiture {
// attributs
float x, y;
float vitesse;
color couleur;
float taille;
// constructeurs
Voiture() {
x = 0;
y = height / 2;
vitesse = 1;
couleur = color(255, 0, 0);
taille = 50;
}
Voiture(float x, float y, float vitesse, color couleur, float taille) {
this.x = x;
this.y = y;
this.vitesse = vitesse;
this.couleur = couleur;
this.taille = taille;
}
// méthodes
void dessiner() {
rectMode(CENTER);
fill(couleur);
rect(x, y, taille, taille / 2);
}
void bouger() {
x = (x + vitesse) % width;
}
}
Nous allons suivre trois étapes pour modifier le code ci-dessus afin d'utiliser les tableaux :
-
Déclarer la voiture :
Voiture maVoiture;
-
Initialiser la voiture :
maVoiture = new Voiture(0, height/2, 1, color(255, 0, 0), 30);
-
Activer la voiture en appelant des méthodes :
maVoiture.dessiner(); maVoiture.bouger();
-
Déclarer les voitures :
Voiture[] voitures = new Voiture[100];
-
Initialiser chaque voiture du tableau :
for (int i = 0; i < voitures.length; i++) { voitures[i] = new Voiture(0, i * 4, random(1, 5), color(i * 2), 8); }
-
Activer chaque voiture du tableau en appelant ses méthodes :
for (int i = 0; i < voitures.length; i++) { voitures[i].dessiner(); voitures[i].bouger(); }
Et voici l'exemple complet.
Exemple 16.1. 100 voitures.
Voiture[] voitures = new Voiture[100];
void setup() {
size(400, 400);
for (int i = 0; i < voitures.length; i++) {
voitures[i] = new Voiture(0, i * 4, random(1, 5), color(i * 2), 8);
}
}
void draw() {
background(255);
for (int i = 0; i < voitures.length; i++) {
voitures[i].dessiner();
voitures[i].bouger();
}
}
Exercice 16.1. Changez l'exemple précédent pour qu'il dessine et fasse bouger 200 voitures. Combien de changements avez-vous fait ?
Quand nous avons appris à utiliser les variables (cours 4) et les conditionnelles (cours 6 et 7), nous avons programmé un mécanisme simple de rollover (exercice 7.4). Un rectangle apparaît dans une fenêtre et change de couleur quand la souris passe dessus. Le code suivant réutilise ce fonctionnement mais le transforme en objet Bande
. Bien qu'il y ait 10 objets Bande
, chacun répond individuellement à la souris avec sa propre méthode rollover()
:
void rollover(float mx, float my) {
mouse = (mx > x && mx < x + w);
}
Cette méthode regarde si un point (mx
,my
) est contenu dans une bande verticale. Une variable booléenne mouse
est donc réglée à vrai si le point est contenu dans la bande. Quand on crée un objet qui possède un comportement binaire, on utilise couramment des variables booléennes. Par exemple, une voiture peut être en route ou arrêtée, Chip peut être heureux ou triste, etc.
Cette variable mouse
est utilisée dans une conditionnelle au sein d'une méthode d'affichage pour déterminer la couleur de la bande verticale :
void dessiner() {
if (mouse) {
fill(255);
} else {
fill(255, 100);
}
noStroke();
rect(x, 0, w, height);
}
Quand nous appellerons la fonction rollover()
, nous passerons les coordonnées de la souris comme arguments :
bandes[i].rollover(mouseX, mouseY);
Bien sûr, nous aurions directement pu utiliser mouseX
et mouseY
dans rollover()
sans passer de paramètres. Mais nous aurons plus de flexibilité par la suite avec ces paramètres, car nous pourrions décider d'activer le rollover non pas quand la souris passe sur la bande, mais quand un autre objet passe. Ainsi notre code devient plus réutilisable.
Voici le code complet des « bandes interactives » :
Exemple 16.2. Bandes interactives
Bande[] bandes = new Bande[10];
void setup() {
size(200, 200);
for (int i = 0; i < bandes.length; i++) {
bandes[i] = new Bande();
}
}
void draw() {
background(100);
for (int i = 0; i < bandes.length; i++) {
bandes[i].bouger();
bandes[i].rollover(mouseX, mouseY);
bandes[i].dessiner();
}
}
class Bande {
float x;
float v;
float w;
boolean mouse;
Bande() {
x = 0;
v = random(1);
w = random(10, 30);
mouse = false;
}
void dessiner() {
if(mouse) {
fill(255);
} else {
fill(255, 100);
}
noStroke();
rectMode(CORNER);
rect(x, 0, w, height);
}
void bouger() {
x += v;
if(x > width) x = -w;
}
void rollover(float mx, float my) {
mouse = (mx > x && mx < x + w);
}
}
Exercice 16.2. Organisez une rencontre entre une voiture et les bandes interactives. Au lieu de réagir à la souris, les bandes vont changer leur couleur quand le centre de la voiture est dedans. Comme nous avons pris le soin d'écrire un code réutilisable, vous n'avez pas à changer les classes Bande
et Voiture
et vous pouvez les utiliser telles quelles. Elle n'est pas belle la vie orientée objet ?
Appliquons les bonnes pratiques que nous avons appris en cours 13 et 14 et transformons notre créature de l'exemple 15.6 en objet.
class Snake {
int taille;
color couleur;
float[] xpos;
float[] ypos;
Snake(int taille, color couleur, float x0, float y0) {
this.taille = taille;
this.couleur = couleur;
xpos = new float[taille];
ypos = new float[taille];
for (int i = 0; i < taille; i++) {
xpos[i] = x0;
ypos[i] = y0;
}
}
void dessiner() {
noStroke();
for (int i = 0; i < taille; i++) {
fill(lerpColor(color(255), couleur, i / (taille - 1.0)));
ellipse(xpos[i], ypos[i], i, i);
}
}
void deplacer(float x, float y) {
for (int i = 0; i < taille - 1; i++) {
xpos[i] = xpos[i + 1];
ypos[i] = ypos[i + 1];
}
xpos[taille - 1] = x;
ypos[taille - 1] = y;
}
}
Les deux tableaux xpos
et ypos
deviennent des attributs. On les crée et initialise dans le constructeur. Pour rendre notre classe réutilisable, on ajoute deux attributs, la couleur et la taille (le nombre de cercles qui constituent le corps). Dans dessiner()
nous utilisons la fonction lerpColor()
pour avoir une couleur qui passe progressivement de blanc pour la queue à couleur
pour la tête. Finalement, deplacer()
a deux arguments, la nouvelle position de la tête.
Ces fonctionnalités nous suffisent pour reproduire l'exemple 15.6.
Exemple 16.3. Snake objet
Snake snake;
void setup() {
size(800, 800);
snake = new Snake(50, color(255, 0, 0), 0, 0);
}
void draw() {
background(0);
snake.deplacer(mouseX, mouseY);
snake.dessiner();
}
Pour rendre notre créature autonome, ajoutons une vitesse et la possibilité de se déplacer vers une position donnée, sur le même principe que Chip de l'exemple 14.2.
class Snake {
...
float vitesse;
...
void avancerVers(float x, float y) {
float xTete = xpos[taille - 1];
float yTete = ypos[taille - 1];
float d = dist(xTete, yTete, x, y);
float vx = vitesse * (x - xTete) / d;
float vy = vitesse * (y - yTete) / d;
deplacer(xTete + vx, yTete + vy);
}
}
Ajoutons également des méthodes permettant de récupérer facilement la position de la tête et de la queue d'un objet.
class Snake{
...
float getXTete() {
return xpos[taille - 1];
}
float getYTete() {
return ypos[taille - 1];
}
float getXQueue() {
return xpos[0];
}
float getYQueue() {
return ypos[0];
}
}
Des méthodes comme celles-ci permettant d'interroger un objet sur son état sont appelés accesseurs. Par convention leur nom commence par get
. Des méthodes qui changent l'état d'un objet comme deplacer()
et avancerVers()
sont des modificateurs.
Avec toutes ces fonctionnalités, le fun peut commencer ! Dans l'exemple suivant nous avons un leader contrôlé par la souris et un tableau de followers qui suivent le leader en toute autonomie.
Exemple 16.4. Leader et followers
Snake leader;
Snake[] followers = new Snake[10];
void setup() {
size(800, 800);
leader = new Snake(50, color(255, 0, 0), 0, 0, 0);
for (int i = 0; i < followers.length; i++) {
followers[i] = new Snake(int(random(20, 80)),
color(random(255), random(255), random(255)), random(2, 10),
random(width), random(height));
}
}
void draw() {
background(0);
leader.deplacer(mouseX, mouseY);
leader.dessiner();
for (int i = 0; i < followers.length; i++) {
followers[i].avancerVers(leader.getXQueue(), leader.getYQueue());
followers[i].dessiner();
}
}
class Snake {
int taille;
color couleur;
float vitesse;
float[] xpos;
float[] ypos;
Snake(int taille, color couleur, float vitesse, float x0, float y0) {
this.taille = taille;
this.couleur = couleur;
this.vitesse = vitesse;
xpos = new float[taille];
ypos = new float[taille];
for (int i = 0; i < taille; i++) {
xpos[i] = x0;
ypos[i] = y0;
}
}
void dessiner() {
noStroke();
for (int i = 0; i < taille; i++) {
fill(lerpColor(color(255), couleur, i / (taille - 1.0)));
ellipse(xpos[i], ypos[i], i, i);
}
}
// modificateurs
void deplacer(float x, float y) {
for (int i = 0; i < taille - 1; i++) {
xpos[i] = xpos[i + 1];
ypos[i] = ypos[i + 1];
}
xpos[taille - 1] = x;
ypos[taille - 1] = y;
}
void avancerVers(float x, float y) {
float xTete = xpos[taille - 1];
float yTete = ypos[taille - 1];
float d = dist(xTete, yTete, x, y);
float vx = vitesse * (x - xTete) / d;
float vy = vitesse * (y - yTete) / d;
deplacer(xTete + vx, yTete + vy);
}
// accesseurs
float getXTete() {
return xpos[taille - 1];
}
float getYTete() {
return ypos[taille - 1];
}
float getXQueue() {
return xpos[0];
}
float getYQueue() {
return ypos[0];
}
}
Exercice 16.3. Au lieu de suivre directement le leader, faites en sorte que chaque follower suive le follower précédent. Ainsi le troupeau se déplacera à la queue leu-leu.
Exercice 16.4. Débarrassez-vous du leader. Chaque individu suit l'individu précédent et le premier suit le dernier.
Exercice 16.5 Implémentez des comportements plus complexes, par exemple des proies qui évitent un prédateur, un banc de poissons etc.
Source xkcd
Exercice 16.6. Il est temps de multiplier les Chips ou plutôt vos propres créatures ! Voici quelques idées :
- Réalisez une classe qui permet de dessiner vos créatures. Elle doit être paramétrable, de façon à produire des objets d'une taille donnée, à une position donnée, avec une couleur principale distincte. Chaque créature pourrait éventuellement être composée de plusieurs parties (pensez aux serpents ou aux LEDs de Chip).
- Dans un programme principal instanciez au moins 1001 objets de petite taille que vous placerez à des positions aléatoires.
- Animez vos créatures. Ajoutez à votre classe des attributs (par exemple vitesse, direction, accélération etc.) et des méthodes (par exemple se déplacer en ligne droite, accélérer, ralentir, rebondir du bord de la fenêtre ou passer de l'autre coté, se déplacer vers une position donnée etc.). Que ça bouge !
- Faites réagir vos créatures à la souris, faites-les interagir entre elles et créer un mouvement collectif.