Articoli Manifesto Tools Links Canali Libri Contatti ?
Giochi / Programmazione / SDL

Gestire immagini con SDL: SDL_Image

Abstract
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.
Data di stesura: 22/05/2004
Data di pubblicazione: 19/04/2004
Ultima modifica: 04/04/2006
di Davide Coppola Discuti sul forum   Stampa

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.
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <SDL/SDL.h> 
  4. #include <SDL/SDL_image.h> 
  5.  
  6. #define DIM_H   800 
  7. #define DIM_V   600 
  8.  
  9. int main() 
  10.   //le 3 superfici principali 
  11.   SDL_Surface *bg; 
  12.   SDL_Surface *tank; 
  13.   SDL_Surface *screen; 
  14.   //superficie temporanea 
  15.   SDL_Surface *temp; 
  16.  
  17.   // si inizializza il sistema video  
  18.   if( SDL_Init(SDL_INIT_VIDEO) <0 ) 
  19.     printf("Errore init SDL: %s\n", SDL_GetError()); 
  20.     return 1; 
  21.  
  22.   // all'uscita del programma esegui SDL_Quit per risistemare le cose 
  23.   // utilizzando atexit si evita di dover chiamare SDL_QUIT prima di ogni return 
  24.   atexit(SDL_Quit); 
  25.  
  26.   //flag per settare lo schermo 
  27.   Uint32 flags=SDL_HWSURFACE|SDL_DOUBLEBUF; 
  28.  
  29.   // per rendere visualizzabile la Superficie bisogna utilizzare SDL_SetVideoMode 
  30.   if((screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags))==NULL) 
  31.     printf("Problemi settaggio video: %s\n", SDL_GetError()); 
  32.     return 1; 
  33.  
  34.   //carica sfondo 
  35.   if((temp = IMG_Load("sabbia.png"))==NULL) 
  36.     printf("Errore caricamento sfondo: %s\n",IMG_GetError()); 
  37.     return 1; 
  38.  
  39.   //Adatta l'img dello sfondo al display attuale e l'assegna ad una surface 
  40.   bg=SDL_DisplayFormat(temp); 
  41.  
  42.   //si libera temp 
  43.   SDL_FreeSurface(temp); 
  44.  
  45.   //copia sfondo su screen 
  46.   SDL_BlitSurface(bg, NULL, screen, NULL); 
  47.  
  48.   //carica tank  
  49.   if((temp = IMG_Load("tank.png"))==NULL) 
  50.     printf("Errore caricamento tank: %s\n",IMG_GetError()); 
  51.     return 1; 
  52.  
  53.   //prendiamo la mappatura del colore #0000FF in base al nostro formato di pixel 
  54.   Uint32 colorkey = SDL_MapRGB(temp->format, 0, 0, 0xFF); 
  55.   //impostiamo il colore come trasparente e attiviamo l'accelerazione RLE 
  56.   SDL_SetColorKey(temp, SDL_SRCCOLORKEY|SDL_RLEACCEL, colorkey); 
  57.  
  58.   //Adatta l'img del tank al display attuale 
  59.   tank = SDL_DisplayFormat(temp); 
  60.  
  61.   //si libera temp 
  62.   SDL_FreeSurface(temp); 
  63.  
  64.   //flag fine main loop 
  65.   int done=0; 
  66.  
  67.   //contatori vari 
  68.   int i,num_tank=1; 
  69.   int dist=tank->h+50; 
  70.   int passo=3; 
  71.  
  72.   //4 flag direzione 
  73.   int up,right,down,left; 
  74.   //inizialmente non si va da nessuna parte 
  75.   up=right=down=left=0; 
  76.  
  77.   //le coordinate del tank 
  78.   int xpos=0,ypos=0; 
  79.  
  80.   //rettangolo del tank 
  81.   SDL_Rect rect_tank; 
  82.   rect_tank.x=0; 
  83.   rect_tank.y=0; 
  84.   rect_tank.h=tank->h; 
  85.   rect_tank.w=tank->w; 
  86.  
  87.   //dichiariamo una struttura SDL_Event 
  88.   SDL_Event event; 
  89.  
  90.   //game loop, ovvero il loop che viene eseguito finche` non si esce 
  91.   while(!done) 
  92.     // SDL_PollEvent attiva il gestore di eventi 
  93.     while ( SDL_PollEvent(&event) ) 
  94.       //premendo la x della finestra col mouse si esce 
  95.       if ( event.type == SDL_QUIT )  
  96.         done = 1; 
  97.  
  98.       //viene premuto un tasto 
  99.       else if ( event.type == SDL_KEYDOWN ) 
  100.         switch(event.key.keysym.sym) 
  101.           //ma si esce anche premendo Esc 
  102.           case SDLK_ESCAPE:        
  103.             done = 1; 
  104.             break; 
  105.  
  106.           //premendo RETURN creiamo un altro tank 
  107.           case SDLK_RETURN:        
  108.             //lo creiamo se c'entra nello schermo 
  109.             if(num_tank<DIM_V/dist) 
  110.               num_tank++; 
  111.             break; 
  112.  
  113.           //premendo spazio si passa dalla modalita` schermo  
  114.           //intero alla modalita` finestra e viceversa 
  115.           case SDLK_SPACE:         
  116.             flags^=SDL_FULLSCREEN; 
  117.             screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  118.             //dobbiamo ridisegnare lo sfondo 
  119.             SDL_BlitSurface(bg, NULL, screen, NULL); 
  120.             break; 
  121.  
  122.           //FRECCIA SU 
  123.           case SDLK_UP:    
  124.             up=1; 
  125.             break; 
  126.  
  127.           //FRECCIA DX 
  128.           case SDLK_RIGHT:         
  129.             right=1; 
  130.             break; 
  131.  
  132.           //FRECCIA GIU 
  133.           case SDLK_DOWN:          
  134.             down=1; 
  135.             break; 
  136.  
  137.           //FRECCIA SX 
  138.           case SDLK_LEFT:          
  139.             left=1; 
  140.             break; 
  141.  
  142.           default: 
  143.             break; 
  144.  
  145.       //viene rilasciato un tasto 
  146.       else if ( event.type == SDL_KEYUP) 
  147.         switch(event.key.keysym.sym) 
  148.           //FRECCIA SU 
  149.           case SDLK_UP:    
  150.             up=0; 
  151.             break; 
  152.  
  153.           //FRECCIA DX 
  154.           case SDLK_RIGHT:         
  155.             right=0; 
  156.             break; 
  157.  
  158.           //FRECCIA GIU 
  159.           case SDLK_DOWN:          
  160.             down=0; 
  161.             break; 
  162.  
  163.           //FRECCIA SX 
  164.           case SDLK_LEFT:          
  165.             left=0; 
  166.             break; 
  167.  
  168.           default: 
  169.             break; 
  170.  
  171.  
  172.     //FRECCIA SU:  
  173.     //se il tank non e` posizionato a (x,0) andiamo su 
  174.     if(up)  
  175.     {        
  176.       if(ypos) 
  177.         ypos-=passo; 
  178.     //FRECCIA GIU:  
  179.     //approssima il bordo inferiore in base al numero di tank e a DIM_V 
  180.     if(down)  
  181.       if(ypos*num_tank<(DIM_V-tank->h)) 
  182.         ypos+=passo;  
  183.     //FRECCIA SX:  
  184.     //se il tank non e` posizionato a (0,y) andiamo a sx 
  185.     if(left)  
  186.       if(xpos) 
  187.         xpos-=passo; 
  188.     //FRECCIA DX:  
  189.     //se il tank non e` posizionato a (DIM_H-tank->w,y) andiamo a dx 
  190.     if(right)  
  191.       if(xpos<(DIM_H-tank->w)) 
  192.         xpos+=passo;  
  193.  
  194.     //si ridisegna lo sfondo per coprire le scie degli spostamenti 
  195.     SDL_BlitSurface(bg, NULL, screen, NULL); 
  196.  
  197.     //si aggiorna la posizione di x 
  198.     rect_tank.x=xpos; 
  199.     //si aggiornano le dimensioni del rettangolo che possono essere  
  200.     //state modificate da Blit precedenti 
  201.     rect_tank.h=tank->h; 
  202.     rect_tank.w=tank->w; 
  203.  
  204.     for(i=0;i<num_tank;i++) 
  205.       //si aggiorna la posizione di y per ogni tank 
  206.       rect_tank.y=ypos+(i*dist); 
  207.       //si disegna il tank su screen 
  208.       SDL_BlitSurface(tank, NULL, screen, &rect_tank); 
  209.  
  210.     //svuota i buffer ed aggiorna lo schermo 
  211.     SDL_Flip(screen); 
  212.  
  213.     //si fa respirare il processore 
  214.     SDL_Delay(1); 
  215.  
  216.   //libera la memoria utilizzata per mantenere le immagini 
  217.   SDL_FreeSurface(screen); 
  218.   SDL_FreeSurface(bg); 
  219.   SDL_FreeSurface(tank); 
  220.  
  221.   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.
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <SDL/SDL.h> 
  4. #include <SDL/SDL_image.h> 
  5.  
  6. #define DIM_H   800 
  7. #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).

  1. int main() 
  2.   //le 3 superfici principali 
  3.   SDL_Surface *bg; 
  4.   SDL_Surface *tank; 
  5.   SDL_Surface *screen; 
  6.   //superficie temporanea 
  7.   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.
  1. // si inizializza il sistema video  
  2. if( SDL_Init(SDL_INIT_VIDEO) <0 ) 
  3.   printf("Errore init SDL: %s\n", SDL_GetError()); 
  4.   return 1; 
Inizializziamo il sottosistema video di SDL verificando che non ci siano errori.
  1. // all'uscita del programma esegui SDL_Quit per risistemare le cose 
  2. // utilizzando atexit si evita di dover chiamare SDL_QUIT prima di ogni return 
  3. atexit(SDL_Quit); 
Affidiamo la gestione dell'uscita a SDL_Quit.
  1. //flag per settare lo schermo 
  2. Uint32 flags=SDL_HWSURFACE|SDL_DOUBLEBUF; 
  3.  
  4. // per rendere visualizzabile la Superficie bisogna utilizzare SDL_SetVideoMode 
  5. if((screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags))==NULL) 
  6.   printf("Problemi settaggio video: %s\n", SDL_GetError()); 
  7.     return 1; 
