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

Introduzione alla programmazione con SDL

Abstract
La guida introduce la programmazione con la libreria SDL (Simple DirectMedia Layer), una libreria open source per lo sviluppo di applicazioni multimediali, presentandone i concetti, le strutture e le primitive basilari.
In particolare verrà realizzato un primo programma di esempio che crei una finestra e permetta il passaggio alla modalità schermo intero e viceversa.
Data di stesura: 08/04/2004
Data di pubblicazione: 01/03/2004
Ultima modifica: 04/04/2006
di Davide Coppola Discuti sul forum   Stampa

Premesse

Questa guida richiede una conoscenza discreta del C, siccome non è suo compito spiegarvelo, nel caso non possedesse questa conoscenza acquistate un buon libro sull'argomento (consiglio "Programmare in C Guida completa" di Peter Aitken, Bradley L. Jones edizioni Apogeo [3]).
Inoltre questa guida e il programma in essa spiegato sono stati realizzati su piattaforma linux (slackware 9.1 con gcc 3.3.3 e SDL 1.2.6), 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.

SDL

SDL ovvero Simple DirectMedia Layer, è una libreria multipiattaforma (utilizzabile su diversi sistemi, come ad esempio linux, MAC OS, windows, ed altri ancora) che permette un accesso di basso livello alle periferiche multimediali, ovvero all'audio, al video 2D, al video 3D tramite OpenGl, alla tastiera, al mouse, al joystick ed al cdrom, inoltre fornisce funzioni per la gestione del tempo e dei thread.

Ovviamente SDL è utilizzata per lo sviluppo di applicazioni multimediali, ed in particolar modo di giochi, come esempi possiamo indicare "Cube" [4], "The battle for Wesnorth" [5] e svariati porting della Loki Games [6].

La libreria può essere scaricata dal sito ufficiale [7] insieme alla documentazione in formato HTML e pagine di man, cosa che consiglio vivamente, vista la comodità di consultazione di queste ultime.

Il nostro obiettivo

In questa guida vedremo come inizializzare correttamente SDL, spiegheremo dei concetti base di SDL e vedremo alcune delle sue prime funzioni.

In particolare verrà creato un programma in cui viene inizializzato il sistema di SDL e si crea una finestra vuota dalla quale si può uscire premendo i tasti Esc oppure clickando con il mouse sulla x di chiusura finestra, inoltre sarà aggiunta la possibilità di passare in modalità schermo intero e tornare alla modalità finestra con la semplice pressione dello spazio.

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.  
  5. #define DIM_H   800 
  6. #define DIM_V   600 
  7.  
  8. int main() 
  9.   // si inizializza il sistema video 
  10.   if( SDL_Init(SDL_INIT_VIDEO) <0 ) 
  11.     // SDL_GetError() ritorna una descrizione dell'errore 
  12.     printf("Errore init SDL: %s\n", SDL_GetError()); 
  13.     return 1; 
  14.  
  15.   // all'uscita del programma esegui SDL_Quit per risistemare le cose 
  16.   atexit(SDL_Quit); 
  17.  
  18.   //dichiariamo un puntatore a una struttura SDL_Surface 
  19.   SDL_Surface *screen; 
  20.  
  21.   //indichiamo la superficie screen come direttamente collegata al video 
  22.   screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,SDL_HWSURFACE|SDL_DOUBLEBUF); 
  23.  
  24.   if ( screen == NULL ) 
  25.     printf("Problemi con il settaggio dello schermo: %s\n", SDL_GetError()); 
  26.     return 1; 
  27.  
  28.   //variabile per il ciclo principale 
  29.   int fine=0; 
  30.   //memoria dello stato dello schermo (intero o a finestra) 
  31.   int full_s=0; 
  32.   //flag per SDL_SetVideoMode 
  33.   Uint32 flags=SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_FULLSCREEN; 
  34.  
  35.   //dichiariamo una struttura SDL_Event 
  36.   SDL_Event event; 
  37.  
  38.   //game loop, ovvero il loop che viene eseguito finche` non si esce 
  39.   while(!fine) 
  40.     //SDL_WaitEvent attende il prossimo evento 
  41.     SDL_WaitEvent(&event); 
  42.  
  43.     //premendo la x della finestra col mouse si esce 
  44.     if ( event.type == SDL_QUIT ) 
  45.       fine = 1; 
  46.  
  47.     if ( event.type == SDL_KEYDOWN ) 
  48.       //ma si esce anche premendo Esc 
  49.       if ( event.key.keysym.sym == SDLK_ESCAPE ) 
  50.         fine = 1; 
  51.  
  52.       if ( event.key.keysym.sym == SDLK_SPACE ) 
  53.         //premendo SPAZIO si passa in modalita` schermo intero 
  54.         if(!full_s) 
  55.           screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  56.           full_s++; 
  57.           flags=SDL_HWSURFACE|SDL_DOUBLEBUF; 
  58.         //premendolo ancora si torna in modalita` finestra 
  59.         else 
  60.           screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  61.           full_s--; 
  62.           flags=SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_FULLSCREEN; 
  63.  
  64.  
  65.   return 0; 

