Aller au contenu

Les fonctions

Vous utilisez des fonctions depuis le début : circle(), fill(), background()… Elles ont été écrites par les auteurs de p5.js. Vous pouvez aussi écrire les vôtres : nuage(), arbre(), mouton(), puis les utiliser autant de fois que vous voulez, comme si elles faisaient partie de p5.js.

Vos sketches deviennent plus courts, plus lisibles, et plus faciles à modifier : on corrige le mouton à un seul endroit, et tous les moutons changent.

On dit aussi méthode : c’est le mot employé par Processing, et par d’anciennes versions de ce cours. En JavaScript, on parle de fonction, d’où le mot function.

Les exemples de cette page sont interactifs. Dans le code, les lignes surlignées en bleu sont celles qui sont exécutées en ce moment.

Imaginez un paysage dont le draw() ressemblerait à ceci :

function draw() {
background(135, 206, 235); // ciel
soleil(500, 80);
colline(0, 300);
arbre(120, 280);
mouton(300, 320);
mouton(420, 340);
}

Ce code se lit presque comme une phrase. Pourtant, soleil(), colline(), arbre() et mouton() n’existent pas dans p5.js : c’est à vous de les écrire. Chacune regroupe les quelques formes qui dessinent un élément, et on peut l’appeler plusieurs fois, à des endroits différents, comme ici les deux moutons.

Le réflexe à prendre : quand vous copiez-collez un groupe de lignes, c’est probablement qu’il manque une fonction.

  1. function nuage() {1function, puis le nom choisi
  2. circle(100, 100, 60);2Entre accolades, les instructions
  3. circle(180, 100, 60);
  4. circle(140, 100, 80);
  5. }
  6. function draw() {
  7. background(240);
  8. nuage();3L'appel : le nom, suivi de ()
  9. }

Les lignes 1 à 5 définissent la fonction : elles expliquent à p5.js ce que veut dire nuage(), mais elles ne dessinent rien. C’est la ligne 9 qui l’appelle : c’est à ce moment-là que les trois cercles sont dessinés.

Vous reconnaissez la forme : setup() et draw() sont, elles aussi, des fonctions que vous définissez. La seule différence, c’est que vous ne les appelez jamais vous-même : p5.js le fait pour vous, une fois pour setup(), puis 60 fois par seconde pour draw().

Que se passe-t-il quand p5.js tombe sur un appel ? Il fait un détour : il saute dans la fonction, exécute ses instructions de haut en bas, puis revient juste après l’appel et continue. Voici ce draw() au ralenti, avec en plus un sol dessiné après le nuage (en vrai, tout s’exécute en un instant, avant que l’image ne s’affiche) :

function nuage() {
circle(100, 100, 60);
circle(180, 100, 60);
circle(140, 100, 80);
}
function draw() {
background(240);
nuage();
rect(0, 220, width, 80);
}

Pendant tout le détour, la ligne 9 reste allumée : on est dans nuage(), mais p5.js n’oublie pas d’où il vient. Quand il arrive à l’accolade fermante de la ligne 5, il retourne à la ligne 9 et passe à la ligne 10. (La forme qui vient d’être dessinée est en rose pour vous aider : ce n’est pas dans le code.)

Remarquez aussi que la fonction est définie avant draw(), alors qu’elle est appelée dedans. L’ordre dans lequel on écrit les fonctions n’a pas d’importance : seul compte l’ordre des appels.

Une fonction se définit au même niveau que setup() et draw() : après l’accolade fermante de l’une, jamais à l’intérieur. Dans un sketch p5.js, tout le code se trouve d’ailleurs dans des fonctions, sauf la déclaration des variables communes, tout en haut (voir plus bas).

Pour le nom :

  • pas d’espace ni d’accent, et une minuscule au début. Pour plusieurs mots, une majuscule à chaque nouveau mot : dessinerMouton() ;
  • un nom qui dit ce que fait la fonction : nuage() plutôt que truc() ;
  • pas un nom déjà pris par p5.js : une fonction à vous nommée circle() prendrait la place de celle de p5.js.