Colleghiamo la superficie screen al video impostandola come superficie hardware e richiedendo il buffer doppio utilizzato in seguito da SDL_Flip.
  1. //carica sfondo 
  2. if((temp = IMG_Load("sabbia.png"))==NULL) 
  3.   printf("Errore caricamento sfondo: %s\n",IMG_GetError()); 
  4.   return 1; 
  5.  
  6. //Adatta l'img dello sfondo al display attuale e l'assegna ad una surface 
  7. bg=SDL_DisplayFormat(temp); 
  8.  
  9. //si libera temp 
  10. 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.

  1. //copia sfondo su screen 
  2. 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.
  1. //carica tank  
  2. if((temp = IMG_Load("tank.png"))==NULL) 
  3.   printf("Errore caricamento tank: %s\n",IMG_GetError()); 
  4.   return 1; 
  5.  
  6. //prendiamo la mappatura del colore #0000FF in base al nostro formato di pixel 
  7. Uint32 colorkey = SDL_MapRGB(temp->format, 0, 0, 0xFF); 
  8. //impostiamo il colore come trasparente e attiviamo l'accelerazione RLE 
  9. SDL_SetColorKey(temp, SDL_SRCCOLORKEY|SDL_RLEACCEL, colorkey); 
  10.  
  11. //Adatta l'img del tank al display attuale 
  12. tank = SDL_DisplayFormat(temp); 
  13.  
  14. //si libera temp 
  15. 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.
  1. //flag fine main loop 
  2. int done=0; 
  3.  
  4. //contatori vari 
  5. int i,num_tank=1; 
  6. int dist=tank->h+50; 
  7. int passo=3; 
  8.  
  9. //4 flag direzione 
  10. int up,right,down,left; 
  11. //inizialmente non si va da nessuna parte 
  12. up=right=down=left=0; 
  13.  
  14. //le coordinate del tank 
  15. 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.
  1. //rettangolo del tank 
  2. SDL_Rect rect_tank; 
  3. rect_tank.x=0; 
  4. rect_tank.y=0; 
  5. rect_tank.h=tank->h; 
  6. 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.
  1. //dichiariamo una struttura SDL_Event 
  2. SDL_Event event; 
  3.  
  4. //game loop, ovvero il loop che viene eseguito finche` non si esce 
  5. while(!done) 
  6.   // SDL_PollEvent attiva il gestore di eventi 
  7.   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).

  1. //premendo la x della finestra col mouse si esce 
  2. if ( event.type == SDL_QUIT )  
  3.   done = 1; 
