cours04 - sbalev/processing101 GitHub Wiki
On peut utiliser plein d'analogies pour expliquer de façon intuitive la notion de variable.
- Une variable est comme un tiroir. Vous pouvez déposer un objet dedans et le récupérer quand vous en avez besoin ;
- Une variable est comme un post-it. Vous pouvez écrire un message et le consulter plus tard ;
- etc.
Techniquement, une variable est un nom qu'on donne à un emplacement (une adresse) dans la mémoire centrale de l'ordinateur, là où les données sont stockées. Les variables permettent de sauvegarder une information et de l'utiliser plus tard. En Processing c'est très utile, les variables peuvent stocker des informations relatives aux formes : couleur, taille, position, etc. Les variables sont ce dont on a besoin pour changer la couleur d'un rectangle, faire bouger un cercle dans la fenêtre, etc.
Exemple 4.1. Arya et Bran jouent aux Dragons. Pour tenir à jour leurs scores, Arya prend une feuille de papier et fait deux colonnes. À la fin de chaque partie, elle ajoute le nombre de points gagnés par chaque joueur à son score total.
Arya | Bran |
---|---|
53 | 47 |
Si on programme un jeu, on voit tout de suite l'intérêt des variables. Le score de chaque joueur sera stocké dans une variable et mis à jour chaque fois que le joueur gagne des points.
On voit qu'une variable a un nom (par exemple « Arya ») et une valeur (par exemple 53). Le nom reste toujours le même, mais la valeur peut varier. En Processing les variables ont également un type : nombres entiers, nombres réels, caractères, etc.
En résume : Une variable est un nom associé avec en emplacement dans la mémoire. Cet emplacement contient une valeur. Chaque variable a un type. Le type indique les valeurs et les opérations possibles, ainsi que la taille de l'emplacement qu'il faut allouer pour stocker la valeur.
Les variables peuvent stocker des valeurs de types de base ou des références. Pour l'instant on ne s'intéressera que des types de base, on reviendra vers les références plus tard.
Les types de bases sont :
-
byte
: petits nombres entiers entre -128 et 127 ; -
short
: nombres entiers entre -32768 et 32767 ; -
int
: nombres entiers entre -2147483648 et 2147483647 ; -
long
: grands nombres entiers entre -9223372036854775808 et 9223372036854775807 ; -
float
: nombres réels, tels que 3.141592 ; -
double
: nombres réels avec double précision, utiles pour faire de calculs mathématiques de grande précision ; -
boolean
: vrai ou faux ; -
char
: caractères, tels que 'a', 'b', '1', '!' etc.
Pour l'instant on va utiliser surtout int
et float
.
On utilise des identifiants pour nommer les variables (mais aussi les fonctions, les classes etc.). Les identifiants sont composés de lettres, chiffres et éventuellement le caractère _
. Le premier caractère ne peut pas être un chiffre. Les identifiants ne peuvent pas contenir des caractères accentués, des caractères de ponctuation et des espaces. On ne peut pas utiliser des mots-clefs (par exemple mouseX
) comme identifiants. Rappelons que Processing distingue les majuscules des minuscules. Par convention les noms des variables commencent par une minuscule. Si les noms sont composés de plusieurs mots, les mots suivants commencent par majuscule, par exemple scoreDeBran
.
Il est important de choisir des identifiants significatifs qui facilitent la compréhension et la maintenance du code.
float chipX = 100;
Pour déclarer une variable, on précise :
- son type :
float
- son nom :
chipX
- sa valeur initiale (facultative) :
= 100
- point-virgule
La déclaration d'une variable est une instruction, c'est pourquoi elle se termine par un point-virgule. Même si l'initialisation est facultative, il est préférable d'initialiser toujours ses variables. On peut initialiser une variable avec une constante (par exemple 100
) ou une expression (par exemple 4 * 5 + 3
).
Exemple 4.2. Déclarations de variables
int score = 0;
char toucheQuitter = 'q';
float forceJoueur = 12.5;
float forceBoss = 2 * forceJoueur; // la valeur initiale est 25
boolean bossEnColere = true;
Considérons ce croquis qui dessine un cercle :
void setup() {
size(200, 200);
}
void draw() {
background(255);
fill(127);
ellipse(100, 100, 50, 50);
}
Nous avons appris comment changer la position de ce cercle pour qu'il suive la souris au lieu de rester collé au centre de la fenêtre :
ellipse(mouseX, mouseY, 50, 50);
mouseX
et mouseY
sont des noms données à la position de la souris. Elles sont des variables ! Mais étant intégrés dans l'environnement Processing, on n'a pas besoin de les déclarer avant de les utiliser.
Ce qu'on veut faire maintenant est d'utiliser nos propres variables à la place de mouseX
et mouseY
. Pour cela, il faut d'abord les déclarer au début de notre croquis. On va voir plus tard qu'on peut déclarer des variables ailleurs dans notre code. Mais pour l'instant, pour éviter toute confusion, toutes les variables seront déclarées au début du croquis, avant setup()
.
Exemple 4.3. Utilisation de variables
// On déclare deux variables au début du croquis
int cercleX = 100;
int cercleY = 100;
void setup() {
size(200, 200);
}
void draw() {
background(255);
fill(127);
// On utilise ces variables pour spécifier la position du cercle
ellipse(cercleX, cercleY, 50, 50);
}
En exécutant ce croquis, on obtient exactement le même résultat comme quand la position du cercle était codée en dur. Même si au premier abord ça peut sembler plus compliqué d'utiliser un mot à la place d'un nombre, les variables rendent notre vie plus facile et plus intéressante. N'oublions pas qu'une variable n'est pas simplement le nom qu'on donne à une constante. Comme son nom l'indique clairement, une variable peut varier (changer sa valeur).
L'instruction qui permet de changer la valeur d'une variable s'appelle affectation. Elle a la syntaxe suviante :
variable = expression;
On lit « variable reçoit expression ». L'affectation ressemble à la déclaration d'une variable, mais on n'a pas besoin de préciser le type, parce que la variable a été déclaré préalablement et son type est déjà connu.
Voici quelques exemples d'affectation :
x = 5;
x = a + b;
x = x + 1;
Regardons de plus près la dernière instruction. Elle ne dit pas que x
est égal à x + 1
(aucun nombre ne vérifie cette égalité). Elle dit le suivant :
- prendre la valeur de
x
(par exemple 41) ; - ajouter 1 à cette valeur ;
- stocker le résultat (dans notre exemple 42) dans
x
.
Ce type d'affectation est très commun. On l'appelle incrémentation. Il existe même un raccourci pour faire la même chose:
x++;
Essayons d'ajouter cela à notre programme, en initialisant cette fois cercleX
à 0.
Exemple 4.4. Affectation
int cercleX = 0;
int cercleY = 100;
void setup() {
size(200, 200);
}
void draw() {
background(255);
fill(127);
ellipse(cercleX, cercleY, 50, 50);
// on incrémente cercleX
cercleX = cercleX + 1;
}
Que se passe-t-il ? Rappelons que draw()
s'exécute en boucle.
- On initialise cercleX à 0
- On exécute
setup()
qui ouvre une fenêtre de 200x200 - On exécute
draw()
- on dessine un cercle en (cercleX, cercleY) -> (0, 100)
- cercleX = 0 + 1 = 1
- On exécute
draw()
- on dessine un cercle en (cercleX, cercleY) -> (1, 100)
- cercleX = 1 + 1 = 2
- On exécute
draw()
- on dessine un cercle en (cercleX, cercleY) -> (2, 100)
- cercleX = 2 + 1 = 3
- Et ainsi de suite
Ainsi le cercle se déplace de gauche à droite sur la fenêtre.
Exercice 4.1. Modifier Exemple 4.4. pour que le cercle descende sur la diagonale de la fenêtre.
Exercice 4.2. Lorsque cercleX
devient trop grand, le cercle sort de la fenêtre en on ne le voit plus jamais. Pour prolonger notre plaisir, faisons-le revenir au bord gauche quand il quitte la fenêtre par le bord droit. Pour ce faire, on peut utiliser l'opérateur modulo (noté %
) qui calcule le reste de la division de deux nombres. Comment partager 20 bananes entre 6 singes ? Il y aura 3 bananes par singe et il reste 2 bananes pour vous (20 = 3 * 6 + 2). Donc 20 % 6 vaut 2. Si vous avez 35 bananes et 7 singes, il n'y aura malheureusement rien pour vous (35 = 5 * 7 + 0) et donc 35 % 7 vaut 0. Notons que a % b est toujours entre 0 et b - 1. Utilisez cet opérateur pour que votre cercle reste toujours visible.
Exercice 4.3. Faire grandir le cercle au lieu de se déplacer.
Exercice 4.4. Faire grandir le cercle tout en suivant la souris.
Exercice 4.5. Faire varier progressivement la couleur du cercle de noir à blanc.
Exercice 4.6. Transformer le cercle en roue en ajoutant des rayons.
Indications :
- Utiliser une variable pour l'angle des rayons.
- Comment change l'angle lorsque
cercleX
est incrémenté ? - Utiliser les fonctions
sin()
etcos()
.
Processing possède plusieurs variables internes qui contiennent des informations utiles. En nommant vos propres variables, il faut éviter les noms déjà utilisés par les variables internes. Voici la liste des variables internes le plus souvent utilisées. Il y en a d'autres qu'on peut trouver dans la documentation.
-
width
,height
: taille de la fenêtre en pixels ; -
frameCount
: le nombre d'images rendus ; -
frameRate
: la vitesse d'affichage d'images (en images par seconde) ; -
key
: la dernière touche appuyée ; -
keyPressed
: est-ce qu'il y a une touche appuyée ? (vrai ou faux) ; -
mouseX
,mouseY
: position de la souris ; -
mousePressed
: est-ce qu'il y a un bouton de souris appuyé ? (vrai ou faux) ; -
mouseButton
: quel bouton est appuyé ? (gauche, droit ou central).
Exemple 4.5. Utilisation des variables internes
void setup() {
size(200, 200);
frameRate(30);
}
void draw() {
background(255);
rectMode(RADIUS);
// on utilise frameCount pour colorer le rectangle
fill(frameCount % 256, 0, 0);
// le rectangle est centré dans la fenêtre
// sa taille est determinée par la position de la souris
rect(width / 2, height / 2, abs(mouseX - width / 2), abs(mouseY - height / 2));
}
void keyPressed() {
// on affiche la dernière touche
println(key);
}
Exercice 4.7. En utilisant width
et height
reproduire le dessin suivant. Les formes doivent changer leur taille en fonction de la taille de la fenêtre. Autrement dit, le résultat doit être similaire, peu importe ce que vous spécifiez dans size()
.
Nous avons accumulé suffisamment de connaissances pour nous lancer dans un tâche plus complexe. L'idée est de faire Chip se déplacer de façon autonome en essayant d'atteindre une cible. L'utilisateur peut changer la position de la cible en cliquant avec la souris. Pour nous faciliter le travail, nous allons suivre le principe du développement incrémental (aka « une chose à la fois »).
On va stocker la position de la cible dans deux variables cibleX
et cibleY
. On veut qu'au début la cible soit au centre de la fenêtre. Mais comme la taille de la fenêtre n'est pas connu au moment de la déclaration, on va laisser les variables non initialisées et les initialiser dans setup()
. Dans draw()
on va dessiner la cible sous forme d'une croix orange. Finalement, dans mousePressed()
on va changer la position de la cible.
int cibleX;
int cibleY;
void setup() {
size(600, 600);
cibleX = width / 2;
cibleY = height / 2;
}
void draw() {
background(255);
// dessiner la cible
stroke(255, 165, 0);
strokeWeight(5);
line(cibleX - 20, cibleY - 20, cibleX + 20, cibleY + 20);
line(cibleX - 20, cibleY + 20, cibleX + 20, cibleY - 20);
}
void mousePressed() {
cibleX = mouseX;
cibleY = mouseY;
}
Nous allons stocker la position de Chip dans deux autres variables, chipX
et chipY
. Initialement Chip sera en (0, 0). On utilisera une troisième variable pour la distance entre Chip et la cible qui nous sera utile dans nos calculs de déplacement. Dans draw()
on va dessiner Chip à sa position courante. Mais rappelons-nous le principe « une chose à la fois ». Pour se concentrer sur l'essentiel, la gestion de la position, on va représenter Chip par un simple rectangle pour l'instant. Après avoir dessiné Chip, nous allons mettre à jour sa position en le faisant faire un pas en direction de la cible.
int cibleX;
int cibleY;
float chipX = 0;
float chipY = 0;
float distChipCible;
void setup() {
size(600, 600);
frameRate(30);
cibleX = width / 2;
cibleY = height / 2;
}
void draw() {
background(255);
// dessiner la cible
stroke(255, 165, 0);
strokeWeight(5);
line(cibleX - 20, cibleY - 20, cibleX + 20, cibleY + 20);
line(cibleX - 20, cibleY + 20, cibleX + 20, cibleY - 20);
// dessiner Chip
// TODO : remplacer le rectangle par le vrai Chip
stroke(0);
strokeWeight(1);
fill(127);
rectMode(CENTER);
rect(chipX, chipY, 70, 70);
// Chip se déplace une unité en direction vers la cible
distChipCible = dist(chipX, chipY, cibleX, cibleY);
chipX += (cibleX - chipX) / distChipCible;
chipY += (cibleY - chipY) / distChipCible;
}
void mousePressed() {
cibleX = mouseX;
cibleY = mouseY;
}
N'ayez pas peur de +=
qu'on voit dans le code qui met à jour la position de Chip.
variable += expression;
est juste un raccourci pour
variable = variable + expression;
On lit « ajouter expression à variable ». Le même raccourci existe pour les autres opérations arithmétiques.
Quand on est sûr que tout marche correctement avec notre simple mannequin, on peut le remplacer par le vrai Chip. Il nous suffit de reprendre notre code d'Exemple 3.7. et de remplacer mouseX
et mouseY
par chipX
et chipY
.
Pour donner un air encore plus intelligent à Chip, on va faire varier la couleur de ses LEDs en fonction de la distance à la cible. La couleur variera de noir quand Chip est très loin à pur rouge, vert ou bleu quand Chip est sur la cible. On introduit une variable distMax
qu'on initialise dans setup()
avec la distance maximale possible (la longueur de la diagonale de la fenêtre). Ensuite dans draw()
on utilise colorMode()
pour obtenir cet effet. Voici le code complet.
Exemple 4.6. Chip suit la cible
int cibleX;
int cibleY;
float chipX = 0;
float chipY = 0;
float distChipCible;
float distMax;
void setup() {
size(600, 600);
frameRate(30);
cibleX = width / 2;
cibleY = height / 2;
distMax = dist(0, 0, width, height);
}
void draw() {
background(255);
// dessiner la cible
stroke(255, 165, 0);
strokeWeight(5);
line(cibleX - 20, cibleY - 20, cibleX + 20, cibleY + 20);
line(cibleX - 20, cibleY + 20, cibleX + 20, cibleY - 20);
// calculer la distance entre Chip et la cible
distChipCible = dist(chipX, chipY, cibleX, cibleY);
// dessiner Chip
// le corps
stroke(0);
strokeWeight(1);
fill(191);
rect(chipX - 35, chipY, 70, 70);
// les trois LEDs
// plus Chip ets proche de la cible, plus les couleurs sont brillantes
colorMode(RGB, distMax, distMax, distMax);
fill(distMax - distChipCible, 0, 0);
rect(chipX - 25, chipY + 20, 10, 10);
fill(0, distMax - distChipCible, 0);
rect(chipX - 5, chipY + 20, 10, 10);
fill(0, 0, distMax - distChipCible);
rect(chipX + 15, chipY + 20, 10, 10);
colorMode(RGB, 255, 255, 255);
// les roues
fill(255);
ellipse(chipX - 20, chipY + 70, 20, 20);
ellipse(chipX + 20, chipY + 70, 20, 20);
// la tête
fill(191);
arc(chipX, chipY - 5, 70, 70, -PI, 0, CHORD);
// la caméra
fill(63, 0, 0);
ellipse(chipX, chipY - 20, 20, 20);
// l'antenne
strokeWeight(3);
line(chipX, chipY - 40, chipX, chipY - 80);
// Chip se déplace une unité en direction vers la cible
chipX += (cibleX - chipX) / distChipCible;
chipY += (cibleY - chipY) / distChipCible;
}
void mousePressed() {
cibleX = mouseX;
cibleY = mouseY;
}
Il reste un petit défaut dans le comportement de Chip. Quand il est très proche de la cible, il commence à osciller autour. Nous allons apprendre plus tard comment éviter ça en utilisant des conditionnelles.