Certains noms sont réservés à un usage précis : setup(), draw(), mousePressed(), keyPressed()… Si vous définissez une fonction avec l’un de ces noms, p5.js l’appelle tout seul au bon moment (au démarrage, à chaque image, à chaque clic, à chaque touche). Vous en avez vu un exemple avec l’interrupteur.

Notre nuage() dessine toujours le même nuage, au même endroit. Pour pouvoir le placer où on veut, on lui donne des paramètres : des variables qui reçoivent des valeurs au moment de l’appel.

function nuage(x, y) {
circle(x - 40, y, 60);
circle(x + 40, y, 60);
circle(x, y, 80);
}
function draw() {
background(240);
nuage(140, 100);
nuage(mouseX, mouseY);
}
Déplacez la souris

x et y sont écrits entre les parenthèses de la définition. Au premier appel, nuage(140, 100), x vaut 140 et y vaut 100 : circle(x - 40, y, 60) devient circle(100, 100, 60). On retrouve exactement le nuage de tout à l’heure.

Au second appel, x et y reçoivent la position de la souris. C’est la même fonction, avec d’autres valeurs : le bandeau sous le canevas montre celles de chaque appel, à chaque image.

Les valeurs sont attribuées dans l’ordre : la première va dans x, la deuxième dans y. C’est pour cela que circle(x, y, d) attend toujours la position avant le diamètre : les fonctions de p5.js ont, elles aussi, des paramètres.

Une fonction peut appeler d’autres fonctions, et on peut l’appeler dans une boucle. Voici un mouton : deux pattes, un corps qui est… un nuage, et une tête noire. Avec la fonction nuage(x, y) de l’exemple précédent :

function mouton(x, y) {
line(x - 30, y, x - 30, y + 65); // pattes
line(x + 30, y, x + 30, y + 65);
nuage(x, y); // la laine
fill(0); // noir
circle(x + 60, y - 10, 30); // la tête
fill(255); // retour au blanc
}
function draw() {
background(240);
for (let x = 100; x < width; x = x + 200) {
mouton(x, 100);
}
}
Survolez les moutons

La boucle fait trois tours, avec x = 100, 300 et 500. À chaque tour, mouton() est appelée, et elle appelle à son tour nuage() avec les mêmes valeurs. (L’étiquette au-dessus du mouton survolé est là pour vous aider, elle n’est pas dans le code.)

Les pattes sont dessinées avant le corps : le nuage recouvre leur partie haute, et on ne voit que le bas.

Notez la dernière ligne de mouton(). Un fill() reste actif après la fin de la fonction : sans fill(255), le corps du mouton suivant serait noir. Une bonne habitude : remettre les réglages comme on les a trouvés.

Certaines fonctions ne dessinent rien : elles calculent une valeur et la renvoient. Vous en connaissez déjà : random(255) renvoie un nombre au hasard, hour() renvoie l’heure. Pour renvoyer une valeur, on utilise le mot return :

function milieu(a, b) {
return (a + b) / 2;
}
function draw() {
background(240);
line(50, 250, mouseX, mouseY);
let x = milieu(50, mouseX);
let y = milieu(250, mouseY);
circle(x, y, 20);
}
Déplacez la souris

milieu() calcule le nombre qui se trouve à mi-chemin entre deux autres. À la ligne 8, l’appel milieu(50, mouseX) est remplacé par le résultat : si la souris est en x = 310, il devient (50 + 310) / 2, c’est-à-dire 180. Ce nombre est rangé dans la variable x.

La fonction est appelée deux fois par image, avec des valeurs différentes : une fois pour la position horizontale, une fois pour la position verticale. Le cercle reste donc toujours au milieu de la ligne.

Ce schéma résume le chemin des valeurs, sur un autre exemple : les valeurs de l’appel entrent dans a et b, et la valeur de return revient à la place de l’appel, dans resultat.

Les valeurs 2 et 2 vont dans a et b, et a * b revient dans resultat
Les valeurs 2 et 2 vont dans a et b, et a * b revient dans resultat

Dans les anciens exemples, comme celui-ci, vous verrez var à la place de let : pour nous, c’est la même chose.

