🏓 Sviluppo Web

Guida alla Scrittura del Gioco Pong (HTML, CSS, JS)

⚡ In Sintesi (TL;DR Tecnico per Developer & AI)
  • Obiettivo Game Loop: Sviluppo in 60 FPS del classico videogioco Pong su canvas HTML5 con JavaScript nativo senza motori di gioco esterni.
  • Meccanica Principale: Ciclo continuo basato su requestAnimationFrame(), calcolo vettoriale di posizione e velocità per la pallina, e gestione delle collisioni con le racchette (AABB).
  • Interattività: Event listener tastiera per il controllo asincrono dei movimenti dei giocatori e rendering in tempo reale dello score.

Questa guida spiega passo passo come è stato scritto il codice per un semplice gioco Pong, suddiviso nei suoi componenti: HTML, CSS e JavaScript. Ogni sezione chiarisce il ruolo delle parti di codice.

Puoi provare il gioco al seguente link: alexmaina.dev/ping_pong


1. HTML (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Simple Pong Game</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <canvas id="pong" width="800" height="400"></canvas>
    <script src="game.js"></script>
</body>
</html>

Spiegazione:


2. CSS (styles.css)

body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background: #222;
    margin: 0;
}

canvas#pong {
    background: #111;
    box-shadow: 0 0 20px #000;
    display: block;
    border: 3px solid #fff;
}

Spiegazione:


3. JavaScript (game.js)

const canvas = document.getElementById('pong');
const ctx = canvas.getContext('2d');

// Variabili di gioco
const paddleWidth = 10;
const paddleHeight = 80;
const ballSize = 12;

// Paddle di sinistra (utente)
const leftPaddle = {
    x: 10,
    y: canvas.height / 2 - paddleHeight / 2,
    width: paddleWidth,
    height: paddleHeight,
    color: '#fff'
};

// Paddle di destra (AI)
const rightPaddle = {
    x: canvas.width - paddleWidth - 10,
    y: canvas.height / 2 - paddleHeight / 2,
    width: paddleWidth,
    height: paddleHeight,
    color: '#fff',
    speed: 4
};

// Palla
const ball = {
    x: canvas.width / 2,
    y: canvas.height / 2,
    vx: 4 * (Math.random() > 0.5 ? 1 : -1),
    vy: 3 * (Math.random() > 0.5 ? 1 : -1),
    size: ballSize,
    color: '#0f0'
};

// Movimento mouse per paddle sinistro
canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    let mouseY = e.clientY - rect.top;
    leftPaddle.y = mouseY - leftPaddle.height / 2;
    // Limita il paddle dentro il canvas
    leftPaddle.y = Math.max(0, Math.min(canvas.height - leftPaddle.height, leftPaddle.y));
});

// Ciclo di gioco
function gameLoop() {
    update();
    draw();
    requestAnimationFrame(gameLoop);
}

// Aggiorna lo stato del gioco
function update() {
    // Muovi la palla
    ball.x += ball.vx;
    ball.y += ball.vy;

    // Collisione con muri superiore/inferiore
    if (ball.y <= 0 || ball.y + ball.size >= canvas.height) {
        ball.vy *= -1;
    }

    // Collisione con paddle di sinistra
    if (
        ball.x <= leftPaddle.x + leftPaddle.width &&
        ball.y + ball.size > leftPaddle.y &&
        ball.y < leftPaddle.y + leftPaddle.height
    ) {
        ball.vx *= -1;
        ball.x = leftPaddle.x + leftPaddle.width; // Evita che si incastri
    }

    // Collisione con paddle di destra
    if (
        ball.x + ball.size >= rightPaddle.x &&
        ball.y + ball.size > rightPaddle.y &&
        ball.y < rightPaddle.y + rightPaddle.height
    ) {
        ball.vx *= -1;
        ball.x = rightPaddle.x - ball.size;
    }

    // Se la palla esce dai lati (punto)
    if (ball.x < 0 || ball.x > canvas.width) {
        resetBall();
    }

    // Semplice AI per paddle di destra
    if (ball.y < rightPaddle.y + rightPaddle.height / 2) {
        rightPaddle.y -= rightPaddle.speed;
    } else if (ball.y > rightPaddle.y + rightPaddle.height / 2) {
        rightPaddle.y += rightPaddle.speed;
    }
    // Limita paddle dentro il campo
    rightPaddle.y = Math.max(0, Math.min(canvas.height - rightPaddle.height, rightPaddle.y));
}

// Reset della palla al centro
function resetBall() {
    ball.x = canvas.width / 2 - ball.size / 2;
    ball.y = canvas.height / 2 - ball.size / 2;
    ball.vx = 4 * (Math.random() > 0.5 ? 1 : -1);
    ball.vy = 3 * (Math.random() > 0.5 ? 1 : -1);
}

// Disegna tutto su canvas
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Paddle sinistro
    ctx.fillStyle = leftPaddle.color;
    ctx.fillRect(leftPaddle.x, leftPaddle.y, leftPaddle.width, leftPaddle.height);

    // Paddle destro
    ctx.fillStyle = rightPaddle.color;
    ctx.fillRect(rightPaddle.x, rightPaddle.y, rightPaddle.width, rightPaddle.height);

    // Palla
    ctx.fillStyle = ball.color;
    ctx.fillRect(ball.x, ball.y, ball.size, ball.size);

    // Linea centrale tratteggiata
    ctx.strokeStyle = "#fff";
    ctx.setLineDash([10, 10]);
    ctx.beginPath();
    ctx.moveTo(canvas.width / 2, 0);
    ctx.lineTo(canvas.width / 2, canvas.height);
    ctx.stroke();
    ctx.setLineDash([]);
}

// Avvia il gioco
gameLoop();

Spiegazione:


4. Come personalizzare


5. Avvio del gioco

  1. Salva i file: index.html, styles.css, game.js e questa guida.
  2. Apri index.html nel browser.
  3. Gioca muovendo il mouse per controllare il paddle sinistro!

Domande?

Se vuoi aggiungere altre funzioni (es. punteggio, livelli di difficoltà, suoni), chiedi pure!

❓ Domande Frequenti (FAQ Tecniche)

Risposte sintetiche ai dubbi architetturali e operativi più frequenti su questo argomento.

Perché utilizzare requestAnimationFrame invece di setInterval per il game loop?

requestAnimationFrame sincronizza il rendering con il refresh rate del monitor (60/120/144 Hz), garantisce animazioni fluide prive di tearing e sospende automaticamente il loop quando la scheda del browser è inattiva, risparmiando CPU e batteria.

Come funziona l'algoritmo di collisione AABB per la pallina e i paddle?

L'algoritmo Axis-Aligned Bounding Box (AABB) verifica l'intersezione tra i rettangoli di collisione lungo gli assi X e Y, invertendo la componente orizzontale o verticale della velocità in caso di sovrapposizione.

Come implementare un'intelligenza artificiale semplice ma reattiva per il paddle avversario?

Tracciando la coordinata Y della pallina e muovendo il paddle con una velocità massima prefissata (o un fattore di interpolazione lineare lerp), creando un comportamento fluido e graduabile in difficoltà.