✚ Sviluppo videogiochi

Collisioni con i tasselli in un platform: assi separati e pendenze

Come gestire le collisioni tra personaggio e mappa a tasselli in un platform 2D: movimento per assi separati, piattaforme attraversabili e pendenze.

In un platform 2D il personaggio passa la maggior parte del tempo a contatto con il livello: cammina sul pavimento, urta i muri, sbatte la testa contro il soffitto. Se il livello è costruito con una griglia di tasselli, le collisioni con la mappa si possono gestire in modo molto più semplice ed efficiente rispetto a quelle tra oggetti liberi. Il segreto sta in una regola: muoversi un asse alla volta.

Perché la griglia semplifica tutto

In una mappa a tasselli ogni cella ha una dimensione fissa, per esempio 16 × 16 pixel, e un tipo: vuota, solida, attraversabile dal basso, pendenza. Per sapere con quali tasselli si sovrappone il rettangolo del personaggio non serve controllare tutta la mappa: basta convertire i bordi del rettangolo in indici di cella.

colonna_sinistra = floor(x / dim_tassello)
colonna_destra   = floor((x + larghezza - 1) / dim_tassello)
riga_alta        = floor(y / dim_tassello)
riga_bassa       = floor((y + altezza - 1) / dim_tassello)

Si esaminano solo le celle in questo piccolo intervallo, di solito da due a sei. Il costo non dipende dalle dimensioni del livello.

Il problema degli spigoli

Se si sposta il personaggio in diagonale e poi si cerca di risolvere la sovrapposizione, nasce un'ambiguità. Quando il rettangolo entra nello spigolo di un blocco, va respinto in orizzontale o in verticale? Una scelta sbagliata produce difetti noti a chiunque abbia programmato un platform: il personaggio si incastra sugli spigoli del pavimento mentre corre, o scivola lungo i muri quando dovrebbe fermarsi.

Muoversi un asse alla volta

La soluzione standard divide lo spostamento in due fasi:

  1. asse orizzontale: si applica velocita.x, si controllano i tasselli solidi sovrapposti e, se c'è collisione, si riporta il personaggio a filo del muro e si azzera velocita.x;
  2. asse verticale: si applica velocita.y, si controllano di nuovo i tasselli e, in caso di collisione, si allinea il personaggio al pavimento o al soffitto e si azzera velocita.y.
x += velocita.x * dt
se collide_con_solidi():
    se velocita.x > 0: x = bordo_sinistro_tassello - larghezza
    altrimenti:        x = bordo_destro_tassello
    velocita.x = 0

y += velocita.y * dt
se collide_con_solidi():
    se velocita.y > 0:
        y = bordo_alto_tassello - altezza
        a_terra = vero
    altrimenti:
        y = bordo_basso_tassello
    velocita.y = 0

Poiché in ogni fase il movimento avviene su un solo asse, la direzione della risposta è sempre ovvia. Il flag a_terra viene impostato quando la collisione verticale avviene scendendo, e serve per decidere se il personaggio può saltare.

Velocità elevate

Se in un passo il personaggio si sposta più di un tassello, potrebbe saltare oltre un blocco sottile senza mai sovrapporsi. Due rimedi:

  • limitare la velocità massima a meno di un tassello per passo, che con un aggiornamento a 60 Hz e tasselli da 16 pixel significa meno di 960 pixel al secondo;
  • suddividere il movimento in più passi piccoli quando la velocità è alta, controllando la collisione a ciascuno.

Piattaforme attraversabili dal basso

Molti platform hanno piattaforme che si attraversano saltando da sotto e su cui si può atterrare dall'alto (one-way platforms). La regola è semplice: il tassello è solido solo se

  • il personaggio si sta muovendo verso il basso, e
  • nel passo precedente il bordo inferiore del personaggio era sopra il bordo superiore del tassello.

La seconda condizione evita che il personaggio venga «teletrasportato» sopra la piattaforma quando la sta ancora attraversando. Per permettere di scendere volontariamente, si può ignorare la piattaforma per qualche fotogramma quando il giocatore preme giù e salto insieme.

Pendenze

Le pendenze richiedono un trattamento diverso, perché il pavimento non è più alla stessa altezza in tutta la cella. Si associa al tassello inclinato una funzione che restituisce l'altezza del suolo in base alla coordinata x all'interno della cella:

Tipo di pendenza Altezza del suolo nella cella
45° in salita verso destra dim - x_locale
45° in salita verso sinistra x_locale
22,5° a due tasselli metà dell'inclinazione, con un offset per il secondo tassello

Di solito si usa il punto centrale inferiore del personaggio come «piede»: si calcola l'altezza del suolo in quel punto e, se il piede è sotto, si riporta il personaggio sopra. In discesa serve un accorgimento in più: se il personaggio era a terra nel passo precedente, lo si «aggancia» alla pendenza anche se si è staccato di pochi pixel, altrimenti scenderebbe con una serie di piccoli salti.

Verificare con il debug visivo

Disegnare sopra il gioco il rettangolo del personaggio, le celle controllate e quelle in collisione, con colori diversi, rende evidenti in pochi secondi errori che altrimenti richiederebbero ore. È uno strumento che conviene preparare fin dall'inizio.

In sintesi

  • Con una mappa a tasselli basta controllare le poche celle sotto il rettangolo del personaggio.
  • Muoversi prima in orizzontale e poi in verticale elimina l'ambiguità degli spigoli.
  • Velocità troppo alte vanno limitate o suddivise in passi più piccoli.
  • Le piattaforme attraversabili sono solide solo in discesa e dall'alto.
  • Le pendenze usano una funzione di altezza del suolo e un aggancio in discesa.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica, unisce il rigore dell'analisi tecnica all'attenzione per le persone che usano la tecnologia.

Leggi anche