Gestire le immagini nei nostri giochi

Fino ad ora, abbiamo utilizzato e imparato a disegnare semplici forme geometriche.
Purtroppo disegnare da codice non e' cosi' comodo come magari puo' esserlo disegnare in appositi programmi come Paint e affini, decisamente meglio preparare la propria immagine e utilizzarla nel programma come parte secondaria.

In questo articolo, voglio introdurre un semplice metodo per gestire le immagini, piu' specificamente dei font disegnati.
Dalla stessa base si potrebbe anche ricavare un comodo metodo per disegnare mappe e quant'altro, il tutto partendo da una semplice immagine BMP popolata di elementi di egual misura.
Piccoli passi per arrivare pian piano a cose sempre piu' complesse, come animazioni e tanto altro.

Trovate il codice completo nel repository ufficiale del blog.

Prima di tutto, partiamo dal presupposto che fino ad ora, abbiamo lavorato su una superficie, in cui abbiamo disegnato in tempo reale semplici forme geometriche.
Azioni di poco conto, molto veloci e sbrigative, ma quando si tratta di gestire immagini esterne le cose cambiano un po'.

Caricare sul momento, stampare l'immagine, cancellare lo schermo e ripetere il tutto per tipo 50 volte al secondo, potrebbe diventare un grosso spreco di risorse sulla macchina, oltre che ad essere una cosa molto stupida...

La soluzione quindi, e' avere una superficie a parte, in cui disegnare in modo permanente l'immagine caricata e semplicemente copiarla in quella principale quando serve, molto piu' veloce e performante sicuramente.

Ci serve un immagine contenete i caratteri del nostro font, proprio come questa qui accanto.

Cominciamo quindi definendo alcune variabili, fondamenta dell'intero progetto.
char message[35];
SDL_Surface *bmpfont;
SDL_Rect area;
SDL_Surface *tmpfont;

//Vari colori
SDL_Color white = {255, 255, 255};
SDL_Color cyan = {0, 255, 255};
SDL_Color orange = {255, 128, 0};
Come gia' detto in precedenza, avremo bisogno di una superficie apposita per gestire la nostra immagine, bmpfont fara' al caso nostro, mentre message ci aiutera' nella stesura dei testi nella nostra finestra di gioco.

Prima di tutto, occorre inizializzarle, proprio come si fa per la nostra superficie Screen, in maniera analoga a questa:
      bmpfont=SDL_LoadBMP("dosemu.bmp");
if (bmpfont == NULL) {
printf("Can't load font: %s\n", SDL_GetError()); //check stdout.txt for this
exit(1);
}

tmpfont=SDL_CreateRGBSurface(SDL_SWSURFACE,8,16,32,0,0,0,0);

if(tmpfont == NULL) {
fprintf(stderr, "CreateRGBSurface failed: %s\n", SDL_GetError());
exit(1);
}

Dato che la prima superficie, conterra' in se il font da utilizzare, la inizializziamo subito passandogli come argomento la funzione SDL_LoadBMP, che creera' e restituira' una superficie dall'immagine passata come argomento.
La seconda superficie, verra' usata in un secondo momento per colorare i singoli caratteri di colori diversi, la sua dimensione e' giusta per un carattere solo, cosi' non sprechiamo piu' risorse di quelle necessarie.

Ora che abbiamo le nostre due superfici impostate, possiamo passare a creare una funzione che sia in grado di prendere un carattere alla volta da bmpfont e disegnarli nel nostro campo di gioco.
void DrawChar(SDL_Surface *screen, SDL_Surface *bmpfont, int X, int Y, int w, int h, int asciicode, SDL_Color color)
{
SDL_Rect pick, tmpfontrect;

pick.x=(asciicode % 32)*w;
pick.y=(asciicode / 32)*h;
pick.w=w;
pick.h=h;
area.x=X;
area.y=Y;
area.w=w;
area.h=h;

tmpfontrect.x=0;
tmpfontrect.y=0;
tmpfontrect.w=8;
tmpfontrect.h=16;

SDL_BlitSurface(bmpfont,&pick,tmpfont,NULL);

replaceColor (tmpfont, SDL_MapRGB (tmpfont->format, 255, 255, 255),
SDL_MapRGB (tmpfont->format, color.r, color.g, color.b));

SDL_BlitSurface(tmpfont,NULL,screen,&area);
}
Questa funzione, leggendo il valore decimale di un carattere sara' in grado di determinarne la posizione esatta nella superficie bmpfont, non e' magia ma semplice logica!