Une fonction qui renvoie une valeur n’a pas forcément de paramètres. Celle-ci renvoie le nombre de secondes écoulées depuis minuit, et print() l’affiche dans la console, sous l’éditeur :

function secondesAujourdhui() {
return hour() * 3600 + minute() * 60 + second();
}
function draw() {
print(secondesAujourdhui());
}

Une variable déclarée à l’intérieur d’une fonction n’existe que dans cette fonction. Dès que la fonction est terminée, elle disparaît. Ce code produit donc une erreur :

function setup() {
createCanvas(400, 300);
let x = 10;
}
function draw() {
x = x + 1; // erreur !
circle(x, 150, 40);
}

Le x de setup() n’existe plus quand draw() s’exécute. La console de l’éditeur affiche alors un message de ce genre (x n’est pas défini) :

« x is not defined » : la variable n'existe pas à cet endroit du programme
« x is not defined » : la variable n'existe pas à cet endroit du programme

Pour qu’une variable soit accessible partout, on la déclare tout en haut, en dehors de toute fonction :

let x = 10;
function setup() {
createCanvas(400, 300);
}
function draw() {
background(240);
x = x + 1;
circle(x, 150, 40);
}

C’est aussi vrai pour les paramètres : le x et le y de nuage(x, y) n’existent qu’à l’intérieur de nuage(). Dans l’exemple des moutons, il y a d’ailleurs deux variables x : celle de la boucle, dans draw(), et le paramètre de mouton(). Elles portent le même nom, mais chacune vit dans sa fonction, sans se gêner.

// définir (une seule fois)
function nom(a, b) {
// instructions
return resultat; // facultatif
}
// appeler (autant de fois qu'on veut)
nom(10, 20);
let r = nom(10, 20);
  • Une fonction regroupe des instructions sous un nom. La définir ne fait rien : il faut l’appeler.
  • À chaque appel, p5.js fait un détour par la fonction, puis reprend juste après l’appel.
  • Les paramètres reçoivent les valeurs de l’appel, dans l’ordre.
  • return renvoie une valeur, qui prend la place de l’appel.
  • Une variable déclarée dans une fonction n’existe que dans cette fonction. Pour la partager, déclarez-la tout en haut.
  1. Ajoutez à nuage() un troisième paramètre, taille, qui remplace le diamètre 80 du cercle du milieu. Dessinez trois nuages de tailles différentes.
  2. Dans le pré, ajoutez un quatrième mouton qui suit la souris.
  3. Écrivez une fonction arbre(x, y), avec un tronc (rect()) et un feuillage (circle()), puis plantez une rangée d’arbres avec une boucle.
  4. Avec milieu(), placez un deuxième cercle à mi-chemin entre le premier cercle et la souris.
Trois étoiles dessinées par la même fonction star()
Trois étoiles dessinées par la même fonction star()

Il n’y a pas de fonction star() dans p5.js. Celle-ci vient des exemples de Processing, traduite en p5.js. Elle utilise des notions que nous n’avons pas encore vues (cos(), sin(), beginShape()), mais vous pouvez l’utiliser sans tout comprendre, comme vous utilisez circle().

Elle prend cinq paramètres. À vous de trouver à quoi ils servent : le plus simple est de changer les valeurs, une à la fois, et de regarder le résultat.

function setup() {
createCanvas(640, 360);
}
function draw() {
background(102);
star(130, 180, 5, 70, 3);
star(320, 180, 80, 100, 40);
star(510, 180, 30, 70, 5);
}
function star(x, y, radius1, radius2, npoints) {
let angle = TWO_PI / npoints;
let halfAngle = angle / 2;
beginShape();
for (let a = 0; a < TWO_PI; a = a + angle) {
let sx = x + cos(a) * radius2;
let sy = y + sin(a) * radius2;
vertex(sx, sy);
sx = x + cos(a + halfAngle) * radius1;
sy = y + sin(a + halfAngle) * radius1;
vertex(sx, sy);
}
endShape(CLOSE);
}

Dans l’original, les étoiles tournent sur elles-mêmes, avec translate() et rotate() : voir Les transformations.