Le basi del tile mapping

La maggior parte dei giochi esistenti, basa la sua struttura su un concetto tanto semplice quanto d'effetto, i Tiles.

Si pensi a giochi come Zelda, Pokemon, Super Mario Bros ed anche Pacman, in un modo o nell'altro basano la loro concezione del campo di gioco sui tiles.

In sostanza, si rappresenta il campo di gioco su una struttura matriciale, composta da svariate tessere.

Un po' quasi come abbiamo fatto nello scorso articolo con Tetris, dove ogni tetramino prendeva una posizione ben precisa su una matrice o nel movimento basava i suoi passi su quello che essa conteneva.

In questo articolo getteremo le basi del tile mapping, parlando della gestione delle collisioni in una mappa di tiles.

La struttura del programma sara' sempre la solita di tutti gli altri articoli, per tanto non mi ripetero' nella stesura di quanto gia' visto.
int matrice[10][10] =
{
0,0,1,1,1,1,1,1,1,1,
1,0,1,0,0,0,0,0,0,1,
1,0,1,0,1,1,1,1,0,1,
1,0,1,0,0,0,0,1,0,1,
1,0,1,1,1,1,1,1,0,1,
1,0,1,0,0,0,1,0,0,1,
1,0,1,0,1,0,1,0,1,1,
1,0,1,0,1,0,1,0,1,1,
1,0,0,0,1,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,
};
In questo esempio di matrice, troviamo che ci crediate o no la stessa mappa che vedete in figura.
Anche se nel clone di Tetris che abbiamo creato nell'ultimo articolo, non era esplicitamente dichiarata elemento per elemento nel codice, quello che graficamente ci appariva sullo schermo poteva essere espresso tranquillamente in maniera analoga a questa.

Teniamo conto che ogni elemento di valore Zero e' da considerarsi spazio vuoto, mentre gli elementi Uno sono da considerarsi spazi occupati o ostacoli, sempre in maniera analoga al clone di Tetris.

Prima di metterci a scrivere del codice per gestire collisioni a livello di tiles, partiamo con un po' di teoria e possibili scenari di gioco.

Prima di tutto, teniamo conto che non sappiamo le coordinate di ogni singolo tiles e che il nostro giocatore non occupa fisicamente un posto nella matrice.
Il nostro personaggio, muovendosi, potra' occupare con la sua posizione seppur in modo parziale, piu' di un elemento della matrice alla volta, come in figura.
Le posizioni effettivamente occupate dal giocatore, sono quelle date dalle sue coordinate X,Y e W,H, basata la vostra concezione di collisione su queste e non sul disegno di per se che non vuol dire niente.

Mettiamo caso, che il personaggio si muova verso destra.
Il primo punto ad incontrare un ostacolo sara' w1, purtroppo non abbiamo le coordinate in pixel di quell'ostacolo dato che basiamo la nostra concezione su una matrice, ma possiamo sempre ricavare le coordinate matriciali del punto w1.

La posizione in pixel di un oggetto nell'equivalente matriciale, si calcola dividendo ciascuna delle sue coordinate per la sua grandezza, ad esempio per sapere la colonna in cui w1 si trova faremo Player.w1/TILE_SIZE (32 pixel).

Sapremo che il nostro punto w1 si trova nella posizione [0][1] della matrice, in quanto se la coordinata X ipoteticamente fosse di valore 16 a cui sommiamo 32 di larghezza, avremmo w1=48.
Dividendo w1 per 32 il risultato sara' 1, e corrispondera' ad una cella della matrice.

Il tutto funziona quasi come in una classica rect collision, ma con una lieve differenza, che qui non confrontiamo due coordinate ma due elementi in un matrice.
In maniera analoga controlleremo anche gli altri angoli, convertendoli in equivalente matriciale per controlli di collisone.

Una funzione che ci agevola questo lavoro per tutti gli angoli in un solo colpo e' la seguente, tileCollision().
int tileCollision(int x, int y, int w, int h)
{
int i, j;
int minx, miny, maxx, maxy;
// Ritorniamo una collisione se il rettangolo sta cercando di uscire dal campo di gioco
if (x < 0 || (x + w) > TILE_SIZE * MAX_MAP_X ||
y < 0 || (y + h) > TILE_SIZE * MAX_MAP_Y)
return 1;
// Convertiamo le coordinate da pixel in un equivalente per la matrice
minx = x / TILE_SIZE;
miny = y / TILE_SIZE;

maxx = (x + w - 1) / TILE_SIZE;
maxy = (y + h - 1) / TILE_SIZE;

// Se il rettangolo interseca un tile nella matrice ritorniamo una collisione
for (i = minx; i <= maxx ; i++)
{
for (j = miny ; j <= maxy ; j++)
{
if (matrice[j][i])
return 1;
}
}

// Nessuna collisione
return 0;
}
La funzione appena mostrata, accetta come argomenti le coordinate e i valori di larghezza e altezza del giocatore, convertendoli in equivalenti matriciali.
Successivamente con l'ausilio di due cicli for annidati, scorre la matrice dal punto in cui si trova il giocatore fino al punto in cui arriva con le sue dimensioni.
Tutto questo risulta molto comodo per gestire collisioni, anche di oggetti di dimensioni piu' grandi di quelle degli ostacoli di gioco.

La sua efficacia comunque e' data dal giusto utilizzo degli argomenti, si notino i vari richiami nella funzione seguente.
int getInput()
{
int x, y;
x = Player.x;
y = Player.y;

keystate = SDL_GetKeyState( NULL );

if (keystate[SDLK_LEFT])
{
if(!tileCollision(x-1, y, TILE_SIZE, TILE_SIZE))
Player.x--;
}
if (keystate[SDLK_RIGHT])
{
if(!tileCollision(x+1, y, TILE_SIZE, TILE_SIZE))
{
Player.x++;
}
}
if (keystate[SDLK_UP])
{
if(!tileCollision(x, y-1, TILE_SIZE, TILE_SIZE))
Player.y--;
}
if (keystate[SDLK_DOWN])
{
if(!tileCollision(x, y+1, TILE_SIZE, TILE_SIZE))
Player.y++;
}
}
Alla funzione tileCollision(), viene passato in argomento una delle coordinate +1, da intendersi come la coordinata successiva in cui andremo.
La funzione tileCollision() a questo punto, controllera' che nella posizione successiva alla nostra in matrice non vi siano ostacoli, se vi sono il movimento non verra' effettuato, semplicemente.

Per disegnare la nostra matrice, possiamo fare affidamento su una semplicissima funzione, simile a quella gia' vista per Tetris.
void drawbackground()
{
int i, j;
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{
if(matrice[j][i]==0)
DrawRect(i * TILE_SIZE, j * TILE_SIZE, TILE_SIZE, TILE_SIZE, 0xffffff);
}
}
}
Due cicli for annidati e la classica funzione DrawRect che disegnera' un cubetto di 32x32 pixel bianco ogni qualvolta nella matrice riscontrera' il valore 0.

La comodita' di usare mappe basate sui tiles, sta nel fatto che possono anche essere salvate in file esterni e ci risparmiano molto lavoro e risorse rispetto all'utilizzo di oggetti e strutture ogni singolo tiles.

Questo articolo getta giusto le basi, molto semplici di come un normalissimo sistema di mappe basato sui tiles funzioni e su come si possano gestire le collisoni.
Con qualche modifica al codice si puo' anche gettare le basi per un proprio RPG game volendo, o un classico maze game, basta solo un po' di ingegno.

Trovate il sorgente per questo articolo qui.

Nessun commento :

Posta un commento

Related Posts Plugin for WordPress, Blogger...