Spiegazioni del sorgente

Procediamo con l'analisi dettagliata del sorgente e con le spiegazioni circa la struttura generale di SDL e le funzioni utilizzate.
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <SDL/SDL.h> 
  4.  
  5. #define DIM_H   800 
  6. #define DIM_V   600 
Nelle prime righe includiamo gli header necessari, oltre i classici stdio e stdlib appare l'header necessario per utilizzare le funzioni della libreria SDL.

Alle righe 5 e 6 definiamo due costanti che useremo in seguito per indicare la risoluzione dello schermo, in questo caso 800x600.

  1. // si inizializza il sistema video 
  2. if( SDL_Init(SDL_INIT_VIDEO) <0 ) 
  3.   // SDL_GetError() ritorna una descrizione dell'errore 
  4.   printf("Errore init SDL: %s\n", SDL_GetError()); 
  5.   return 1; 
In questo primo blocco di codice andiamo ad inizializzare SDL, ed in particolare il sottosistema video.
Per fare ciò utilizziamo la function SDL_Init che prende come parametri una delle seguenti costanti:
  • SDL_INIT_TIMER (inizializzazione sottosistema temporizzazione)
  • SDL_INIT_AUDIO (inizializzazione sottosistema audio)
  • SDL_INIT_VIDEO (inizializzazione sottosistema video)
  • SDL_INIT_CDROM (inizializzazione sottosistema cdrom)
  • SDL_INIT_JOYSTICK (inizializzazione sottosistema joystick)
  • SDL_INIT_NOPARACHUTE (impedisce a SDL di lanciare segnali fatali)
  • SDL_INIT_EVENTTHREAD (inizializzazione thread? - non documentata)
  • SDL_INIT_EVERYTHING (inizializzazione di tutti i sottosistemi)
ovviamente è possibile inizializzare più sottosistemi alla volta, utilizzando l'operatore or, ovvero passando come parametro una lista del genere:
SDL_INIT_VIDEO|SDL_INIT_AUDIO|...altre const...
quindi, volendo inizializzare i sottosistemi video ed audio trasformiamo la chiamata alla function in:
SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);
La function SDL_Init ritorna -1 in caso di errore, 0 altrimenti, pertanto gestiamo il caso di errore stampando a video la notifica dell'errore ed una breve descrizione del problema, descrizione ottenuta grazie alla function SDL_GetError().

Una volta inizializzato correttamente un sottosistema è possibile utilizzarlo, ovviamente non è possibile utilizzare i sottosistemi non inizializzati.
Nel caso fosse necessario inizializzare un sottosistema nel corso del programma, quindi dopo aver già eseguito la SDL_Init, è possibile utilizzare la function SDL_InitSubSystem, che prende come unico parametro le stesse costanti descritte per SDL_Init.

  1. // all'uscita del programma esegui SDL_Quit per risistemare le cose 
  2. atexit(SDL_Quit); 
Utilizzando la function del C atexit andiamo ad eseguire la function SDL_Quit ad ogni uscita corretta dal nostro programma.
SDL_Quit si occupa di disattivare i sottosistemi attivati e di liberare le risorse allocate dalla libreria.

Utilizzando atexit evitiamo di dover richiamare SDL_Quit prima di ogni punto di uscita dal programma.

  1. //dichiariamo un puntatore a una struttura SDL_Surface 
  2. SDL_Surface *screen; 
  3.  
  4. //indichiamo la superficie screen come direttamente collegata al video 
  5. screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,SDL_HWSURFACE|SDL_DOUBLEBUF); 
  6.  
  7. if ( screen == NULL ) 
  8.   printf("Prolemi con il settaggio dello schermo: %s\n", SDL_GetError()); 
  9.   return 1; 
