◆ Portail Antalaha

Annuaire › Code ouvert

Pirogue d'Antalaha - jeu de vanille (p5.js)

tutorial · Voir le site ↗
Connectez-vous pour copier ce site

Code publié par son auteur, en lecture seule. « Copier dans mon espace » crée un nouveau site à vous (non publié) que vous pouvez modifier librement.

js/fond.js · 7.3 Ko

/* ============================================================
   fond.js - le décor qui défile (effet de PARALLAXE)
   ============================================================
   La parallaxe donne une impression de profondeur : les éléments
   LOINTAINS défilent lentement, les éléments PROCHES défilent vite.
   Ici, 4 couches (de la plus lente à la plus rapide) :
     1. l'eau profonde (image fixe, dessinée une seule fois) ;
     2. de petites vaguelettes lointaines (x 0,25 de la vitesse) ;
     3. de grandes traînées d'écume       (x 0,6) ;
     4. les rives avec les palmiers       (x 1,0 : au plus près).
   Le point de vue est « vu du ciel » : tout défile vers le BAS,
   comme si la pirogue avançait vers le haut de l'écran.
   ============================================================ */

class Fond {

  /* constructor : prépare l'image de l'eau et les éléments décoratifs. */
  constructor() {
    this.decalage = 0;                         // distance totale "parcourue" par le décor
    this.periode = HAUTEUR_JEU + 120;          // hauteur au bout de laquelle un élément reboucle

    // createGraphics(l, h) crée un 2e canevas invisible : on y dessine UNE fois
    // le dégradé de l'eau, puis on le colle à chaque image avec image().
    // C'est bien plus rapide que de redessiner 320 rectangles 60 fois par seconde.
    this.eau = createGraphics(LARGEUR_JEU, HAUTEUR_JEU);
    this.eau.pixelDensity(1);
    this.dessinerEau();

    // Éléments décoratifs placés au hasard (une seule fois).
    this.vaguelettes = this.creerMotifs(26, 14, 26);
    this.ecumes = this.creerMotifs(12, 34, 60);

    // Palmiers : 6 par rive. `dx` = position entre le bord de l'écran (0) et le sable (1).
    this.palmiers = [];
    for (let cote = 0; cote < 2; cote++) {      // 0 = gauche, 1 = droite
      for (let i = 0; i < 6; i++) {
        this.palmiers.push({
          cote: cote,
          y: i * (this.periode / 6) + random(-25, 25),
          dx: random(0.15, 0.7),
          taille: random(17, 26)
        });
      }
    }
  }

  /* ----------------------------------------------------------
     dessinerEau()
     But : dessiner le dégradé vertical dans l'image this.eau.
     On appelle les fonctions de dessin SUR l'objet : this.eau.fill()
     au lieu de fill() ; sinon on dessinerait sur le canevas principal.
     ---------------------------------------------------------- */
  dessinerEau() {
    const haut = color(COULEURS.eauHaut);
    const bas = color(COULEURS.eauBas);
    this.eau.noStroke();
    for (let y = 0; y < HAUTEUR_JEU; y += 2) {
      this.eau.fill(lerpColor(haut, bas, y / HAUTEUR_JEU));
      this.eau.rect(0, y, LARGEUR_JEU, 2);
    }
  }

  /* ----------------------------------------------------------
     creerMotifs(nombre, lMin, lMax)
     But : fabriquer des positions aléatoires pour des motifs d'eau.
     Retour : tableau d'objets {x, y, l} (l = largeur du motif).
     ---------------------------------------------------------- */
  creerMotifs(nombre, lMin, lMax) {
    const liste = [];
    for (let i = 0; i < nombre; i++) {
      liste.push({
        x: random(RIVE + 6, LARGEUR_JEU - RIVE - 6),
        y: random(this.periode),
        l: random(lMin, lMax)
      });
    }
    return liste;
  }

  /* ----------------------------------------------------------
     mettreAJour(dt, vitesse)
     But : faire avancer le décor.
     Paramètres : dt = secondes écoulées ; vitesse = unités par seconde
     (distance = vitesse x temps)
     ---------------------------------------------------------- */
  mettreAJour(dt, vitesse) {
    this.decalage += vitesse * dt;
  }