Prendiamo in esempio il valore 4, in decimale vale 52, per determinarne la colonna lo si divide per 32, il numero di caratteri presenti in ogni riga tenendo conto solo del resto.
Prendiamo il resto dalla divisione, ovvero 20, contando da 0 a 20 arriveremo proprio alla colonna contenente il carattere 4.
Per determinarne la riga esatta, ci bastera' prendere la parte intera della divisione, in questo caso 1, partendo sempre da 0 verso il basso, la seconda riga (ovvero la 1) contiene il carattere 4.
Moltiplichiamo le rispettive coordinate per larghezza e altezza, cosi' da poter avere le coordinate esatte a cui andare a prendere il carattere nella superficie, niente di cosi' difficile dopo tutto.

Il passaggio successivo a tutto cio', e' il rimpiazzo del colore con la funzione replaceColor, che di default in questo caso rimpiazzerra' tutte le occorrenze del colore bianco con un colore specificato da noi.
Per evitare di dover colorare tutti i caratteri, anche quelli non utilizzati ed evitare tante altre scomodita' in fase di programmazione, ho optato per la colorazione di un singolo carattere alla volta in una superficie a parte, la tmpfont.
La colorazione avverra' attraverso 3 passaggi, alla fine di tutto avremo il nostro carattere disegnato nella superficie del campo di gioco.

Le funzioni dedite alla gestione dei singoli pixel sono le seguenti:
Uint32 get_pixel(SDL_Surface *surface, int x, int y)
{
int bpp = surface->format->BytesPerPixel;
Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

switch (bpp)
{
case 1:
return *p;

case 2:
return *(Uint16 *)p;

case 3:
if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
return p[0] << 16 | p[1] << 8 | p[2];
else
return p[0] | p[1] << 8 | p[2] << 16;
case 4:
return *(Uint32 *)p;
default:
return 0;
}
}

void drawPixel(SDL_Surface *_ima, int x, int y, Uint32 pixel)
{
Uint32 bpp, ofs;

bpp = _ima->format->BytesPerPixel;
ofs = _ima->pitch*y + x*bpp;

SDL_LockSurface(_ima);
memcpy(_ima->pixels + ofs, &pixel, bpp);
SDL_UnlockSurface(_ima);
}

void replaceColor (SDL_Surface * src, Uint32 target, Uint32 replacement)
{
int x;
int y;

if (SDL_MUSTLOCK (src))
{
if (SDL_LockSurface (src) < 0)
return;
}
for (x = 0; x < src->w; x ++)
{
for (y = 0; y < src->h; y ++)
{
if (get_pixel (src, x, y) == target)
put_pixel (src, x, y, replacement);
}
}

if (SDL_MUSTLOCK (src))
SDL_UnlockSurface (src);
}
L'ultima, forse la piu' semplice da comprendere, legge in lungo e in largo la superficie passata in argomento, in questo caso tmpfont, cambiando eventuali pixel del colore specificato sempre in argomento.
Il disegno del singolo pixel e' uguale a quanto visto in un precedente articolo, mentre la lettura e' un qualcosa di piu' complesso preso dalla documentazione delle SDL.

Completiamo tutto il meccanismo con una semplice funzione da chiamare per disegnare la nostra stringa, per agevolare tutti gli eventuali passaggi ad uno solo per il programmatore.

void Drawstring(SDL_Surface *screen, int X, int Y, char text[], SDL_Color color)
{
int i=0;
int asciicode;
area.x=X;
area.y=Y;

//Per la lunghezza della stringa
for (i=0;i<35;i++){ asciicode=text[i]; //Valori decimali del carattere
if (asciicode == 0 )
{
break;
}
// 8x16 dimensioni della lettera
DrawChar(screen, bmpfont, area.x, area.y, 8, 16, asciicode, color);
area.x=area.x+8;
}
}

Per ogni carattere presente nella stringa message, avvieremo tutto il meccanismo gia' visto per disegnare ogni singolo carattere, ci basta impostare la nostra stringa e darla impasto a questa funzione per vedere il risultato finale!
void Draw()
{
sprintf(message,"Ciao a tutti!");
Drawstring(screen, 10, 10, message, cyan);
}
La solita funzione Draw gia' vista in tutti gli altri articoli, impostiamo la stringa con la funzione sprintf e la disegnamo in azzurro alle coordinate (10,10).

Trovate il sorgente completo nel repository ufficiale del blog, smanettateci su!

Nessun commento :

Posta un commento

Related Posts Plugin for WordPress, Blogger...