Alla riga 22 dichiariamo un puntatore ad una struttura SDL_Surface, ovvero a una superficie di SDL.
Le superfici sono la componente fondamentale nell'utilizzo di SDL, una superficie è ogni cosa sulla quale si può disegnare e sulla quale possono essere disegnate anche altre superfici.
Anche lo schermo è considerato una superficie, e per renderlo utilizzabile (disegnabile) è necessario assegnarlo ad una superficie precedentemente dichiarata, utilizzando la function SDL_SetVideoMode come mostrato alla riga 25.

La function SDL_SetVideoMode prende in ingresso 4 parametri che sono, nell'ordine:

  1. la componente orizzontale della risoluzione (DIM_H nel nostro caso)
  2. la componente verticale della risoluzione (DIM_V nel nostro caso)
  3. la profondità del colore (0 nel nostro caso)
  4. una o più flag (SDL_HWSURFACE|SDL_DOUBLEBUF nel nostro caso)

ma andiamo ad analizzare questi parametri con maggiore dettaglio:

  1. i primi due parametri rappresentano la risoluzione della nostra superficie, e nel caso in cui ci troviamo ad operare con una finestra, anche la dimensione della finestra.
  2. la profondità del colore è un valore che può variare tra 8,16,24,32, valori che indicano i bit per pixel. Utilizzando il valore 0 si utilizza la risoluzione settata al momento di lanciare il programma nell'ambiente dell'utente.
  3. i flag che si possono utilizzare sono i seguenti:
    • SDL_HWSURFACE: crea la superficie video in modalità hardware (ovviamente più performante)
    • SDL_SWSURFACE: crea la superficie video in modalità software (utilizzata nel caso di problemi con la superficie hw)
    • SDL_ASYNCBLIT: incrementa le prestazioni su SMP, da non usare su mono processori
    • SDL_ANYFORMAT: se non è disponibile la profondità desiderata usa quella più simile
    • SDL_HWPALETTE: dà ad SDL l'accesso esclusivo alle palette, onde evitare problemi con SDL_SetColors
    • SDL_DOUBLEBUF: attiva il buffering doppio, in pratica permette la scrittura di un buffer su una superficie con SDL_Flip, richiede SDL_HWSURFACE
    • SDL_FULLSCREEN: va in modalità schermo intero, se ciò non è possibile usa la risoluzione più grande possibile
    • SDL_OPENGL: utilizza il rendering OpenGl
    • SDL_OPENGLBLIT: utilizza OpenGl e permette alpha channel
    • SDL_RESIZABLE: permette il ridimensionamento della finestra da parte dell'utente
    • SDL_NOFRAME: crea una finestra senza la barra superiore

SDL_SetVideoMode ritorna NULL in caso di errore, pertanto alla riga 27 controlliamo questa eventualità e gestiamo l'errore.

  1. //variabile per il ciclo principale 
  2. int fine=0; 
  3. //memoria dello stato dello schermo (intero o a finestra) 
  4. int full_s=0; 
  5. //flag per SDL_SetVideoMode 
  6. Uint32 flags=SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_FULLSCREEN; 
In queste righe dichiariamo ed iniziliazziamo due variabili (fine e full_s) che ci serviranno nel ciclo principale per determinare se è stata raggiunta la condizione di uscita (fine) e se ci troviamo o meno in modalità schermo intero.

Oltre alle prime due variabili dichiariamo ed inizializziamo la variabile flags che useremo per cambiare la modalità dello schermo.
Da notare che la variabile viene dichiarata con il tipo Uint32 che è un tipo definito all'interno di SDL. Gli altri tipi definiti sono riportati di seguito:

  • Uint8: un unsigned char
  • Uint16: unsigned integer a 16 bit (2 byte)
  • Uint32: unsigned integer a 32 bit (4 byte)
  • Uint64: unsigned integer a 64 bit (8 byte)
  • Sint8: un signed char
  • Sint16: signed integer a 16 bit (2 byte)
  • Sint32: signed integer a 32 bit (4 byte)
  • Sint64: signed integer a 64 bit (8 byte)
  1. //dichiariamo una struttura SDL_Event 
  2. SDL_Event event; 
Alla riga 41 dichiariamo una struttura SDL_Event, che è una struttura generica che contiene al suo interno l'insieme di tutte le sottostrutture associate a tutti i possibili eventi in SDL.
Per SDL, infatti, un evento è una qualsiasi azione eseguita dall'utente, dalla pressione di un tasto allo spostamento del mouse.
Tutti gli eventi che si verificano nel corso del programma, sono inseriti in una coda di eventi dalla quale possono essere prelevati e gestiti da funzioni apposite (come quella che vedremo adesso).
  1. //game loop, ovvero il loop che viene eseguito finche` non si esce 
  2. while(!fine) 