Il primo evento gestito è il click sulla x della finestra, che ovviamente comporta la terminazione del game loop.
  1. //viene premuto un tasto 
  2. else if ( event.type == SDL_KEYDOWN ) 
  3.   switch(event.key.keysym.sym) 
Di seguito si passa ad analizzare i tasti premuti.
  1. //ma si esce anche premendo Esc 
  2. case SDLK_ESCAPE:        
  3.   done = 1; 
  4.   break; 
Un secondo metodo per uscire è premere il tasto ESC.
  1. //premendo RETURN creiamo un altro tank 
  2. case SDLK_RETURN:        
  3.   //lo creiamo se c'entra nello schermo 
  4.   if(num_tank<DIM_V/dist) 
  5.     num_tank++; 
  6.   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).
  1. //premendo spazio si passa dalla modalita` schermo  
  2. //intero alla modalita` finestra e viceversa 
  3. case SDLK_SPACE:         
  4.   flags^=SDL_FULLSCREEN; 
  5.   screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  6.   //dobbiamo ridisegnare lo sfondo 
  7.   SDL_BlitSurface(bg, NULL, screen, NULL); 
  8.   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).

  1. //FRECCIA SU 
  2. case SDLK_UP:    
  3.   up=1; 
  4.   break; 
  5.  
  6. //FRECCIA DX 
  7. case SDLK_RIGHT:         
  8.   right=1; 
  9.   break; 
  10.  
  11. //FRECCIA GIU 
  12. case SDLK_DOWN:          
  13.   down=1; 
  14.   break; 
  15.  
  16. //FRECCIA SX 
  17. case SDLK_LEFT:          
  18.   left=1; 
  19.   break; 
