Giocare con le collisioni

Molti videogiochi, spesso e volentieri presentano un gameplay basato sul contatto dei vari oggetti di gioco, tipo il contatto tra il giocatore e un muro che ne impedisca l'avanzare.
Per questa ed altre situazioni esistono svariati metodi, ognuno specifico per uno o piu' tipi di situazioni o comunque molto piu' indicati da utilizzare per semplicita' in casi specifici.

Il metodo piu' semplice da utilizzare per gestire delle collisioni e' sicuramente il Rectangular Collision (Collisione rettangolare), conosciuto anche come Box Collision. Il presupposto e' che un qualunque oggetto sullo schermo occupa un'area quadrata o rettangolare nel campo di gioco, a discapito dello sprite (l'immagine dell'oggetto).

Si tiene conto delle coordinate di tre angoli di quest'area, segnati in verde nell'immagine.
Tenendo conto delle loro rispettive posizioni sul piano, si puo' fare un confronto fra le loro coordinate, larghezze e altezze.

Ad esempio, nel primo caso vogliamo controllare un'eventuale collisione nel lato basso del nostro giocatore, questo lato e' definito con la coordinata h1 data dalla somma del valore di Y + H dell'oggetto.
Per prima cosa controlliamo se h1 e' maggiore di y2, se si ha questa condizione successivamente si controlla che la coordinata w1 (data da X + W) sia maggiore di x2 (coordinata dell'altro oggetto).
Avendo entrambe le condizioni soddisfatte come si puo' vedere nell'immagine a destra, si ha una collisione! Se anche una sola delle due condizioni non viene soddisfatta, non si puo' parlare di collisione, questo ragionamento puo' essere applicato con facilita' a tutti e 4 i lati del rettangolo.

Una funzione d'esempio per il controllo di questi valori puo' essere la seguente:
int rectCollision(int x1, int y1, int w1, int h1,
int x2, int y2, int w2, int h2)
{
if (y1+h1 < y2) return 0;
if (y1 > y2+h2) return 0;
if (x1+w1 < x2) return 0;
if (x1 > x2+w2) return 0;

return 1;
}

Questa funzione accetta come argomenti le coordinate e i valori di larghezza e altezza degli oggetti di cui si vuole controllare un eventuale collisione, di seguito esegue un confronto sui valori finche' il tutto e' vero. Nel caso nessuno dei controlli sia vero, si riscontra una collisione e di conseguenza viene ritornato indietro il valore 1.

La collisione puo' essere controllata prima ancora del movimento effettivo del giocatore, ovvero controllare la coordinata in cui si andra' prima di occuparla effettivamente.
Nell'immagine qui accanto, abbiamo una zona tratteggiata che equivale al valore di incremento, ovvero lo spazio che occuperemo nel movimento successivo.
Nella parte alta il giocatore ha ancora un po' di spazio per muoversi, non appena si accosta all'ostacolo, viene effettuato un controllo sulla successiva posizione. Come si puo' notare, si sovrappone ad un ostacolo e quindi al nostro giocatore non sara' piu' concesso procedere in quella direzione, per tanto il suo incremento sara' pari a zero.

Prendendo spunto dall'articolo Datti una mossa, possiamo modificare la nostra funzione di gestione della tastiera per adattarla alle nuove esigenze. Invece di incrementare direttamente le coordinate X e Y del giocatore, assegneremo un valore fisso ad una variabile di incremento che successivamente provvedera' ad aggiornare le coordinate.
 /* Gestione tastiera */
if (keystate[SDLK_UP])
Player.incY=-1;
if (keystate[SDLK_DOWN])
Player.incY=+1;
if (keystate[SDLK_LEFT])
Player.incX=-1;
if (keystate[SDLK_RIGHT])
Player.incX=+1;

if(!keystate[SDLK_LEFT] && !keystate[SDLK_RIGHT])
Player.incX=0;

if(!keystate[SDLK_UP] && !keystate[SDLK_DOWN])
Player.incY=0;

move(); //Funzione per il posizionamento dell'oggetto
Come si puo' notare e' cambiato ben poco dalla funzione di partenza, la struttura _Player e' stata riscritta in maniera analoga:
struct _Objects
{
int x, y, w, h, incX, incY;
} Player, Obstacle;
Per comodita' di comprensione l'ho chiamata _Objects, da qui ricavo sia l'oggetto Player che un ipotetico oggetto Obstacle, entrambi con una coppia di coordinate oltre che l'altezza e la larghezza. Le due variabili incX e incY sono le variabili di incremento che citavo prima, le coordinate del giocatore verranno incrementate o decrementate in base al loro valore.

Nella funzione move() abbiamo il posizionamento effettivo dell'oggetto Player alle nuove coordinate sempre se queste coordinate risultano libere, il controllo viene effettuato preventivamente con la funzione rectCollision().

void move()
{
if(Player.y>=0 || Player.y + PLAYER_H <= SCREEN_HEIGHT ||
Player.x >= 0 || Player.x + PLAYER_W <= SCREEN_WIDTH)
{
if(!rectCollision(Player.x+Player.incX, Player.y, PLAYER_W-1, PLAYER_H-1,
Obstacle.x, Obstacle.y, Obstacle.w-1, Obstacle.h-1))
{
Player.x+=Player.incX;
}

if(!rectCollision(Player.x, Player.y+Player.incY, PLAYER_W-1, PLAYER_H-1,
Obstacle.x, Obstacle.y, Obstacle.w-1, Obstacle.h-1))
{
Player.y+=Player.incY;
}
}
}
Questa funzione da prima controlla che il giocatore non lasci il campo di gioco, in secondo luogo passa alla funzione rectCollision() le coordinate del giocatore sommate al valore di incremento.
Cio' permette di controllare se alla coordinata successiva del giocatore vi e' un ostacolo, come gia' spiegato poco fa, se non vi e' collisione, si incrementano le coordinate del giocatore al valore della variabile. La gestione delle coordinate e' separata per impedire il blocco del giocatore contro gli ostacoli nel caso di una direzione verticale e una orizzontale scelte allo stesso tempo.

Assegniamo il valore che vogliamo di altezza e larghezza del nostro ostacolo e posizioniamolo nel campo di gioco, il nostro giocatore si fermera' proprio a pelo di esso.

Questo metodo e' molto semplice e al tempo stesso limitativo, torna molto utile nei casi in cui si debba gestire piccole entita' vaganti sul campo di gioco come astronavi o proiettili ad esempio, volendo anche gestire interi ostacoli di livello ma di certo troppo dispendioso per questi ultimi.

Nessun commento :

Posta un commento

Related Posts Plugin for WordPress, Blogger...