Avrete sicuramente sentito parlare del famoso gioco Flappy Bird ma qui nel forum non abbiamo ancora analizzato nel dettaglio come funziona la parte Javascript.

Iniziamo subito definendo le variabili principali:

let bird;
let pipes = [];
let birdImg;

Una per l'oggetto "bird" (in questo caso il logo EHF), un array "pipes" per contenere gli oggetti "Pipe" (tubi) e una variabile "birdImg" per caricare l'immagine dell'uccello.

Definiamo preload:

function preload() {
  birdImg = loadImage('birdie.png');
}

La funzione "preload" viene eseguita una volta prima che venga avviata la funzione "setup()". Qui viene caricata l'immagine del logo "birdie.png".

Funzione setup:

function setup() {
  createCanvas(800, 600);
  bird = new Bird();
  pipes.push(new Pipe());
}

La funzione "setup()" anche questa viene eseguita una volta come preload. Qui viene creato un canvas (area di disegno) di dimensioni 800x600 pixel dove viene creato "bird" come istanza della classe "Bird" e viene creato un primo tubo che viene aggiunto all'array "pipes".

Vediamo la funzione draw

function draw() {
  background(112, 190, 201);

  for (let i = pipes.length - 1; i >= 0; i--) {
    pipes[i].show();
    pipes[i].update();

    if (pipes[i].hits(bird)) {
      console.log("HIT");
    }

    if (pipes[i].offscreen()) {
      pipes.splice(i, 1);
    }
  }

  bird.update();
  bird.show();

  if (frameCount % 100 == 0) {
    pipes.push(new Pipe());
  }
}

A differenza delle funzioni citate sopra questa viene eseguita in loop disegnando il gioco sul canvas. All'interno di questa funzione viene creato:

  • Sfondo: azzurro-verde.
  • Iterazione sui tubi: viene eseguito un ciclo "for" per iterare su tutti i tubi nell'array "pipes". Per ogni tubo abbiamo le funzioni "show()" e "update()" dell'oggetto "Pipe" corrispondente.
  • Collisione con il tubo: in questo caso con funzione "hits(bird)" su "Pipe".
  • Rimozione dei tubi fuori dalla schermata: Se un tubo è fuori dalla schermata (alla sinistra del canvas) viene rimosso dall'array "pipes".
  • Aggiornamento e visualizzazione dell'uccello: con funzioni "update()" e "show()" su "bird".
  • Aggiunta di nuovi tubi: ad ogni 100 frame viene creato un nuovo tubo e aggiunto all'array "pipes".

Funzioni di controllo per il logo:

function mousePressed() {
  bird.up();
}

function keyPressed() {
  if (key === ' ') {
    bird.up();
  }
}

Importanti perché consentono di far volare l'uccello quando il giocatore preme il mouse o la barra spaziatrice. Entrambe chiamano la funzione "up()" dell'oggetto "bird" che modifica la velocità verticale dell'uccello per farlo "saltare" verso l'alto.

Classe bird:

function Bird() {

  this.y = height / 2;
  this.x = 64;
  this.gravity = 0.6;
  this.lift = -15;
  this.velocity = 0;

  this.show = function() {
    image(birdImg, this.x, this.y, 32, 32);
  }

  // Funzione per far volare l'uccello verso l'alto
  this.up = function() {
    this.velocity += this.lift;
  }

  this.update = function() {
    this.velocity += this.gravity;
    this.velocity *= 0.9;
    this.y += this.velocity;


    if (this.y > height) {
      this.y = height;
      this.velocity = 0;
    }

    if (this.y < 0) {
      this.y = 0;
      this.velocity = 0;
    }
  }
}

Con questa parte si definiscono le caratteristiche base della classe bird come posizione (x, y), velocità verticale ("velocity"), gravità, "lift" (la forza con cui l'uccello salta verso l'alto) e viene disegnato con l'immagine "birdImg". I metodi "show()", "up()" e "update()" sono responsabili per la visualizzazione dei movimenti del logo.

Classe "Pipe":

function Pipe() {
  this.top = random(height / 2);
  this.bottom = random(height / 2);
  this.x = width;
  this.w = 20;
  this.speed = 2;
  this.highlight = false;

  this.hits = function(bird) {
    if (bird.y < this.top || bird.y > height - this.bottom) {
      if (bird.x > this.x && bird.x < this.x + this.w) {
        this.highlight = true;
        return true;
      }
    }
    this.highlight = false;
    return false;
  }

  this.show = function() {
    fill(52, 235, 55);
    if (this.highlight) {
      document.getElementById("endscreen").style.display = "block"
    }
    rect(this.x, 0, this.w, this.top);
    rect(this.x, height - this.bottom, this.w, this.bottom);
  }

  this.update = function() {
    this.x -= this.speed;
  }

  this.offscreen = function() {
    return (this.x < -this.w);
    document.getElementById("endscreen").style.display = "block"
  }
}