In queste righe vengono attivati i flag che indicano la direzione dello spostamento del carrarmato in base ai tasti direzione premuti.
  1.                     
  2.     default: 
  3.       break; 
Non vengono gestiti altri tasti premuti.
  1. //viene rilasciato un tasto 
  2. else if ( event.type == SDL_KEYUP) 
  3.   switch(event.key.keysym.sym) 
  4.     //FRECCIA SU 
  5.     case SDLK_UP:    
  6.       up=0; 
  7.       break; 
  8.  
  9.     //FRECCIA DX 
  10.     case SDLK_RIGHT:         
  11.       right=0; 
  12.       break; 
  13.  
  14.     //FRECCIA GIU 
  15.     case SDLK_DOWN:          
  16.       down=0; 
  17.       break; 
  18.  
  19.     //FRECCIA SX 
  20.     case SDLK_LEFT:          
  21.       left=0; 
  22.       break; 
  23.  
  24.     default: 
  25.       break; 
Ovviamente si deve gestire anche il rilascio dei tasti direzione.
  1. //FRECCIA SU:  
  2. //se il tank non e` posizionato a (x,0) andiamo su 
  3. if(up)  
  4. {        
  5.   if(ypos) 
  6.     ypos-=passo; 
  7. //FRECCIA GIU:  
  8. //approssima il bordo inferiore in base al numero di tank e a DIM_V 
  9. if(down)  
  10.   if(ypos*num_tank<(DIM_V-tank->h)) 
  11.     ypos+=passo;  
  12. //FRECCIA SX:  
  13. //se il tank non e` posizionato a (0,y) andiamo a sx 
  14. if(left)  
  15.   if(xpos) 
  16.     xpos-=passo; 
  17. //FRECCIA DX:  
  18. //se il tank non e` posizionato a (DIM_H-tank->w,y) andiamo a dx 
  19. if(right)  
  20.   if(xpos<(DIM_H-tank->w)) 
  21.     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.

  1. //si ridisegna lo sfondo per coprire le scie degli spostamenti 
  2. 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.
  1. //si aggiorna la posizione di x 
  2. rect_tank.x=xpos; 
  3. //si aggiornano le dimensioni del rettangolo che possono essere  
  4. //state modificate da Blit precedenti 
  5. rect_tank.h=tank->h; 
  6. 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).

  1. for(i=0;i<num_tank;i++) 
  2.   //si aggiorna la posizione di y per ogni tank 
  3.   rect_tank.y=ypos+(i*dist); 
  4.   //si disegna il tank su screen 
  5.   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.
  1. //svuota i buffer ed aggiorna lo schermo 
  2. 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).

  1.   //si fa respirare il processore 
  2.   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).
  1.   //libera la memoria utilizzata per mantenere le immagini 
  2.   SDL_FreeSurface(screen); 
  3.   SDL_FreeSurface(bg); 
  4.   SDL_FreeSurface(tank); 
  5.  
  6.   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.

Informazioni sull'autore

Davide Coppola, studente di informatica alla Federico II di Napoli, appassionato di programmazione, sicurezza e linux. Fondatore e sviluppatore di dev-labs e di Mars Land of No Mercy. Per maggiori informazioni e contatti potete visitare la sua home page.

È possibile consultare l'elenco degli articoli scritti da Davide Coppola.

Altri articoli sul tema Giochi / Programmazione / SDL.

Risorse

  1. Sorgenti dell'esempio.
    http://www.siforge.org/articles/2004/04/sdl_image/src.zip (5Kb)
  2. Introduzione alla programmazione con SDL (parte prima)
    http://www.siforge.org/articles/2004/03/01-sdl-intro.html
  3. "Programmare in C Guida completa" di Peter Aitken, Bradley L. Jones edizioni Apogeo
    http://www.apogeonline.com/libri/88-7303-850-6/scheda
  4. Sito ufficiale del progetto SDL_Image.
    http://www.libsdl.org/projects/SDL_image/
  5. Documentazione on-line di SDL_Image.
    http://jcatki.no-ip.org/SDL_image/
  6. Il libro "Programming Linux Games".
    http://www.overcode.net/~overcode/writing/plg/local/release/
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.