Premesse
Questa guida richiede una conoscenza discreta del C e di un pò di logica booleana,
siccome non è suo compito spiegarvi queste due cose, nel caso non possedesse queste
conoscenze, acquistate un buon libro sul C (consiglio "Programmare in C Guida completa"
di Peter Aitken, Bradley L. Jones edizioni Apogeo
[3]) e cercate di colmare le vostre lacune
sulla logica booleana con qualche documento in rete (se ne trovano in quantità).
Oltre alla conoscenza del C, presumo che siano già state lette la mie precedenti guide
dedicate all'introduzione di SDL, pertanto i concetti già spiegati non verranno
trattati con la stessa profondità.
Si fa presente che questa guida ed il programma in essa spiegato sono stati realizzati
su piattaforma linux (slackware 9.1 con gcc 3.3.3 e SDL 1.2.7), ciò non dovrebbe comportare
problemi nell'esecuzione del programma sotto altri sistemi, ma di questo non posso dare certezze.
Si segnala che l'articolo è stato realizzato durante lo sviluppo di
Mars, Land of No Mercy, un gioco
strategico a turni opensource.
Il nostro obiettivo
In questa guida verrà mostrato come caricare, gestire e visualizzare correttamente
delle immagini a video mostrando le funzioni generiche fornite da SDL e le funzioni fornite
dalla libreria SDL_Image.
In particolare verrà creato un programma che disegnerà uno o più carrarmati su di uno
sfondo e verrà data la possibilità all'utente di muoverli con le frecce.
Inoltre sarà aggiunta la possibilità di passare in modalità schermo intero e tornare alla
modalità finestra con la semplice pressione dello spazio.
Per uscire dal programma sarà sufficiente premere Esc oppure fare click sulla x della finestra.
Chiedo scusa per la grafica utilizzata, ma non sono un grafico e dopotutto questa guida
è sulla programmazione di SDL e non sull'utilizzo di Gimp.
SDL_image
SDL fornisce il supporto nativo per un unico tipo di immagini, ovvero BMP.
Siccome utilizzare file BMP è altamente sconsigliato visto il loro peso elevato, in genere si
preferisce utilizzare altri formati di immagini come ad esempio PNG, JPG e GIF.
Per poter operare su questi altri formati si utilizza la libreria SDL_Image
[4], la
quale si occupa principalmente del caricamento delle immagini all'interno di superfici.
Oltre la gestione del caricamento SDL_Image fornisce delle funzioni per l'identificazione
dei tipi di file delle immagini e per la gestione degli errori.
I formati supportati dalla libreria sono: TGA, BMP, PNM, XPM, XCF, PCX, GIF, JPG, TIF,
LBM e PNG. In pratica tutti i formati principali.
Oltre la documentazione online
[5] della SDL_Image, consiglio anche la lettura del
libro Programming Linux Games
[6], almeno per quanto riguarda il capitolo su SDL.
Sorgente
Di seguito è riportato il sorgente del programma che andremo a realizzare.
Potete iniziare a darci un primo sguardo per avere una prima di idea di quello che
andremo a spiegare nel prossimo paragrafo.
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#define DIM_H 800
#define DIM_V 600
int main()
{
//le 3 superfici principali
SDL_Surface *bg;
SDL_Surface *tank;
SDL_Surface *screen;
//superficie temporanea
SDL_Surface *temp;
// si inizializza il sistema video
if( SDL_Init(SDL_INIT_VIDEO) <0 )
{
printf("Errore init SDL: %s\n", SDL_GetError());
return 1;
}
// all'uscita del programma esegui SDL_Quit per risistemare le cose
// utilizzando atexit si evita di dover chiamare SDL_QUIT prima di ogni return
atexit(SDL_Quit);
//flag per settare lo schermo
Uint32 flags=SDL_HWSURFACE|SDL_DOUBLEBUF;
// per rendere visualizzabile la Superficie bisogna utilizzare SDL_SetVideoMode
if((screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags))==NULL)
{
printf("Problemi settaggio video: %s\n", SDL_GetError());
return 1;
}
//carica sfondo
if((temp = IMG_Load("sabbia.png"))==NULL)
{
printf("Errore caricamento sfondo: %s\n",IMG_GetError());
return 1;
}
//Adatta l'img dello sfondo al display attuale e l'assegna ad una surface
bg=SDL_DisplayFormat(temp);
//si libera temp
SDL_FreeSurface(temp);
//copia sfondo su screen
SDL_BlitSurface(bg, NULL, screen, NULL);
//carica tank
if((temp = IMG_Load("tank.png"))==NULL)
{
printf("Errore caricamento tank: %s\n",IMG_GetError());
return 1;
}
//prendiamo la mappatura del colore #0000FF in base al nostro formato di pixel
Uint32 colorkey = SDL_MapRGB(temp->format, 0, 0, 0xFF);
//impostiamo il colore come trasparente e attiviamo l'accelerazione RLE
SDL_SetColorKey(temp, SDL_SRCCOLORKEY|SDL_RLEACCEL, colorkey);
//Adatta l'img del tank al display attuale
tank = SDL_DisplayFormat(temp);
//si libera temp
SDL_FreeSurface(temp);
//flag fine main loop
int done=0;
//contatori vari
int i,num_tank=1;
int dist=tank->h+50;
int passo=3;
//4 flag direzione
int up,right,down,left;
//inizialmente non si va da nessuna parte
up=right=down=left=0;
//le coordinate del tank
int xpos=0,ypos=0;
//rettangolo del tank
SDL_Rect rect_tank;
rect_tank.x=0;
rect_tank.y=0;
rect_tank.h=tank->h;
rect_tank.w=tank->w;
//dichiariamo una struttura SDL_Event
SDL_Event event;
//game loop, ovvero il loop che viene eseguito finche` non si esce
while(!done)
{
// SDL_PollEvent attiva il gestore di eventi
while ( SDL_PollEvent(&event) )
{
//premendo la x della finestra col mouse si esce
if ( event.type == SDL_QUIT )
done = 1;
//viene premuto un tasto
else if ( event.type == SDL_KEYDOWN )
{
switch(event.key.keysym.sym)
{
//ma si esce anche premendo Esc
case SDLK_ESCAPE:
done = 1;
break;
//premendo RETURN creiamo un altro tank
case SDLK_RETURN:
//lo creiamo se c'entra nello schermo
if(num_tank<DIM_V/dist)
num_tank++;
break;
//premendo spazio si passa dalla modalita` schermo
//intero alla modalita` finestra e viceversa
case SDLK_SPACE:
flags^=SDL_FULLSCREEN;
screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags);
//dobbiamo ridisegnare lo sfondo
SDL_BlitSurface(bg, NULL, screen, NULL);
break;
//FRECCIA SU
case SDLK_UP:
up=1;
break;
//FRECCIA DX
case SDLK_RIGHT:
right=1;
break;
//FRECCIA GIU
case SDLK_DOWN:
down=1;
break;
//FRECCIA SX
case SDLK_LEFT:
left=1;
break;
default:
break;
}
}
//viene rilasciato un tasto
else if ( event.type == SDL_KEYUP)
{
switch(event.key.keysym.sym)
{
//FRECCIA SU
case SDLK_UP:
up=0;
break;
//FRECCIA DX
case SDLK_RIGHT:
right=0;
break;
//FRECCIA GIU
case SDLK_DOWN:
down=0;
break;
//FRECCIA SX
case SDLK_LEFT:
left=0;
break;
default:
break;
}
}
}
//FRECCIA SU:
//se il tank non e` posizionato a (x,0) andiamo su
if(up)
{
if(ypos)
ypos-=passo;
}
//FRECCIA GIU:
//approssima il bordo inferiore in base al numero di tank e a DIM_V
if(down)
{
if(ypos*num_tank<(DIM_V-tank->h))
ypos+=passo;
}
//FRECCIA SX:
//se il tank non e` posizionato a (0,y) andiamo a sx
if(left)
{
if(xpos)
xpos-=passo;
}
//FRECCIA DX:
//se il tank non e` posizionato a (DIM_H-tank->w,y) andiamo a dx
if(right)
{
if(xpos<(DIM_H-tank->w))
xpos+=passo;
}
//si ridisegna lo sfondo per coprire le scie degli spostamenti
SDL_BlitSurface(bg, NULL, screen, NULL);
//si aggiorna la posizione di x
rect_tank.x=xpos;
//si aggiornano le dimensioni del rettangolo che possono essere
//state modificate da Blit precedenti
rect_tank.h=tank->h;
rect_tank.w=tank->w;
for(i=0;i<num_tank;i++)
{
//si aggiorna la posizione di y per ogni tank
rect_tank.y=ypos+(i*dist);
//si disegna il tank su screen
SDL_BlitSurface(tank, NULL, screen, &rect_tank);
}
//svuota i buffer ed aggiorna lo schermo
SDL_Flip(screen);
//si fa respirare il processore
SDL_Delay(1);
}
//libera la memoria utilizzata per mantenere le immagini
SDL_FreeSurface(screen);
SDL_FreeSurface(bg);
SDL_FreeSurface(tank);
return 0;
}
Spiegazioni del sorgente - il main
Procediamo con l'analisi dettagliata del sorgente e con le spiegazioni circa
le nuove function e strutture utilizzate.
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#define DIM_H 800
#define DIM_V 600
Nelle prime righe vengono inclusi gli header necessari (1-4), in particolare potete
notare l'header SDL_image.h che ci permetterà di utilizzare la libreria SDL_image.
Subito dopo sono definite le dimensioni della finestra (ovvero la nostra risoluzione video).
int main()
{
//le 3 superfici principali
SDL_Surface *bg;
SDL_Surface *tank;
SDL_Surface *screen;
//superficie temporanea
SDL_Surface *temp;
Nel nostro programma avremo tre superfici da gestire, bg conterrà l'immagine di sfondo,
tank conterrà l'immagine del carrarmato e su screen, che sarà la superficie legata al monitor,
disegneremo le prime due per permettere la visualizzazione.
Oltre queste tre superfici utilizzeremo una superficie temporanea per effettuare delle operazioni
di ottimizzazione sulle immagini caricate.
// si inizializza il sistema video
if( SDL_Init(SDL_INIT_VIDEO) <0 )
{
printf("Errore init SDL: %s\n", SDL_GetError());
return 1;
}
Inizializziamo il sottosistema video di SDL verificando che non ci siano errori.
// all'uscita del programma esegui SDL_Quit per risistemare le cose
// utilizzando atexit si evita di dover chiamare SDL_QUIT prima di ogni return
atexit(SDL_Quit);
Affidiamo la gestione dell'uscita a SDL_Quit.
//flag per settare lo schermo
Uint32 flags=SDL_HWSURFACE|SDL_DOUBLEBUF;
// per rendere visualizzabile la Superficie bisogna utilizzare SDL_SetVideoMode
if((screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags))==NULL)
{
printf("Problemi settaggio video: %s\n", SDL_GetError());
return 1;
}
Colleghiamo la superficie screen al video impostandola come superficie hardware e
richiedendo il buffer doppio utilizzato in seguito da SDL_Flip.
//carica sfondo
if((temp = IMG_Load("sabbia.png"))==NULL)
{
printf("Errore caricamento sfondo: %s\n",IMG_GetError());
return 1;
}
//Adatta l'img dello sfondo al display attuale e l'assegna ad una surface
bg=SDL_DisplayFormat(temp);
//si libera temp
SDL_FreeSurface(temp);
Ecco le prime function per la gestione delle immagini ovvero IMG_Load e IMG_GetError,
fornite da SDL_Image, oltre queste vi sono anche SDL_DisplayFormat e SDL_FreeSurface, fornite
da SDL per operare sulle superfici.
IMG_Load si occupa di caricare un'immagine da un file su di una superficie, mentre
IMG_GetError si occupa di ritornare una stringa di descrizione di un eventuale errore generato
dalla libreria.
Nel caso avessimo voluto utilizzare il caricamento nativo di SDL avremmo potuto utilizzare
la function SDL_LoadBMP, ma per il motivo visto prima (la pesantezza), l'utilizzo di file BMP
è fortemente sconsigliato (a meno di non voler creare applicazioni che non hanno altre
dipendenze all'infuori della libreria SDL).
Una volta caricata l'immagine su di una superficie temporanea, andiamo ad ottimizzarla
rispetto le proprietà del nostro display e a copiare il risultato sulla superficie bg grazie
alla function SDL_DisplayFormat alla riga 47, questo ci permetterà di ottenere le migliori
prestazioni possibili durante il disegno a video.
Ovviamente effettuata la copia deallochiamo lo spazio riservato alla superficie temporanea.
//copia sfondo su screen
SDL_BlitSurface(bg, NULL, screen, NULL);
Una volta caricata l'immagine copiamo l'intera superficie contenente lo sfondo su la
superficie screen tramite la function SDL_BlitSurface, tale function copia una superficie
(bg nel nostro caso) su di un'altra (screen nel nostro caso).
Siccome lo sfondo occupa l'intero schermo il secondo ed il quarto parametro sono impostati a NULL,
ovvero viene detto alla function di copiare l'intera superficie iniziale sull'intera superficie
finale. In seguito vedremo come è possibile effettuare copie di parti di superfici.
//carica tank
if((temp = IMG_Load("tank.png"))==NULL)
{
printf("Errore caricamento tank: %s\n",IMG_GetError());
return 1;
}
//prendiamo la mappatura del colore #0000FF in base al nostro formato di pixel
Uint32 colorkey = SDL_MapRGB(temp->format, 0, 0, 0xFF);
//impostiamo il colore come trasparente e attiviamo l'accelerazione RLE
SDL_SetColorKey(temp, SDL_SRCCOLORKEY|SDL_RLEACCEL, colorkey);
//Adatta l'img del tank al display attuale
tank = SDL_DisplayFormat(temp);
//si libera temp
SDL_FreeSurface(temp);
In queste righe effettuiamo di nuovo l'operazione di caricamento dell'immagine e di
ottimizzazione per caricare l'immagine del carrarmato, però alle righe 62-65 eseguiamo
un'operazione fondamentale nella gestione delle immagini, ovvero impostiamo il colore
trasparente dell'immagine tank.png.
Come noterete visualizzando l'immagine tank.png con un qualsiasi programma di grafica, il
carrarmato è contenuto in un rettangolo di colore blu.
Tale accorgimento si utilizza per poter rendere trasparente il rettangolo e quindi utilizzare
l'immagine su di uno sfondo di qualsiasi colore.
Per fare ciò abbiamo bisogno innanzitutto di mappare il nostro blu (colore web #0000FF
oppure colore RGB 0,0,255) in base alla risoluzione utilizzata, pertanto utilizziamo la function
SDL_MapRGB passandole le informazioni sul formato della superficie (tank->format) e sulle 3
componenti RGB del colore (0, 0, 0xFF).
Ottenuta la mappatura la utilizziamo per settare quel colore come colore trasparente
dell'immagine associata alla superficie tank, ciò è possibile grazie alla function SDL_SetColorKey
che prende come parametri la superficie da gestire, dei flag e l'indice del colore da gestire.
I flag disponibili sono:
-
SDL_SRCCOLORKEY: fa si che il colore indicato da colorkey sia impostato come trasparente.
-
SDL_RLEACCEL: utilizza l'accelerazione RLE (se disponibile) quando si disegna una superficie.
Nel caso si imposti NULL come flag, tutti i colori della superficie verranno cancellati.
//flag fine main loop
int done=0;
//contatori vari
int i,num_tank=1;
int dist=tank->h+50;
int passo=3;
//4 flag direzione
int up,right,down,left;
//inizialmente non si va da nessuna parte
up=right=down=left=0;
//le coordinate del tank
int xpos=0,ypos=0;
In queste righe vengono dichiarate ed inizializzate le variabili utilizzate nel ciclo
principale.
La variabile done determinerà l'uscita dal loop principale e quindi dal programma.
La variabile i verrà utilizzata come contatore.
La variabile num_tank conterrà il numero di carrarmati da visualizzare, inizialmente sarà 1.
La variabile dist conterrà la distanza verticale (in pixel) tra un carrarmato e l'altro.
La variabile passo conterrà il valore dello spostamento (in pixel) del carrarmato.
Le quattro variabili up, right, down, left agiranno da flag sulla direzione dello spostamento,
inizialmente vengono impostate a 0 perchè il carrarmato è fermo.
Le variabili xpos e ypos gestiranno la posizione del primo tank sullo schermo.
//rettangolo del tank
SDL_Rect rect_tank;
rect_tank.x=0;
rect_tank.y=0;
rect_tank.h=tank->h;
rect_tank.w=tank->w;
Come ho accennato prima, la function SDL_BlitSurface permette di definire anche
porzioni di superfici dalle quali e sulle quali effettuare la copia.
Per fare ciò si utilizza la struttura SDL_Rect che identifica un rettangolo utilizzato per
delimitare un'area specifica di una superficie.
Il rettangolo è descritto mediante le coordinate del vertice in alto a sinistra (x,y) e dalle
due dimensioni (h e w) come mostrato nella figura seguente:
-------------------------
|screen |
| |
| x |
| y --------- |
| |tank | |
| | | |
| ---------w |
| h |
| |
-------------------------
Nel nostro caso useremo questo rettangolo per andare disegnare la superficie tank su
di una precisa zona dello sfondo.
//dichiariamo una struttura SDL_Event
SDL_Event event;
//game loop, ovvero il loop che viene eseguito finche` non si esce
while(!done)
{
// SDL_PollEvent attiva il gestore di eventi
while ( SDL_PollEvent(&event) )
{
In queste righe diamo inizio al loop principale ed al gestore degli eventi, ovviamente
dopo aver dichiarato la struttura SDL_Event necessaria.
Si tenga presente che non è possibile utilizzare un puntatore a tale struttura, pena
sicuri seg-fault all'uscita del programma (in alcuni casi anche durante).
//premendo la x della finestra col mouse si esce
if ( event.type == SDL_QUIT )
done = 1;
Il primo evento gestito è il click sulla x della finestra, che ovviamente comporta
la terminazione del game loop.
//viene premuto un tasto
else if ( event.type == SDL_KEYDOWN )
{
switch(event.key.keysym.sym)
{
Di seguito si passa ad analizzare i tasti premuti.
//ma si esce anche premendo Esc
case SDLK_ESCAPE:
done = 1;
break;
Un secondo metodo per uscire è premere il tasto ESC.
//premendo RETURN creiamo un altro tank
case SDLK_RETURN:
//lo creiamo se c'entra nello schermo
if(num_tank<DIM_V/dist)
num_tank++;
break;
Il secondo tasto gestito è Invio, il quale viene utilizzato per aggiungere sullo schermo
un altro carrarmato incrementando la variabile num_car. Onde evitare la generazione di carrarmati
non visualizzabili, viene effettuato un controllo su num_car richiedendo che il numero di carrarmati
attuali sia inferiore del valore ottenuto dividendo la componente dell'altezza della risoluzione
(DIM_V) per la variabile dist, ovvero l'immagine di un carrarmato più lo spazio che la separa dal
prossimo (dist=tank->h+50).
//premendo spazio si passa dalla modalita` schermo
//intero alla modalita` finestra e viceversa
case SDLK_SPACE:
flags^=SDL_FULLSCREEN;
screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags);
//dobbiamo ridisegnare lo sfondo
SDL_BlitSurface(bg, NULL, screen, NULL);
break;
Quando viene premuto il tasto SPAZIO si modifica la modalità del video, passando da quella
finestra a quella schermo intero e viceversa.
Da notare che quando viene effettuato questo passaggio è necessario ridisegnare lo sfondo,
questo per evitare di visualizzare soltanto i carrarmati (se la cosa non vi è chiara provate a
commentare la riga 132 e a compilare).
//FRECCIA SU
case SDLK_UP:
up=1;
break;
//FRECCIA DX
case SDLK_RIGHT:
right=1;
break;
//FRECCIA GIU
case SDLK_DOWN:
down=1;
break;
//FRECCIA SX
case SDLK_LEFT:
left=1;
break;
In queste righe vengono attivati i flag che indicano la direzione dello spostamento
del carrarmato in base ai tasti direzione premuti.
Non vengono gestiti altri tasti premuti.
//viene rilasciato un tasto
else if ( event.type == SDL_KEYUP)
{
switch(event.key.keysym.sym)
{
//FRECCIA SU
case SDLK_UP:
up=0;
break;
//FRECCIA DX
case SDLK_RIGHT:
right=0;
break;
//FRECCIA GIU
case SDLK_DOWN:
down=0;
break;
//FRECCIA SX
case SDLK_LEFT:
left=0;
break;
default:
break;
}
}
Ovviamente si deve gestire anche il rilascio dei tasti direzione.
//FRECCIA SU:
//se il tank non e` posizionato a (x,0) andiamo su
if(up)
{
if(ypos)
ypos-=passo;
}
//FRECCIA GIU:
//approssima il bordo inferiore in base al numero di tank e a DIM_V
if(down)
{
if(ypos*num_tank<(DIM_V-tank->h))
ypos+=passo;
}
//FRECCIA SX:
//se il tank non e` posizionato a (0,y) andiamo a sx
if(left)
{
if(xpos)
xpos-=passo;
}
//FRECCIA DX:
//se il tank non e` posizionato a (DIM_H-tank->w,y) andiamo a dx
if(right)
{
if(xpos<(DIM_H-tank->w))
xpos+=passo;
}
In queste righe viene gestito lo spostamento dei carrarmati in base alle coordinate del
primo carrarmato (visto che gli altri sono solo delle copie speculari spostate sull'asse y).
Per farlo si verifica quali flag sono attivi al momento, andando poi ad incrementare
o a decrementare le coordinate del primo carrarmato di tani pixel quanti indicati dalla variabile
passo.
Nel caso si voglia accelerare o decelerare il movimento si può operare sul numero di pixel
che costituiscono lo spostamento, aumentandoli (per aumentare) o diminuendoli (per decellerare).
Da notare come vengano effettuati dei controlli (righe 196, 203, 210, 217) per evitare che le
immagini dei carrarmati vadano oltre lo sfondo.
//si ridisegna lo sfondo per coprire le scie degli spostamenti
SDL_BlitSurface(bg, NULL, screen, NULL);
Ad ogni iterazione del game loop ridisegniamo lo sfondo per coprire le modifiche effettuate
dagli spostamenti dei carrarmati. È importante disegnare prima lo sfondo e poi tutti i carrarmati
onde evitare comportamenti anomali nella rappresentazione.
//si aggiorna la posizione di x
rect_tank.x=xpos;
//si aggiornano le dimensioni del rettangolo che possono essere
//state modificate da Blit precedenti
rect_tank.h=tank->h;
rect_tank.w=tank->w;
Una volta che abbiamo determinato le nuove coordinate del primo carrarmato possiamo
aggiornare la posizione orizzontale del rettangolo che andremo a disegnare (la verticale verrà
aggiornata di seguito per ogni carrarmato da disegnare).
Notate come sia importante riassegnare anche il valore dei campi .h e .w onde evitare
comportamenti anomali dovuti a modifiche di questi ultimi da altre function (come ad esempio
SDL_BlitSurface).
for(i=0;i<num_tank;i++)
{
//si aggiorna la posizione di y per ogni tank
rect_tank.y=ypos+(i*dist);
//si disegna il tank su screen
SDL_BlitSurface(tank, NULL, screen, &rect_tank);
}
All'interno di questo for vengono calcolate le componenti verticali dei vari carrarmati
da disegnare e vengono disegnate le superfici sullo schermo mediante la function SDL_BlitSurface.
Notate come questa volta il quarto parametro è l'indirizzo della struttura SDL_Rect, infatti,
come abbiamo visto la struttura identificherà un rettangolo all'interno di screen dove disegnare
l'intera superficie tank (infatti il secondo parametro è NULL).
Nel caso avessimo voluto disegnare parte del tank avremmo dovuto passare l'indirizzo di un altra
struttura SDL_Rect (con valori opportunatamente assegnati) come secondo parametro.
//svuota i buffer ed aggiorna lo schermo
SDL_Flip(screen);
Una volta disegnate le superfici necessarie su screen andiamo a disegnare quest'ultima sul
video.
Per fare ciò nel miglior modo possibile ci affidiamo alla function SDL_Flip che sfruttando il doppio
buffer gestisce il tutto nella maniera migliore (maggiore velocità).
Si tenga presente che l'aggiornamento del video deve essere effettuato una sola volta dopo
che si è disegnato tutte le superfici su quella screen.
Nel caso si effettuino aggiornamenti multipli (ad esempio uno dopo ogni chiamata a SDL_BlitSurface),
si ottiene uno sfarfallio delle immagini improponibile (oltre che un sovraccarico del lavoro del
programma).
//si fa respirare il processore
SDL_Delay(1);
}
Al termine del game loop è bene inserire una chiamata alla function SDL_Delay per fermare
temporaneamente il processo e quindi ridurre il carico del processore.
Il parametro passato rappresenta il numero di millisecondi di pausa da effettuare, ovviamente il
valore è approssimativo in quanto il valore effettivo dipende dallo scheduling dei processi
eseguito dal kernel.
Un valore di 1 ms. permette di fare scendere l'utilizzo del processore al 50%, nel caso si voglia
diminuire ancora il carico si devono utilizzare valori superiori ai 10 ms.
Ovviamente aumentando il valore vengono introdotte delle "pause" nel programma che possono
compromettere la resa grafica, pertanto valori superiori al millisecondo sono da considerare solo
per giochi non in tempo reale e comunque con poche animazioni.
Per giochi in tempo reale si può scegliere di usare come valore di pausa 1, oppure di non chiamare
proprio la function, ottenendo così le migliori prestazioni possibili (ma anche l'utilizzo massimo
della CPU).
//libera la memoria utilizzata per mantenere le immagini
SDL_FreeSurface(screen);
SDL_FreeSurface(bg);
SDL_FreeSurface(tank);
return 0;
}
Usciti dal ciclo principale ci preoccupiamo di liberare la memoria utilizzata dalle immagini
grazie alla function SDL_FreeSurface per poi uscire.
In questo caso avremmo potuto anche evitare di liberare la memoria, visto che sarebbe stata
l'ultima azione del programma e quindi la liberazione sarebbe stata gestita da SDL_Quit, ma è buona
regola liberare sempre la memoria utilizzata dalle immagini non appena queste non sono più necessarie.
Compilazione
Per compilare il nostro sorgente è necessario utilizzare come segue il gcc:
g++ -O2 -s -o tank immagini.cpp `sdl-config --cflags --libs` -lSDL_image
L'opzione -O2 dirà al compilatore di effettuare un'ottimizzazione di secondo
livello.
L'opzione -s effettuerà lo strip sull'eseguibile, rimuovendo le parti non
utilizzate, rendendo quindi, le dimensioni decisamente ridotte.
L'opzione -o imposterà il nome dell'eseguibile in tank.
`sdl-config --cflags --libs` ci darà tutte le opzioni necessarie per includere
ed utilizzare la libreria SDL.
Infine -lSDL_image caricherà la libreria SDL_image.