Come per la classe bird in questo caso si definiscono le caratteristiche dei tubi: posizione orizzontale (x) iniziale, altezza superiore ("top") e inferiore ("bottom") casuali, una larghezza ("w") di 20 pixel e una velocità orizzontale ("speed") di 2 pixel.
Si può notare che ci sono diverse funzioni ripetute come ad esempio la funzione hits(bird) che controlla se l'uccello colpisce uno dei tubi, funzione "show()" visualizza i tubi sul canvas evidenziando il tubo se l'uccello lo colpisce, "update()" aggiorna la posizione orizzontale del tubo in base alla sua velocità, "offscreen()" controlla se il tubo è fuori dalla schermata restituendo "true" se il tubo ha superato la sinistra del canvas. Le animazioni sono state create con p5.

Mettiamo insieme i pezzi:

Codice:

HTML:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>EHFflappy</title>
  <link href="style.css" rel="stylesheet" type="text/css" />
  <script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
</head>

<body>
  <div id="game">
    <script src="script.js"></script>
  </div>
  <div id="endscreen">
    <h1>Hai perso! Riprova!</h1>
  </div>
</body>

</html>

CSS:

body{
  font-family:"Arial";
}
#endscreen{
  background-color:black;
  color:white;
  width:100%;
  height:100%;
  display:none;
}

JAVASCRIPT:

let bird;
let pipes = [];
let birdImg;

function preload() {
  birdImg = loadImage('birdie.png');
}

function setup() {
  createCanvas(800, 600);
  bird = new Bird();
  pipes.push(new Pipe());
}

function draw() {
  background(112, 190, 201);

  for (let i = pipes.length - 1; i >= 0; i--) {
    pipes[i].show();
    pipes[i].update();

    if (pipes[i].hits(bird)) {
      console.log("HIT");
    }

    if (pipes[i].offscreen()) {
      pipes.splice(i, 1);
    }
  }

  bird.update();
  bird.show();

  if (frameCount % 100 == 0) {
    pipes.push(new Pipe());
  }
}

function mousePressed() {
  bird.up();

}
function keyPressed() {
  if (key === ' ') {
    bird.up();
  }

}

function Bird() {
  this.y = height / 2;
  this.x = 64;

  this.gravity = 0.6;
  this.lift = -15;
  this.velocity = 0;

  this.show = function() {
    image(birdImg, this.x, this.y, 32, 32);
  }

  this.up = function() {
    this.velocity += this.lift;
  }

  this.update = function() {
    this.velocity += this.gravity;
    this.velocity *= 0.9;
    this.y += this.velocity;

    if (this.y > height) {
      this.y = height;
      this.velocity = 0;
    }

    if (this.y < 0) {
      this.y = 0;
      this.velocity = 0;
    }
  }
}

function Pipe() {
  this.top = random(height / 2);
  this.bottom = random(height / 2);
  this.x = width;
  this.w = 20;
  this.speed = 2;

  this.highlight = false;

  this.hits = function(bird) {
    if (bird.y < this.top || bird.y > height - this.bottom) {
      if (bird.x > this.x && bird.x < this.x + this.w) {
        this.highlight = true;
        return true;
      }
    }
    this.highlight = false;
    return false;
  }

  this.show = function() {
    fill(52, 235, 55);
    if (this.highlight) {
      document.getElementById("endscreen").style.display = "block"
    }
    rect(this.x, 0, this.w, this.top);
    rect(this.x, height - this.bottom, this.w, this.bottom);
  }

  this.update = function() {
    this.x -= this.speed;
  }
  this.offscreen = function() {
    return (this.x < -this.w);
    document.getElementById("endscreen").style.display = "block"

  }
}

RISULTATO:

    Samueleex molto interessante e potenzialmente anche personalizzabile in modo semplice 😀 dopo averlo testato, faccio alcuni commenti e precisazioni:

    • attenzione alle CORS policy (vedi Configurare Localhost per il web3D), possiamo cercare un'apposita estensione per il browser ("Allow CORS") oppure tramite Python, in questo caso:
      • aprire il terminale nella directory del file HTML
      • scrivere il codice: python3 -m http.server
      • aprire il browser e inserire questo indirizzo: localhost:8000; vediamo in questo modo la directory con tutti i file, fra cui il nostro nomefile.html, che apriamo e quindi in questo modo viene avviato
    • ovviamente essendoci nel codice birdImg = loadImage('birdie.png');, dobbiamo avere nella stessa directory un file chiamato birdie.png (oppure modificare questa riga di codice, es. nomeimmagine.jpg)
    • future implementazioni: ad esempio al termine del gioco, mostrare il tempo totale come "record della modalità sopravvivenza"! Quindi ho aggiunto queste modifiche (prendendo spunto da: Limitare tempo di esecuzione del codice (C/C++, Python, JavaScript, PHP)): verso l'inizio del codice JavaScript, mettiamo la stringa var start = performance.now(); Poi, al posto della scritta statica <h1>Hai perso! Riprova!</h1> inseriamo invece il seguente codice (dinamico) JavaScript: <script>document.getElementById("endscreen").innerHTML="<h1>Hai perso! Riprova!<br>tempo totale="+1.0*(performance.now()-start)+"s</h1>"</script>

    Powered by: FreeFlarum.
    (remove this footer)