Alla riga 44 facciamo partire il loop principale, il cosiddetto "game loop", che è il ciclo che viene eseguito finchè non è terminato l'utilizzo del programma da parte dell'utente.
  1. //SDL_WaitEvent attende il prossimo evento 
  2. SDL_WaitEvent(&event); 
Alla riga 47 chiamiamo la function SDL_WaitEvent che mette il programma in attesa del prossimo evento gestibile, ciò permette di non sprecare utilizzo di CPU durante lo scarso utilizzo del programma.
Una volta chiamata, SDL_WaitEvent richiede alla coda degli eventi SDL il primo evento disponibile e memorizza le informazioni necessarie nella struttura SDL_Event passata.

Nel nostro caso gestiremo come eventi la pressione dei tasti Esc e spazio ed il click sulla x di uscita dalla finestra.

  1. //premendo la x della finestra col mouse si esce 
  2. if ( event.type == SDL_QUIT ) 
  3.   fine = 1; 
Alla riga 50 riconosciamo il click del mouse sulla x della finestra aperta e gestiamo l'evento ponendo ad 1 il valore della nostra variabile fine, ovvero uscendo dal loop principale.
  1. if ( event.type == SDL_KEYDOWN ) 
Alla riga 53 riconosciamo la pressione di un tasto, siccome dobbiamo riconoscere la pressione di più tasti, dobbiamo addentrarci in un altro livello di if.
  1. //ma si esce anche premendo Esc 
  2. if ( event.key.keysym.sym == SDLK_ESCAPE ) 
  3.   fine = 1; 
il primo tasto che andiamo a riconoscere è Esc, quando l'utente lo preme settiamo a 1 la variabile fine, quindi usciamo.

Per capire bene come è strutturata la struttura SDL_event e le sue sottostrutture utilizzate nel riconoscimento del tasto premuto consiglio di eseguire man SDL_Event, SDL_KeyboardEvent e man SDL_Event, mentre per conoscere i codici associati ai tasti man SDLKey.

  1. if ( event.key.keysym.sym == SDLK_SPACE ) 
  2.   //premendo SPAZIO si passa in modalita` schermo intero 
  3.   if(!full_s) 
  4.     screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  5.     full_s++; 
  6.     flags=SDL_HWSURFACE|SDL_DOUBLEBUF; 
  7.   //premendolo ancora si torna in modalita` finestra 
  8.   else 
  9.     screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  10.     full_s--; 
  11.     flags=SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_FULLSCREEN; 
Alla riga 59 riconosciamo la pressione del tasto spazio ed andiamo a gestire, nelle righe successive, il passaggio di modalità video da finestra a schermo intero e viceversa grazie alla variabile full_s che tiene traccia dello stato attuale.

Per spostarci da una modalità all'altra utilizziamo la function SDL_SetVideoMode che oltre ad associare una superficie allo schermo serve anche a modificare le impostazioni di quest'ultimo nel corso del programma.
La modifica avviene settando la variabile flag, con o senza la costante SDL_FULLSCREEN.

  1.  
  2.   return 0; 
Una volta chiusi correttamente i vari blocchi possiamo uscire correttamente ed andare a compilare il nostro sorgente.

Compilazione

Per compilare il nostro sorgente è necessario utilizzare come segue il gcc:
g++ -Wall -O2 -s -o SDL_primo sorgente.cpp `sdl-config --cflags --libs`
L'opzione -Wall ci segnalerà tutti i warning.

L'opzione -O2 userà l'ottimizzazione di secondo livello del gcc (riduzione delle dimensioni e miglioramento di prestazioni dell'eseguibile).

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 SDL_primo

`sdl-config --cflags --libs` ci darà tutte le opzioni necessarie per includere ed utilizzare la libreria SDL.

Adesso abbiamo finalmente il nostro programma che apre una bella finestra nera!

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. Seconda parte: "Seconda introduzione alla programmazione con SDL".
    http://www.siforge.org/articles/2004/03/22-sdl-intro-2.html
  2. Sorgente dell'esempio.
    http://www.siforge.org/articles/2004/03/sdl-intro/sorgente.cpp (2Kb)
  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. "Cube", Game/3D Engine.
    http://cube.sourceforge.net
  5. "The battle for Wesnorth", gioco di strategia a turni.
    http://www.wesnoth.org
  6. I portings della Loki Games.
    http://www.lokigames.com/products
  7. Sito ufficiale del progetto SDL.
    http://www.libsdl.org
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

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