Projecte Pong amb p5.js: Pla pas a pas

A continuació et proposo un pla pas a pas per crear un projecte d’un joc de Pong amb p5.js. Aquest enfocament project-based learning et permetrà aprendre els conceptes bàsics (i alguns avançats) mentre vas implementant funcionalitats gradualment. Cada pas inclou objectius, instruccions i exemples de codi.


Pas 1. Preparació de l’Entorn

Objectius

Instruccions

  1. Utilitza el p5.js Web Editor o crea un projecte local amb un fitxer index.html que inclogui la biblioteca p5.js.
  2. Si treballes localment, crea una estructura bàsica:
    <!DOCTYPE html>
    <html lang="ca">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Pong Didàctic</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>
    </head>
    <body>
      <script src="sketch.js"></script>
    </body>
    </html>
            
  3. Crea un fitxer sketch.js on escriuràs tot el codi en p5.js.

Pas 2. Crear el Canvas i Dibuixar el Fons

Objectius

Instruccions

  1. Defineix la funció setup() per crear el canvas.
  2. A la funció draw(), pinta el fons de color negre.

Exemple de codi

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  background(0);
}
    

Tasca addicional: Experimenta canviant la mida del canvas modificant els paràmetres de createCanvas().

Pas 3. Dibuixar els Elements Bàsics: Pilota i Pala

Objectius

Instruccions

  1. Declara variables globals per a la pilota i la pala.
  2. Dins de setup(), inicialitza aquestes variables amb les seves propietats (posició, mida, etc.).
  3. Escriu funcions per dibuixar la pilota i la pala.

Exemple de codi

// Variables globals
let ball, paddle;

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Inicialització de la pilota
  ball = {
    x: width / 2,
    y: height / 2,
    size: 20,
    dx: 5,
    dy: 5
  };

  // Inicialització de la pala
  paddle = {
    x: width / 2 - 50,
    y: height - 30,
    width: 100,
    height: 10
  };
}

function draw() {
  background(0);
  drawBall();
  drawPaddle();
}

function drawBall() {
  fill(255);
  noStroke();
  ellipse(ball.x, ball.y, ball.size);
}

function drawPaddle() {
  fill(255);
  rect(paddle.x, paddle.y, paddle.width, paddle.height);
}
    

Tasca addicional: Canvia la mida de la pilota i la pala per veure com es comporta el joc.

Pas 4. Fer Moure la Pilota

Objectius

Instruccions

  1. Actualitza la posició de la pilota dins de draw().
  2. Comprova quan la pilota arriba a la vora del canvas i inverteix la direcció.

Exemple de codi

function draw() {
  background(0);
  
  // Actualitzar la posició de la pilota
  ball.x += ball.dx;
  ball.y += ball.dy;
  
  // Comprovar col·lisions amb les vores
  if (ball.x < ball.size/2 || ball.x > width - ball.size/2) {
    ball.dx *= -1;
  }
  if (ball.y < ball.size/2) {
    ball.dy *= -1;
  }
  
  // Dibuixar elements
  drawBall();
  drawPaddle();
}
    

Tasca addicional: Modifica la velocitat de la pilota per fer el joc més o menys difícil.

Pas 5. Moure la Pala amb el Ratolí

Objectius

Instruccions

  1. Afegeix un event per actualitzar la posició de la pala segons mouseX.
  2. Limita el moviment perquè la pala no surti del canvas.

Exemple de codi

function draw() {
  background(0);
  
  ball.x += ball.dx;
  ball.y += ball.dy;
  
  if (ball.x < ball.size/2 || ball.x > width - ball.size/2) {
    ball.dx *= -1;
  }
  if (ball.y < ball.size/2) {
    ball.dy *= -1;
  }
  
  // Actualitza la pala seguint el mouse, centrant-la en el cursor
  paddle.x = constrain(mouseX - paddle.width / 2, 0, width - paddle.width);
  
  drawBall();
  drawPaddle();
}
    

Tasca addicional: Prova de moure la pala amb el teclat (per exemple, amb les fletxes esquerra/dreta).

Pas 6. Detecció de Col·lisions entre la Pilota i la Pala

Objectius

Instruccions

  1. Afegeix una condició que comprovi si la pilota ha tocat la pala.
  2. Si es compleix, inverteix la direcció vertical de la pilota i actualitza la puntuació.

Exemple de codi

let score = 0;

function draw() {
  background(0);
  
  ball.x += ball.dx;
  ball.y += ball.dy;
  
  if (ball.x < ball.size/2 || ball.x > width - ball.size/2) {
    ball.dx *= -1;
  }
  if (ball.y < ball.size/2) {
    ball.dy *= -1;
  }
  
  // Comprovar col·lisió amb la pala
  if (ball.y + ball.size/2 >= paddle.y && 
      ball.x > paddle.x && ball.x < paddle.x + paddle.width) {
    ball.dy *= -1;
    score++;
  }
  
  // Mostrar la puntuació
  fill(255);
  textSize(24);
  text(`Puntuació: ${score}`, 20, 30);
  
  paddle.x = constrain(mouseX - paddle.width / 2, 0, width - paddle.width);
  
  drawBall();
  drawPaddle();
}
    

Tasca addicional: Fes que la velocitat de la pilota augmenti lleugerament en cada impacte per incrementar la dificultat.

Pas 7. Gestionar les Vides i el Reinici del Joc

Objectius

Instruccions

  1. Defineix una variable per a les vides i redueix-la quan la pilota surti de la part inferior del canvas.
  2. Mostra un missatge de "Game Over" i una opció per reiniciar.

Exemple de codi

let lives = 3;
let gameOver = false;