  /* ----------------------------------------------------------
     positionY(yMonde, facteur)
     But : convertir la position "dans le monde" d'un motif en position
           à l'écran, pour une couche de vitesse `facteur`.
     L'opérateur % (modulo) fait revenir le motif en haut quand il
     sort par le bas : le défilement est infini.
     ---------------------------------------------------------- */
  positionY(yMonde, facteur) {
    return ((yMonde + this.decalage * facteur) % this.periode) - 60;
  }

  /* ----------------------------------------------------------
     dessiner()
     But : dessiner toutes les couches, de l'arrière vers l'avant.
     ---------------------------------------------------------- */
  dessiner() {
    // Couche 1 : eau profonde
    image(this.eau, 0, 0);

    // Couche 2 : petites vaguelettes (lentes)
    noFill();
    stroke(255, 255, 255, 60);
    strokeWeight(2);
    for (const m of this.vaguelettes) {
      const y = this.positionY(m.y, 0.25);
      // arc(x, y, largeur, hauteur, angleDébut, angleFin) : un morceau d'ellipse
      arc(m.x, y, m.l, m.l * 0.4, PI + 0.3, TWO_PI - 0.3);
    }

    // Couche 3 : traînées d'écume (moyennes)
    stroke(255, 255, 255, 90);
    strokeWeight(3);
    for (const m of this.ecumes) {
      const y = this.positionY(m.y, 0.6);
      arc(m.x, y, m.l, m.l * 0.3, PI + 0.2, TWO_PI - 0.2);
    }

    // Couche 4 : rives et palmiers (rapides = les plus proches)
    this.dessinerRive(0);   // gauche
    this.dessinerRive(1);   // droite
    for (const p of this.palmiers) {
      this.dessinerPalmier(p);
    }
  }

  /* ----------------------------------------------------------
     dessinerRive(cote)
     But : dessiner une bande de sable à bord ondulé.
     Paramètre : cote = 0 (gauche) ou 1 (droite)
     beginShape()/vertex()/endShape() : on trace un polygone
     point par point. Le bord ondule avec sin().
     ---------------------------------------------------------- */
  dessinerRive(cote) {
    // On dessine deux fois : d'abord l'écume (un peu plus large), puis le sable.
    const couches = [
      { extra: 9, couleur: color(255, 255, 255, 110) },
      { extra: 0, couleur: color(COULEURS.sable) }
    ];
    noStroke();
    for (const c of couches) {
      fill(c.couleur);
      beginShape();
      vertex(cote === 0 ? 0 : LARGEUR_JEU, -10);
      for (let y = -10; y <= HAUTEUR_JEU + 10; y += 16) {
        // Le bord dépend de (y - décalage) : il "glisse" vers le bas avec le décor.
        const bord = RIVE + c.extra + 5 * sin((y - this.decalage) * 0.025);
        vertex(cote === 0 ? bord : LARGEUR_JEU - bord, y);
      }
      vertex(cote === 0 ? 0 : LARGEUR_JEU, HAUTEUR_JEU + 10);
      endShape(CLOSE);
    }
  }

  /* ----------------------------------------------------------
     dessinerPalmier(p)
     But : dessiner un palmier vu du dessus (étoile de palmes).
     Paramètre : p = objet {cote, y, dx, taille}
     push()/translate()/rotate()/pop() : on déplace le repère sur
     le palmier, on tourne pour chaque palme, puis on restaure.
     ---------------------------------------------------------- */
  dessinerPalmier(p) {
    const y = this.positionY(p.y, 1.0);
    // Position horizontale : entre le bord de l'écran et le sable.
    const xGauche = 6 + p.dx * (RIVE - 22);
    const x = p.cote === 0 ? xGauche : LARGEUR_JEU - xGauche;

    push();
    translate(x, y);
    noStroke();
    fill(COULEURS.palmeFonce);
    for (let k = 0; k < 7; k++) {
      push();
      rotate(k * TWO_PI / 7 + p.y);              // +p.y : chaque palmier est tourné différemment
      ellipse(0, -p.taille * 0.5, p.taille * 0.35, p.taille);
      pop();
    }
    fill(COULEURS.palmeClair);
    circle(0, 0, p.taille * 0.35);
    pop();
  }
}