Quasi ogni gioco deve rispondere centinaia di volte al secondo alla stessa domanda: questi due oggetti si stanno toccando? Il proiettile ha colpito il nemico, il giocatore ha raccolto la moneta, il personaggio è atterrato sulla piattaforma. Per i giochi 2D bastano quasi sempre poche forme semplici, e conoscerne le formule permette di scrivere collisioni affidabili anche senza un motore fisico completo.
Perché usare forme semplici
Lo sprite di un personaggio può avere una sagoma complessa, ma confrontare pixel per pixel due immagini è costoso e raramente necessario. Si associa invece a ogni oggetto una forma di collisione semplificata, chiamata spesso hitbox o collider. Le più usate sono:
- il rettangolo allineato agli assi (AABB, axis-aligned bounding box);
- il cerchio;
- il punto, utile per proiettili piccolissimi o per il cursore del mouse.
Il giocatore non vede la forma di collisione, ma ne percepisce gli effetti. Una hitbox leggermente più piccola dello sprite del giocatore rende il gioco più «generoso», mentre una hitbox più grande dei nemici fa sembrare ingiusti i colpi ricevuti.
Rettangolo contro rettangolo
Un AABB si descrive con la posizione dell'angolo in alto a sinistra e con larghezza e altezza, oppure con centro e semidimensioni. I suoi lati sono sempre paralleli agli assi dello schermo, ed è questo che rende il test così economico.
Due rettangoli si sovrappongono se e solo se si sovrappongono le loro proiezioni su entrambi gli assi:
funzione aabb_sovrapposti(a, b):
return a.x < b.x + b.larghezza and
a.x + a.larghezza > b.x and
a.y < b.y + b.altezza and
a.y + a.altezza > b.y
Basta che una sola delle quattro condizioni sia falsa perché i rettangoli siano separati. È il controllo più usato nei giochi 2D, dai platform ai giochi di ruolo visti dall'alto.
Attenzione alla scelta tra < e <=: con il minore stretto due rettangoli che si toccano esattamente su un bordo non risultano in collisione. Per un personaggio appoggiato a terra può essere proprio il comportamento desiderato; per una moneta da raccogliere forse no. L'importante è decidere e restare coerenti.
Cerchio contro cerchio
Due cerchi si toccano se la distanza tra i centri è minore della somma dei raggi. Per evitare la radice quadrata, operazione relativamente costosa, si confrontano i quadrati:
funzione cerchi_sovrapposti(a, b):
dx = a.cx - b.cx
dy = a.cy - b.cy
r = a.raggio + b.raggio
return dx*dx + dy*dy < r*r
I cerchi sono ideali per oggetti tondi o che ruotano, come asteroidi, palle e personaggi visti dall'alto. Hanno un vantaggio importante: la rotazione non cambia la forma, quindi il test resta identico qualunque sia l'orientamento.
Cerchio contro rettangolo
Il caso misto è molto comune, per esempio una palla che rimbalza contro i mattoncini in un clone di Breakout. Il metodo consiste nel trovare il punto del rettangolo più vicino al centro del cerchio, limitando le coordinate del centro all'interno del rettangolo:
funzione cerchio_rettangolo(c, r):
px = limita(c.cx, r.x, r.x + r.larghezza)
py = limita(c.cy, r.y, r.y + r.altezza)
dx = c.cx - px
dy = c.cy - py
return dx*dx + dy*dy < c.raggio * c.raggio
Se il centro del cerchio è dentro il rettangolo, il punto più vicino coincide con il centro stesso e la distanza è zero: la collisione viene rilevata correttamente.
Punto dentro una forma
Il test più semplice di tutti serve per clic del mouse, tocchi sullo schermo e proiettili minuscoli:
- punto in un AABB: la coordinata x è tra il bordo sinistro e quello destro, la y tra quello superiore e quello inferiore;
- punto in un cerchio: il quadrato della distanza dal centro è minore del quadrato del raggio.
Rilevare non basta: rispondere
Sapere che due oggetti si toccano è solo metà del lavoro. Poi bisogna decidere che cosa succede, e le risposte sono molto diverse:
| Situazione | Risposta tipica |
|---|---|
| Giocatore e moneta | Rimuovere la moneta, aumentare il punteggio |
| Proiettile e nemico | Applicare danno, distruggere il proiettile |
| Giocatore e muro | Separare gli oggetti, azzerare la velocità su quell'asse |
| Palla e mattoncino | Invertire la componente della velocità perpendicolare al lato colpito |
Per separare due AABB si calcola la penetrazione su ciascun asse e si sposta l'oggetto lungo l'asse con penetrazione minore. È il criterio più semplice per capire da quale lato è avvenuto il contatto.
Errori frequenti
- Oggetti troppo veloci: un proiettile che in un aggiornamento si sposta più della larghezza del bersaglio può attraversarlo senza mai sovrapporsi. Si risolve con passi più piccoli, con test lungo il segmento percorso o con un raycast.
- Origine dello sprite diversa da quella della hitbox: se lo sprite è disegnato dal centro e il rettangolo dall'angolo, le collisioni appaiono spostate. Disegnare le hitbox in modalità debug evita ore di ricerca.
- Rettangoli ruotati trattati come AABB: se l'oggetto ruota, l'AABB non è più adatto. Serve un test per rettangoli orientati, per esempio con il teorema dell'asse separatore, oppure un cerchio.
- Confronti tra tutti gli oggetti: con molti oggetti il numero di coppie cresce in fretta; servono tecniche di partizione dello spazio per scartare subito le coppie lontane.
In sintesi
- Le collisioni si calcolano su forme semplificate, non sui pixel degli sprite.
- Due AABB collidono se si sovrappongono le proiezioni su entrambi gli assi.
- Per i cerchi si confronta il quadrato della distanza con il quadrato della somma dei raggi.
- Il caso cerchio-rettangolo si risolve con il punto del rettangolo più vicino al centro.
- Dopo il rilevamento serve una risposta: raccolta, danno, separazione o rimbalzo.