function draw() {
  background(0);
  
  if (gameOver) {
    fill(255, 0, 0);
    textSize(36);
    textAlign(CENTER, CENTER);
    text("Game Over", width / 2, height / 2);
    return;
  }
  
  ball.x += ball.dx;
  ball.y += ball.dy;
  
  if (ball.x < ball.size/2 || ball.x > width - ball.size/2) {
    ball.dx *= -1;
  }
  if (ball.y < ball.size/2) {
    ball.dy *= -1;
  }
  
  // Comprovar col·lisió amb la pala
  if (ball.y + ball.size/2 >= paddle.y && 
      ball.x > paddle.x && ball.x < paddle.x + paddle.width) {
    ball.dy *= -1;
    score++;
  }
  
  // Si la pilota cau per sota
  if (ball.y - ball.size/2 > height) {
    lives--;
    if (lives <= 0) {
      gameOver = true;
    } else {
      // Reset de la posició de la pilota
      ball.x = width / 2;
      ball.y = height / 2;
      ball.dx = 5;
      ball.dy = 5;
    }
  }
  
  fill(255);
  textSize(24);
  textAlign(LEFT);
  text(`Puntuació: ${score}  |  Vides: ${lives}`, 20, 30);
  
  paddle.x = constrain(mouseX - paddle.width / 2, 0, width - paddle.width);
  
  drawBall();
  drawPaddle();
}
    

Tasca addicional: Crea un botó (o una tecla) per reiniciar el joc quan s’acaba.

Pas 8. Afegir Funcionalitats Extra (Bonus i Configuracions)

Objectius

Instruccions

  1. Defineix una estructura per a les boles bonus amb propietats com el color, la posició i la velocitat.
  2. Utilitza la funció setInterval per generar bonus cada cert temps.
  3. Detecció de col·lisions entre la pala i les boles bonus per modificar la mida de la pala (per exemple, augmentar-la si el bonus és verd o reduir-la si és vermell).

Exemple de codi per bonus

let bonusBalls = [];
let bonusInterval;

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Inicialitza la pilota, la pala, puntuació i vides aquí...
  bonusInterval = setInterval(spawnBonusBall, 3000);
}

function spawnBonusBall() {
  // Genera un bonus amb tipus aleatori
  let bonus = {
    x: random(0, width),
    y: 0,
    size: 15,
    dy: 3,
    type: random() < 0.5 ? "red" : "green"
  };
  bonusBalls.push(bonus);
}

function drawBonusBalls() {
  bonusBalls.forEach((ball, i) => {
    fill(ball.type);
    ellipse(ball.x, ball.y, ball.size);
    ball.y += ball.dy;

    // Elimina el bonus si surt del canvas
    if (ball.y > height) {
      bonusBalls.splice(i, 1);
    }
    
    // Comprovar col·lisió amb la pala
    if (ball.y + ball.size/2 >= paddle.y &&
        ball.x > paddle.x &&
        ball.x < paddle.x + paddle.width) {
      // Si és "red": redueix la mida; si és "green": augmenta-la
      if (ball.type === "red") {
        paddle.width = max(50, paddle.width - 20);
      } else {
        paddle.width = min(200, paddle.width + 20);
      }
      bonusBalls.splice(i, 1);
    }
  });
}

function draw() {
  background(0);
  
  if (gameOver) {
    fill(255, 0, 0);
    textSize(36);
    textAlign(CENTER, CENTER);
    text("Game Over", width / 2, height / 2);
    return;
  }
  
  // Actualitza la posició i dibuixa la pilota com abans...
  ball.x += ball.dx;
  ball.y += ball.dy;
  
  if (ball.x < ball.size/2 || ball.x > width - ball.size/2) {
    ball.dx *= -1;
  }
  if (ball.y < ball.size/2) {
    ball.dy *= -1;
  }
  
  if (ball.y + ball.size/2 >= paddle.y && 
      ball.x > paddle.x && ball.x < paddle.x + paddle.width) {
    ball.dy *= -1;
    score++;
  }
  
  if (ball.y - ball.size/2 > height) {
    lives--;
    if (lives <= 0) {
      gameOver = true;
    } else {
      ball.x = width / 2;
      ball.y = height / 2;
      ball.dx = 5;
      ball.dy = 5;
    }
  }
  
  fill(255);
  textSize(24);
  textAlign(LEFT);
  text(`Puntuació: ${score}  |  Vides: ${lives}`, 20, 30);
  
  paddle.x = constrain(mouseX - paddle.width / 2, 0, width - paddle.width);
  
  // Dibuixar elements
  drawBall();
  drawPaddle();
  drawBonusBalls();
}
    

Tasca addicional: Permet al jugador configurar paràmetres (velocitat, mida de la pala, etc.) a través d’un petit menú inicial.

Conclusions

Aquest projecte-based learning es basa en:

  1. Exploració gradual: Començar per establir el canvas i elements bàsics, i anar afegint funcionalitats pas a pas.
  2. Experimentació: Modifica paràmetres (velocitat, mida, intervals, etc.) per veure com afecta al joc i comprova diferents aproximacions per gestionar col·lisions i actualitzar l’estat.
  3. Retroalimentació: Comprova en temps real el resultat dels canvis al codi i consulta la documentació de p5.js per trobar solucions.
  4. Extensibilitat: Un cop implementada la mecànica bàsica, afegeix funcionalitats addicionals com bonus, menú de configuració, suport per dispositius tàctils i altres millores visuals.

Aquest enfocament no sols t’ajuda a entendre els conceptes bàsics de la programació amb p5.js, sinó que també et prepara per a la modularització, l’organització del codi i la resolució de problemes a mesura que el projecte creix i es fa més complex. Prova d’afegir-te petits desafiaments a cada pas per aprofundir encara més en els conceptes apresos.