Guida libreria SDL di Giovanni Di Liberto aka Oligoatria 2006-2007
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
Guida libreria SDL di Giovanni Di Liberto aka Oligoatria 2006-2007 Introduzione Cos’è la libreria SDL: Simple DirectMedia Layer - SDL, è una libreria multipiattaforma per lo sviluppo di applicazioni multimediali. In parole semplici, si tratta di un’utile concentrazione di funzioni riguardanti grafica, suoni, eventi, thread, e altro. Queste si pongono tra l’hardware e l’applicazione, rendendo così possibile la creazione di videogiochi, demo, emulatori, etc., molto performanti. Creata nel 1997 da Sam Lantinga, è stata utilizzata per effettuare il port di alcuni famosi videogiochi dal sistema Windows a Linux. La libreria è open-source; in particolare è rilasciata sotto licenza GNU LGPL (GNU LESSER GENERAL PUBLIC LICENSE – per informazioni rimando al link http://www.gnu.org/licenses/lgpl.html), dunque può essere utilizzata per lo sviluppo di applicazioni commerciali, anche closed-source, a patto che le modifiche apportate al codice sorgente del software stesso vengano rese pubbliche e che sia inserito un linking dinamico alla libreria stessa. Requisiti minimi per affrontare questa guida: • Conoscenza discreta del C++; • M. Visual C++ (solo per la compilazione, senza modifiche, dei sorgenti); • Una release completa SDL, la mia è la versione 1.2.9.0; • Tanta voglia di imparare e sperimentare. Il mio apprendimento: Il mio primo problema, quando ho deciso di provare ad utilizzare questa libreria, è stato trovare una guida che mi facesse procedere passo passo in via pratica (un po’ di teoria l’avevo già studiata e ciò che mi interessava era solo “disegnare un cerchio sullo schermo”). Purtroppo ho trovato solo guide tecniche infinite in inglese, mentre in italiano solo brevissimi tutorial. I sorgenti presenti in rete erano molto complicati (non tutti solo in apparenza) e così il mio apprendimento è stato lento e pieno di lacune. La svolta è stata quando mi sono reso conto che la mia versione 1.2.9.0 aveva un fastidioso bug riguardo la trasparenza delle immagini. Inoltre le guide che stavo studiando non mi fornivano delle spiegazioni esaustive su tutte le possibilità della release. Così mi sono deciso a personalizzare un po’ la libreria. Perché questa guida: Sulla rete sono disponibili così tanti suggerimenti sull’SDL che ho pensato: “copia e incolla, metto un titolo e divento famoso”! A parte gli scherzi, sono
convinto che ci sia molta gente interessata alla grafica, in particolar modo se mirata alla creazione di videogiochi, e credo che l’SDL sia un ottimo punto di partenza per chiunque intenda poi approfondire l’argomento programmazione grafica, sia 2D che 3D. Ovviamente non è sufficiente limitarsi all’utilizzo delle funzioni, ma è necessario capirne il funzionamento per poterle utilizzare al meglio e, se necessario, ampliarle. L’obiettivo di questa guida è fornire i concetti chiave e le relative funzioni SDL per permettere di proseguire da soli, approfondire e implementare le proprie idee, ancora carichi di interesse. Quali possibilità fornisce la libreria SDL?: Ci vengono messe a disposizione molte funzioni, raggruppabili in sette categorie principali: • Video • Gestione delle finestre • Gestione degli eventi • Timer • Multi-threading • Audio • Interfaccia CD-Audio Nota fondamentale: Gli esempi relativi a questa guida sono scaricabili dal sito http://www.pierotofy.it, sezione C++. E’ sufficiente cercare SDL e nella lista saranno presenti i file SDL_Example* (1, 2, 3, …). La guida e gli esempi sono disponibili anche nel sito http://www.oligoatria.it.
Perché usare l’SDL Nella rete sono disponibili molte soluzioni al nostro desiderio di realizzare animazioni, screensaver, giochi o altro: • applicazioni che si concludono in eseguibili, come ad esempio Game Maker o Game Factory; • software come Flash, che necessitano anche di un player per eseguire il progetto creato; • librerie grafiche + proprie capacità di programmatore. Non intendo sminuire in alcun modo il lavoro svolto tramite software dei primi 2 tipi elencati, in quanto ho lavorato per anni con Game Maker e ho avuto qualche esperienza in Flash (ed il suo comodo action script); mi sento però di poter affermare che lavorare utilizzando librerie grafiche, o loro evoluzioni in motore grafico, tramite qualsiasi linguaggio di programmazione è decisamente più complesso e istruttivo, senza contare che fornisce la possibilità di realizzare un software performante, portabile e di qualità. Per capire perché credo sia utile imparare a conoscere ed utilizzare questa libreria è importante distinguerla tra le altre di grande importanza (premetto che parlerò di quello di cui ho esperienza pratica, dunque nulla togliere a quelle che non conosco): • Allegro (da http://www.gameprog.it/?resource=358) “La libreria ha questi pregi: - Grande portabilità - Grande ottimizzazione del codice sotto Dos (motivo per il quale è molto usata per gli emulatori) - Sorgenti disponibili e modificabili a piacere - Gestisce anche periferiche di nuova concezione (force feedback) - Semplifica notevolmente la vita del programmatore di videogiochi/emulatori - Esistono TANTISSIMI pacchetti sviluppati con Allegro per gestire un sacco di cose (grafica 3D con Open-GL, nuove istruzioni dei processori, Font sfumati, funzioni ottimizzate per i tile, Moduli sonorie (.mod, .xm e Mp3) e pensate, anche un piccolo browser HTML!) La libreria ha questi difetti - Il supporto 3D è molto limitato, ma esiste un plug-in per utilizzare le Open-GL con le funzioni 3D di Allegro. - Sotto Windows la libreria è un semplice Wrapper per DirectX, e questo purtroppo mina molto le sue prestazioni (l'ottimizzazione spinta è per la versione DOS). - Le versione più nuova (WIP) non è ancora perfettamente debuggata per tutti i sistemi.” Sono d’accordo con quasi tutta la descrizione, tranne che per il fatto che troppo poco vengono fatti pesare i difetti.. oltre che per il fatto che SDL non è da meno, riguardo gli emulatori! • SDL Rispetto ai pregi di ALLEGRO modificherei solo la semplicità, che non è più notevole ma non eccessiva. Il supporto 3D è decisamente migliore (anche se
sconveniente) e, con un po’ di impegno, è possibile controllare più che pienamente le operazioni anche a basso livello. L’unico aspetto da curare molto è l’ottimizzazione. Si può realizzare di tutto e di più, dal gioco allo screensaver all’emulatore. Ed è proprio un emulatore open-source che mi ha nuovamente sorpreso poco tempo fa; è facilmente reperibile in rete, si chiama DOSBox (ho scaricato la versione 0.70): emula dos 5 in maniera quasi impeccabile, così da poter utilizzare vecchi giochetti – o altro – a prestazioni ottimali. • OpenGL Si tratta di una specifica che definisce API per più linguaggi e sistemi per la realizzazione di applicazioni 2D o – principalmente - 3D. E’ incredibilmente performante e con la possibilità di lavorare bene sia ad alto che a basso livello. E’ la mia passione del momento, ma sono convinto che sia molto importante imparare prima l’SDL e solo in seguito l’OpenGL! E’ utile prendere dimestichezza con la mentalità grafica 2D prima di passare alla terza dimensione, altrimenti si rischia di andare incontro a malintesi, e ad avere grosse lacune che si rischierebbe di non colmare mai. Invece imparare i concetti dell’OpenGL partendo da una base di SDL renderà le cose molto più naturali. • DirectX E’ qualcosa in più dell’OpenGL, ovvero, non si occupa solo dell’aspetto grafico, ma anche di quello dei suoni, della riproduzione video, delle periferiche di input e molto altro. Utilizzando le DirectX si può concludere un’applicazione completa, con l’OpenGL no, in quanto mancherebbero aspetti fondamentali, colmabili attraverso altre librerie o singole funzioni, comunque esterne al progetto OpenGL. Per questo aspetto le DirectX sono simili alla libreria SDL, nel senso che conglobano più aspetti. Problema.. è sviluppato dalla Microsoft, quindi è, diciamo, esclusivo di Windows, a differenza dell’OpenGL che presenta un’architettura nota e può essere sfruttata da più piattaforme. Ritengo fondamentale sottolineare la differenza profonda tra le librerie descritte sopra: OpenGL e Direct3D sono API grafiche che offrono un'astrazione software della GPU o della scheda video; queste hanno sostituito i render software come le API Glide. DirectX, SDL, OpenAL (non citata sopra) sono librerie a basso livello che offrono un accesso diretto anche ad altri componenti hardware (suoni, periferiche di input, …). Quindi si può realizzare un software che sfrutta le OpenGL per la grafica e l’SDL, o le DirectX, per tutto il resto. Per questo è necessaria una conoscenza non solo del codice, ma anche dei concetti base stessi.
Come impostare un progetto SDL con Visual Studio 6.0 Copia delle librerie: Puoi scaricare la libreria SDL precompilata dal sito http://www.libsdl.org/download.php, oppure compilarla da te. Non tratterò quest’ultimo caso dato che lo ritengo un passaggio superfluo – inutile spiegare qualcosa che interessa solo chi ne è già capace. Creare ed impostare un nuovo progetto per l’SDL: • Creare una Win32 Application; • Se non presente (ne dubito) creare un file .cpp principale per il progetto; • Nel menu: “Project|Settings|C/C++ tab|Code Generation|Runtime Library“ imposta il ‘C runtime’ su "Multithreaded DLL" ; • Nel menu: “Project|Settings|C/C++ tab|Preprocessor|Additional include directories“ aggiungi la directory ‘include’ dell’SDL (quella con i .h); • Copia il file ‘SDL.dll’ nella directory del progetto • ʎ Aggiungi i file ‘SDL.lib’ e ‘SDLmain.lib’ al tuo progetto (non è necessario copiarli nella cartella come il .dll) – per aggiungerli, click destro sul progetto, seleziona ‘Add file sto project’; • ʎ In alternativa al precedente e forse necessario al rilascio della release finale, si possono aggiungere i due file nelle opzioni del linker (Project|Properties|Linker|Command Line), aggiungendone i nomi nelle ‘Additional Options’; • Includi ‘SDL.h’ nel file .cpp principale. Attenzione: per poter utilizzare l’eseguibile, d’ora in poi, sarà sempre necessaria la presenza del file ‘SDL.dll’ nella sua stessa directory. Per impostare il progetto con un IDE diverso da Visual Studio non cambia praticamente nulla: ovvio che il menù sarà diverso, ma le operazioni da eseguire sono le stesse (dopo averlo fatto la prima volta vi potreste domandare: “mmm, ma cosa ho fatto?”, se è così andatevi a studiare il funzionamento di un compilatore e gli argomenti correlati).
La prima dialog La prima cosa da imparare è l’inizializzazione video. Una delle comodità di questa libreria è la semplicità delle operazioni necessarie alla creazione di una dialog. Inizializzazione: per utilizzare la libreria è necessario inizializzare i sottoinsiemi interessati (SDL_INIT_VIDEO, SDL_INIT_AUDIO, SDL_INIT_CDROM, SDL_INIT_TIMER) tramite la funzione SDL_Init(FLAGS). Al termine dell’applicazione è necessario chiamare la funzione SDL_Quit() per terminare questi sottosistemi; un metodo alternativo per la chiusura è la funzione atexit(SDL_Quit), solitamente posta subito dopo l’inizializzazione, per chi ha paura di dimenticarsi la chiamata alla funzione di chiusura. In questo caso, però, nel main si dovrà utilizzare l’exit(). Subito dopo possiamo inizializzare la finestra. In un’applicazione multipiattaforma non si può prendere nessuna decisione a priori; è necessario, invece, gestire il software in relazione alle possibilità hardware della macchina e al sistema operativo. A questo proposito la libreria SDL mette a disposizione una serie di funzioni per ottenere informazioni riguardanti i driver video (SDL_GetVideoInfo()) e le potenzialità dell’hardware video (SDL_GetVideoInfo()). Non mi soffermo però su questo; vediamo invece i passi base per la creazione di una dialog: SDL_Surface *screen; screen = SDL_SetVideoMode( x, y, bits, Flags ); if(! screen ) return -1; // gestione del possible errore SDL_LockSurface(screen); SDL_Surface questo sarà d’ora in poi il tipo fondamentale, infatti è questo che contiene le informazioni relative a un’immagine. Non si deve però confondere questo tipo con la dialog, la quale viene “collegata” ad una variabile di questo tipo; infatti molto spesso si caricano immagini non visualizzate completamente o del tutto, cosa che avremo modo di approfondire con gli sprite. SDL_SetVideoMode( int x, int y, int bits, int Flags ) questa è appunto la funzione che alloca la nuova SDL_Surface e crea una nuova Dialog a essa riferita. x – larghezza della superficie y – altezza della superficie bits – numero bit di colori Flags – SDL_OPENGL | SDL_FULLSCREEN | SDL_DOUBLEBUF | ... SDL_LockSurface si rende utile quando si inizia lo sviluppo di funzioni più complesse; il suo scopo è quello di bloccare eventuali operazioni di scrittura non desiderate, che potrebbero causare errori. Piccolo fastidio: per ogni blocco di operazioni di disegno (blit) sarà necessario sbloccare e poi riboccare la superficie, con appunto SDL_UnlockSurface(SDL_Surface*) e SDL_LockSurface(SDL_Surface*)
Se si intende caricare un’immagine la superficie si adatterà al formato della stessa; al contrario è necessario definirne i parametri base se la propria intenzione è di colorare “manualmente” l’immagine, o comunque senza appoggiarsi a file esterni. PrimaryLayer = SDL_CreateRGBSurface( (Flags & SDL_HWSURFACE), x, y, bits, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask ); if(! PrimaryLayer ) return -1; // gestione del possible errore
Creare un ciclo di gioco Per rendere utilizzabile una Dialog, utilizzando il procedimento classico di sfruttamento della libreria SDL, è necessaria la creazione di un ciclo di RunTime (1); all’interno di esso si dovrà svolgere l’intero programma, o una parte. Si tratta di re-implementare dalle basi la programmazione oggi definita ad eventi; in realtà non è nulla di logicamente complicato. Mi spiego con un esempio: SDL_Event event, test_event; Uint8 *keys; for( ; ; ) // ciclo principale di gioco { SDL_Delay(frame_skip); // rallenta il ciclo di RunTime if (SDL_PushEvent(&test_event) == 0) // se si verifica un qualsiasi evento // causato dall’utente/ { SDL_PollEvent(&event); // “cattura l’evento”; concretamente, aggiorna la // rilevazione eventi da periferiche esterne; keys = SDL_GetKeyState(NULL); // aggiorna l’array puntato da keys if ( keys[SDLK_ESCAPE] == SDL_PRESSED ) // esco dal ciclo su pressione // di Escape break; } } SDL_Delay(int) è un comando fondamentale, anche concettualmente (vedi il capitolo “I Timer”: se viene omesso il ciclo di RunTime rischia di essere eseguito con una frequenza tale da “moltiplicare gli eventi” creando effetti sgradevoli. Per essere più chiari faccio un esempio: in un menù premendo SU si sposta un cursore di una casella, se il RunTime è così veloce da fare più di un ciclo mentre premo il tasto potrei ritrovarmi con il cursore sul soffitto deve essere limitato, o meglio gestito attraverso un parametro variabile intero (FrameSkip) impostabile manualmente o automaticamente per avere prestazioni ottimali su ogni PC. Tutto questo senza considerare che, se non viene utilizzata questa funzione, il software assorbe una parte considerevole della CPU, rendendo così instabile il sistema. if (SDL_PushEvent(&test_event) == 0) non è necessario che, ad ogni ciclo, si ripetano tutti i controlli sugli eventi, così, per lasciare un po’ di respiro al processore, è stato “inventato” un controllo generico necessario al funzionamento in RunTime del sistema ad eventi. if (keys[SDLK_ESCAPE] == SDL_PRESSED) gestione dell’evento “tasto ESCAPE premuto”; nel caso riportato se la condizione è verificata si esce dal ciclo. L’evento SDL_PRESSED è verificabile in keys (di tipo Uint8) grazie all’operazione di aggiornamento svolta nella riga precedente ( keys = SDL_GetKeyState(NULL) ).
La lista di tutti gli eventi DA tastiera e mouse è presente nei file SDL_Events.h e SDL_keysym.h; per completezza l’ho riportata nell’appendice1. (1) Nella programmazione visuale (un esempio sono Java, Delphi, Visual Basic, le MFC app. in Visual C++) questo ciclo è presente per definizione, anche se non è immediato il suo riconoscimento nel sorgente – programmazione ad eventi.
Un primo esempio di visualizzazione di un’immagine Una volta impostato il ciclo principale di gioco non è difficile imparare a far visualizzare un’immagine BMP all’utente; cominciamo con la teoria: • Si deve caricare la BMP in una SDL_Surface (*image); • Si deve copiare *image in una determinata posizione (x=20, y=20) nella SDL_Surface principale (*screen). Quest’ultima operazione consiste nel prendere ogni pixel dell’immagine e copiarla in *screen, dunque si tratta di un semplice ciclo annidato (questo verrà approfondito per spiegare la trasparenza). Il codice seguente è fatto per essere inserito appena prima del ciclo di gioco SDL_Surface *image; image = SDL_LoadBMP( “file_name” ); // carica l’immagine nella superficie, la quale // viene allocata automaticamente SDL_Rect dst; // struttura composta da x,y,w (larghezza), h (altezza) // è necessaria in SDL_BlitSurface per indicare ‘rettangolo’ sorgente(src) // o destinazione(dst) dst.x = 20 ; // nel caso di rect di destinazione è sufficiente indicare x e y dst.y = 20 ; SDL_UnlockSurface(screen); // sblocco screen // necessario per poter modificare questa // Surface // SDL_BlitSurface(SDL_Surface*image, SDL_Rect *src, SDL_Surface*screen, SDL_Rect *dst); SDL_BlitSurface(image, NULL, screen, &dst) SDL_LockSurface(screen); // riblocco screen //SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h); SDL_UpdateRect(screen, 0, 0, 0, 0); // aggiorna la superficie visualizzata // i parametri a 0 stanno a significare che l’aggiornamento interessarà tutto lo schermo, // altrimenti qualcosa del tipo 1,1,10,50 avrebbe preso il rettangolo con estremi // A(1,1) B(11,51) Nel caso qui specificato si copia un’immagine BMP caricata da file nella superficie principale a partire dalle coordinate 20,20. Il fatto che il 2° parametro di SDL_BlitSurface sia NULL sta a significare che si copia l’immagine completa; e’ dunque chiaro che basterebbe passargli un SDL_Rect src per copiarne solo la parte desiderata.
Per andare sul concreto, faccio l’esempio degli sprite: in molti tipi di gioco le immagini di un personaggio vengono unite in un unico file; nella fase di disegno se ne prenderà solo una parte alla volta. Ma questo lo vedremo nel capitolo degli sprite.. andiamo per gradi.
Muovi l’immagine nello schermo Arrivati a questo punto, per riuscire a muovere l’immagine interessata è sufficiente aggiungere due punti principali: • La gestione degli eventi da tastiera.. mi spiego: Quando premo la freccia destra, sposto l’immagine a destra, e così via; • L’aggiornamento della Superficie e poi della visualizzazione. Prima di proseguire ricordo che per indicare la posizione dell’immagine abbiamo utilizzato la variabile dst (.x e .y). Qui di seguito riporto il codice del ciclo di gioco modificato ( le parti nuove sono in grassetto ) for( ; ; ) // ciclo principale di gioco { SDL_Delay(frame_skip); // rallenta il ciclo di RunTime if (SDL_PushEvent(&test_event) == 0) // { SDL_PollEvent(&event); /* gestione degli eventi da tastiera */ keys = SDL_GetKeyState(NULL); // aggiorna la situazione attuale dei // tasti premuti o no if ( keys[SDLK_ESCAPE] == SDL_PRESSED ) // esco dal ciclo su // pressione di Escape break; if ( keys[SDLK_UP] == SDL_PRESSED ) dst.y--; if ( keys[SDLK_DOWN] == SDL_PRESSED ) dst.y++; if ( keys[SDLK_LEFT] == SDL_PRESSED ) dst.x--; if ( keys[SDLK_RIGHT] == SDL_PRESSED ) dst.x++; } SDL_UnlockSurface(screen); // sblocco screen SDL_BlitSurface(image, NULL, screen, &dst); SDL_LockSurface(screen); // blocco screen SDL_UpdateRect(screen, 0, 0, 0, 0); }
Reblitting: muovere un’immagine su uno sfondo statico Con l’esigenza di uno sfondo caricato da una BMP sorgono le prime difficoltà. Per poter muovere la nostra immagine su uno sfondo statico è necessario, ad ogni ciclo, disegnare prima lo sfondo, poi l’immagine interessata (ricordo che l’UpdateRect deve essere effettuato una sola volta per ogni ciclo, altrimenti si ottengono brutti effetti visivi (1)). Per ora questo è tutto ciò che ci basta sapere; nel prossimo capitolo analizzeremo il problema del rallentamento dell’applicazione, ma prima è necessario sperimentare. (1) Carichiamo in una nuova superficie la nostra BMP di sfondo; SDL_Surface *sfondo; sfondo = SDL_LoadBMP( "sfondo1.bmp" ); // NEW (2) Prima del blit dell’immagine da muovere, nel ciclo di gioco, ridisegnamo lo sfondo. Nell’esempio: src==NULL: disegniamo l’intero sfondo dst==NULL: lo sfondo verrà disegnato da (0,0) SDL_BlitSurface(sfondo, NULL, screen, NULL); // NEW (1) Le librerie SDL utilizzano automaticamente un algoritmo per sincronizzare l’UpdateRect con l’aggiornamento fisico dello schermo. Se nel ciclo di gioco ci fossero più aggiornamenti visivi della superficie (SDL_UpdateRect o anche SDL_Flip, nel caso del DOUBLE_BUFFERING) si visualizzerebbero effetti visivi che renderebbero l’applicazione normalmente inutilizzabile.
Reblitting: il parziale è più rapido? Uno dei più grandi problemi che ci possono assillare è l’ottimizzazione dell’applicazione, così da poterla rendere funzionale nei PC più lenti. I più attenti, riguardo il capitolo precedente, si saranno chiesti: “Ridisegnando l’intera superficie ad ogni ciclo di gioco non rallenterà di molto l’applicazione?”. Per capire se questo è vero tentiamo di realizzare un Reblitting parziale, cioè… Sappiamo che l’UpdateRect è già ottimizzato, in quanto aggiorna visivamente solo la superficie modificata con il BlitSurface, quindi perché non migliorare la nostra tecnica di Blitting? L’idea è questa: dobbiamo spostare l’immagine da un’area A ad una B; dopo aver calcolato l’area B, copiamo in una SDL_Rect temporanea A e, nel ciclo successivo prima si ridisegnerà lo sfondo solo nell’area A, poi si procederà con il Blitting dell’immagine in B (chiaramente tutto questo si farà solo se (A != B)). Per sfruttare questo concetto anche con più oggetti dinamici è sufficiente creare una lista (preferibilmente puntata) di SDL_Rect che, ad ogni ciclo, conterranno le aree degli oggetti che si stanno spostando, basterà dunque ridisegnare lo sfondo solo dove c’è movimento. La domanda più ovvia alla quale voglio dare una risposta è: “Dov’è il problema? Implementiamo questo concetto e tutto sarà più veloce”. Non sono d’accordo perché penso al caso più pessimistico, e cioè ad un’applicazione con un numero di oggetti dinamici tale da rendere le operazioni di copia delle SDL_Rect temporanee complesse a tal punto da rallentare l’applicazione. E’ evidente che è più rapido ridisegnare il meno possibile dello sfondo, ma si deve capire che la funzione SDL_BlitSurface è relativamente lenta, quindi è meglio richiamarla il meno volte possibile: è più efficace quindi disegnare una volta l’intero sfondo che molte volte piccoli pezzetti di esso. A sommarsi a questo c’è il tempo impiegato per la gestione delle aree temporanee, anche se si tratta di un problema minimo in confronto al richiamare più volte la funzione BlitSurface. Ulteriore difficoltà da affrontare: se si intende utilizzare una mappa scorrevole e più sfondi, si devono gestire contemporaneamente il Reblitting e lo scroll, il che non è cosa da poco. Nelle applicazioni più avanzate viene calcolato un limite, valicato il quale questa tecnica diventa svantaggiosa; quindi utilizzo del metodo più vantaggioso. Negli esempi preferisco non trattare questo problema in quanto intendo mantenere una semplicità di codice tale da renderne l’analisi il più facile possibile.
Primo approccio alla trasparenza Con gli aspetti visti finora si potrebbe realizzare un menù, o al massimo un tetris, comunque di scarsa qualità. Le nostre possibilità cambiano radicalmente con l’utilizzo della trasparenza, tecnica tramite la quale è possibile stampare a video anche figure rotonde o irregolari. Ogni oggetto SDL_Surface può avere un colore di trasparenza diverso e, nel nostro esempio, utilizzando un’immagine a 8 bit ,ovvero una palette di 256 colori, è sufficiente utilizzare la funzione SDL_SetColorKey per impostare il colore di trasparenza. La logica è questa: quando disegno in *screen l’immagine interessata copio solo i pixel diversi da quello di trasparenza impostato tramite la funzione così richiamata: if ( image->format->palette ) { SDL_SetColorKey(image, (SDL_SRCCOLORKEY|SDL_RLEACCEL), *(Uint8 *)image->pixels); } Questa riga di codice può essere inserita subito dopo image = SDL_LoadBMP( "file_name.bmp" ). Il prototipo della funzione è il seguente: int SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key); Ho optato per un’immagine ad 8 bit perché a profondità di colore maggiore si possono riscontrare dei problemi. In particolare con questa versione delle librerie si visualizza un brutto effetto visivo che “mi ha perseguitato” per parecchio tempo. Per ovviarlo, invece di cambiare versione della libreria, ho pensato di implementare una mia personale versione di blitting trasparente. Questa mi si è poi rivelata fondamentale per la realizzazione di un’applicazione definibile carina, anche perché fornisce altre possibilità, come ad esempio la rotazione dell’immagine e l’ingrandimento. Purtroppo non è affatto veloce, quindi ho dovuto cercare altre soluzioni. Chi intende provare ad approfondire può passare all’APPENDICE 2, dove spiego logica e contenuti della mia implementazione. Per chi abbia intenzione di utilizzare le funzioni predefinite può facilmente reperirle nella documentazione ufficiale, nulla di difficile se si possiede la versione giusta della libreria; quindi potete passare direttamente al capitolo successivo. Qui di seguito riporto come utilizzare la funzione SDL_SetColorKey anche con immagini di profondità di colore maggiore a 8 bit, dunque con i metodi true-color: SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image ->format, 0xff, 0x00, 0xff)); (0xff, 0x00, 0xff) – indicano il colore RGB (red==255, green==0, blue==255), ovvero violetto, spesso come colore di default.
Blit parziale dell’oggetto e introduzione agli sprite Dopo aver visto come disegnare immagini utilizzando la trasparenza con colore chiave, non è difficile avvicinarsi alla mentalità degli sprite. Prima, però, è necessario vedere come disegnare porzioni di un’immagine sulla superficie. Blit parziale: Come per indicare le coordinate dello schermo dove disegnare la BMP, si utilizza una variabile di tipo SDL_Rect: SDL_Rect src; src.x = 0; src.y = 0; src.w = 23; src.h = 10; Eseguendo poi la funzione SDL_BlitSurface(image, &src, screen, &dst ) si comunica alla libreria di disegnare in screen dst.x, dst.y, la porzione dell’immagine image contenuta nel rettangolo di larghezza 23 pixel ed altezza 10. Cos’è uno sprite: Per definizione uno sprite è un’immagine bidimensionale che può essere spostata rispetto ad uno sfondo. Nei primi videogame questo veniva gestito direttamente via hardware, per ottenere quindi l’effetto di trasparenza. Oggi per sprite intendiamo un’immagine 2D, generalmente formata da n frame; questi vengono visualizzati uno alla volta in successione, in maniera da essere sovrapposti e da dare l’impressione di una continuità, di un movimento, fluido il più possibile. Queste immagini sono caratteristiche di varie tipologie di applicazioni, dai giochi di ruolo 2D, dove sono utilizzati praticamente per ogni cosa, ad applicazioni con gestione del testo, dove gli stessi font hanno uno sprite per ogni stile di caratteri, o charset. Per essere eloquenti al massimo, è lo stesso sistema adottato per le gif animate, sostanzialmente. Come applicare il concetto: Pensando come sviluppare un codice che realizzi questo concetto, il più facile che mi viene alla mente è il seguente (vedi SDL_Example4): // Prima del ciclo di loop /* Variabili riguardanti la gestione degli sprites */ unsigned int frame_attuale=0; SDL_Rect src; src.x = (LARGHEZZA_IMMAGINE)*(frame_attuale); src.y = 0 ; src.w = LARGHEZZA_IMMAGINE; src.h = ALTEZZA_IMMAGINE; // Nel ciclo di loop, prima di disegnare /* Cambio frame */ if (++frame_attuale >= NUMERO_FRAMES) frame_attuale=0; src.x = (LARGHEZZA_IMMAGINE)*(frame_attuale);
/* Disegno la frame */ SDL_BlitSurface_transparent(image, &src, screen, &dst, 0, 0, 0, 1, 0, 0);
Una prima implementazione di motore fisico Ho pensato di includere in questa guida anche un piccolo esempio che possa far capire cosa si può sviluppare utilizzando questa libreria. Ora che abbiamo la padronanza della tecnica degli sprite è necessario almeno dare un’occhiata al movimento, comandato dall’utente tramite tastiera, sullo stile platform; quindi il classico omino che subisce la forza di gravità. Prima di tutto dobbiamo dichiarare due nuove variabili: /* Gravità */ int gravita=0; /* Direzione */ bool sinistra=false; Poi, nel ciclo di loop togliamo l’effetto in seguito alla pressione della freccia verso il basso, e sostituiamo a quella verso l’alto: if ( (keys[SDLK_UP] == SDL_PRESSED) && ((dst.y+ALTEZZA_IMMAGINE)>=y) ) { gravita=-20; } L’intento è di far saltare il personaggio, ora vedremo cosa abbiamo sostanzialmente fatto con questi comandi. Prima di disegnare, aggiungiamo il codice seguente: /* Gravità */ dst.y+=gravita++; if ((dst.y+ALTEZZA_IMMAGINE)>=y) { gravita=0; dst.y=y-ALTEZZA_IMMAGINE; } In questo modo la posizione dell’immagine si sposterà verso il basso di un valore crescente.. nel mondo reale la crescita è esponenziale, nei platform questa non sempre lo è; la tipologia di gioco influenza la forza della gravità. Come soglia di collisione prendiamo i piedi del personaggio, quindi, essendo che le coordinate partono dall’angolo in alto a sinistra dello schermo (0, 0), posizione y attuale + altezza immagine. A questo punto si può capire che per avere un salto è sufficiente, alla pressione del tasto interessato, assegnare alla gravità un valore negativo; è chiaro che tanto più grande sarà questo numero (in valore assoluto), tanto maggiore sarà la forza del salto.
L’importanza della console Spesso mi è capitato di perdere molto tempo nel cercare un problema la cui causa sembrava introvabile. Intoppi del genere sono alla base del mondo della programmazione, in particolare web. I linguaggi compilati, come il C++, hanno un fantastico sistema di debug, il quale facilita sicuramente la ricerca degli errori. Sarebbe però sicuramente meno frustrante se, al verificarsi di un problema, la sua causa fosse arginata all’interno di poche righe di codice; queste, certo, sono tanto più ridotte tanto è maggiore la nostra bravura, esperienza ed intuizione. Non basta, però! Il mio consiglio è di sfruttare al meglio la console predefinita. Con questo intendo dire che dobbiamo fare un po’ di fatica in più e gestire gli errori tramite try/catch, ed in caso di eccezioni segnalarle con un printf, o cout, nella console. Si possono classificare i tipi di errori, se il software diventa complesso. Oltre alla classificazione e segnalazione degli errori si può utilizzare la console per segnalare gli eventi, l’accesso a funzioni o condizioni, lo stato di thread, connessioni, suoni. E’ molto importante decidere se impostare un progetto sotto questo profilo fin dall’inizio dello sviluppo, altrimenti poi, quando il codice raggiunge uno sviluppo avanzato, risulta molto faticoso scegliere i punti giusti dove inserire i controlli, anche perché capita che non ci si ricorda più come è fatta una funzione, quindi si perde ulteriore tempo. Chiaro che, se intenzione dell’autore, la console verrà nascosta nelle release stabili, o ridotta a funzione di indicatore di caricamento.
Audio La libreria SDL gestisce bene anche l’aspetto riguardante i suoni. Nel dettaglio, la libreria ci mette a disposizione una serie di funzioni che rendono facile la riproduzione di file audio: SDL_LoadWav(), SDL_OpenAudio(), SDL_PauseAudio(). SDL chiama automaticamente una funzione di callback che copia i campioni dalla memoria di sistema al buffer della scheda audio. Questa funzione dev’essere creata dall’utente e il suo indirizzo, assieme ai valori della frequenza di campionamento e della risoluzione dei campioni, deve essere specificato nella struttura SDL_AudioSpec: typedef struct{ int freq; Uint16 format; Uint8 channels; Uint8 silence; Uint16 samples; Uint32 size; void (*callback)(void *userdata, Uint8 *stream, int len); void *userdata; } SDL_AudioSpec; freq - Frequenza di campionamento Il numero di campioni copiati nel buffer della scheda audio al secondo. I valori più comuni sono 11025, 22050 e 44100. Più il valore è alto, migliore è il suono. format - Formato audio (1) Specifica la dimensione ed il tipo di ogni campione: AUDIO_U8 – Campioni di tipo Unsigned 8-bit AUDIO_S8 – Signed 8-bit AUDIO_U16 o AUDIO_U16LSB – Unsigned 16-bit little-endian AUDIO_S16 o AUDIO_S16LSB – Signed 16-bit little-endian AUDIO_U16MSB – Unsigned 16-bit big-endian AUDIO_S16MSB – Signed 16-bit big-endian AUDIO_U16SYS – Sia AUDIO_U16LSB che AUDIO_U16MSB dipendenti dal sistema endianness specifico AUDIO_S16SYS – Sia AUDIO_S16LSB che AUDIO_S16MSB dipendenti dal sistema endianness specifico. channels – Numero di canali: 1 mono, 2 stereo silence – Valore di silenzio del buffer audio (calcolato automaticamente) samples – Dimensione del buffer audio, in campioni size – Dimensione, in byte, del buffer audio (calcolato automaticamente) callback(..) – Funzone di callback per copiare i campioni nel buffer della scheda audio userdata – Puntatore ai parametri da passare alla funzione di callback.
Riporto qui di seguito un esempio di funzione di callback, presa da http://www.libsdl.org: #define NUM_SOUNDS 2 struct sample { Uint8 *data; Uint32 dpos; Uint32 dlen; } sounds[NUM_SOUNDS]; void mixaudio(void *unused, Uint8 *stream, int len) { int i; Uint32 amount; for ( i=0; i len ) { amount = len; } SDL_MixAudio(stream, &sounds[i].data[sounds[i].dpos], amount, SDL_MIX_MAXVOLUME); sounds[i].dpos += amount; } } Questo invece è un esempio di struttura SDL_AudioSpec. SDL_AudioSpec fmt; /* Setta l'audio a 16-bit stereo 22Khz */ fmt.freq = 22050; fmt.format = AUDIO_S16; fmt.channels = 2; fmt.samples = 512; /* Un buon valore per i giochi */ fmt.callback = mixaudio; fmt.userdata = NULL; SDL permette anche la gestione dell’audio da CD-ROM. Riporto qui di seguito la semplice funzione ricavata dal sito http://www.libsdl.org: void PlayTrack(SDL_CD *cdrom, int track) { if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) { SDL_CDPlayTracks(cdrom, track, 0, track+1, 0); } while ( SDL_CDStatus(cdrom) == CD_PLAYING ) { SDL_Delay(1000); } } Si spiega da sola. (1) Big, Middle e Little Endian sono le tre tipologie alternative alla memorizzazione di dati con dimensione base superiore al byte. Per endianness si intende generalmente l’ordine dei byte.
Applicazioni Multi-threaded A volte capita di dover affrontare problemi che trovano soluzione solo con operazioni svolte in parallelo; purtroppo la gestione di 2 processi pesa parecchio al sistema, quindi sono stati creati i thread, definibili come “processi leggeri”; ogni processo può avere più thread, i quali condividono lo stesso spazio di indirizzamento. La libreria SDL ci mette a disposizione funzioni per creare thread e per poterli gestire. Prima di vedere quali sono queste funzioni è necessario sapere a cosa è necessario prestare attenzione per evitare effetti indesiderati: • Prima di tutto pensa a tutte le possibili soluzioni alternative all’utilizzo di un nuovo thread; • Se non c’è altro modo, o se questo è il più efficace, è bene evitare l’utilizzo di variabili globali su thread differenti; • Non terminare i threads, utilizza invece un flag e fai in modo che si chiuda autonomamente; questo sia per evitare errori di esecuzione che il possibile memory- leak; • E’ preferibile non fare chiamate SDL Video/Event su più threads. SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data); Questa funzione permette la creazione di un nuovo thread, avviando la funzione fn con parametri data. void SDL_WaitThread(SDL_Thread *thread, int *status); Aspetta che un thread finisca; status è il valore restituito dalla funzione main del thread terminato. void SDL_KillThread(SDL_Thread *thread); Termina un determinato thread. SDL permette anche la risoluzione del problema delle variabili condivise tramite mutex, ovvero il procedimento di sincronizzazione che impedisce l’accesso contemporaneo di più threads alla stessa risorsa, evitando così errori parecchio fastidiosi. La conoscenza del loro utilizzo và oltre lo scopo di questa guida. Un esempio di operazione da svolgere in multi-threading è la gestione della comunicazione di rete; in questo caso, a meno che non si intenda bloccare il software in attesa di un determinato pacchetto, o fino alla scadenza di un timeout, risulta fondamentale l’utilizzo di un secondo thread che gestisca in maniera autonoma la connessione.
I Timer Tutte le operazioni svolte da un computer sono intervallate da un determinato tempo, denominato clock. Questo dev’essere necessariamente gestito dal programmatore in questo genere di applicazioni, ovvero dove si mette mano direttamente al codice del ciclo di loop. Il non affrontare questo argomento compromette sensibilmente l’esecuzione del software, il quale sfrutterebbe tutta la memoria concessagli, rischiando di mandare in crash il sistema, oltre che l’applicazione stessa. La libreria SDL mette a disposizione delle funzioni per la gestione del tempo, una delle quali è SDL_GetTicks(), che restituisce il tempo trascorso dall’avvio dell’applicazione (in ms); anzi, per essere precisi, questo tempo è riferito all’inizializzazione della libreria. L’altra funzione fondamentale è SDL_Delay(), che attende il tempo, in ms, passatogli come parametro. Questa funzione è particolarmente precisa, se la rapportiamo ad altre disponibili nelle librerie standard. Un’altra possibilità fornitaci dalla libreria è quella di creare timer e collegarci funzioni; si tratta quindi del classico evento OnTimer disponibile in quasi tutti i linguaggi visuali. ATTENZIONE: SDL mette a disposizione funzioni comode e molto utili, ma è compito del programmatore gestire in maniera efficace quello che nelle applicazioni grafiche sono chiamati FPS (Frame per second). Non si tratta affatto di un argomento banale, anzi, ci si sbatte la testa più volte, nello sviluppo di applicazioni grafiche.
SDL sopra OpenGL SDL mette a disposizione una serie di funzioni per l’accesso al buffer video, quindi permette un approccio a basso livello. Tuttavia non ci viene fornita alcuna funzione per il disegno di primitive. La caratteristica che ci può far dimenticare questa piccola mancanza è la facile integrazione con la libreria OpenGL (per dettagli a riguardo rimando al capitolo “Perché usare l’SDL”). Quest’ultima ha la caratteristica di non interfacciarsi direttamente con il S.O., bensì attraverso un contesto grafico in cui poter disegnare la scena. E’ qui che entra in campo l’SDL, mettendo a disposizione questo contesto attraverso una serie di funzioni: SDL_GL_SetAttribute() – consente l’impostazione degli attributi del contesto grafico (bit per colore, flag per il DOUBLE_BUFFERING o per il DEPTH_TEST); In SDL_SetVideoMode() è necessario passare come flag SDL_OPENGL; SDL_GL_SwapBuffers() – Effettua lo scambio tra back-buffer e frame-buffer; SDL_GL_GetProcAddress() – Restituisce l’indirizzo di una funzione contenuta in un’estensione OpenGL. Ci sarebbe molto da dire in questo capitolo, ma andrei al di fuori dell’argomento SDL. C’è già abbastanza da capire in questa libreria. Penso che solo la convinzione di essere interessati ad approfondire possa portare a qualche risultato; altrimenti lo studio si riduce a semplice lettura, di scarsa utilità, oltretutto. Il dare un’occhiata può servire solo a spaventare e ad allontanare la persona da un argomento del quale è meglio pensare più ai concetti che al codice.
Appendice1 – lista degli eventi da tastiera e mouse L’utilizzo è il seguente: Per la tastiera: Uint8 *keys; keys = SDL_GetKeyState(NULL); // aggiorna il vettore keys if ( keys[SDLK_EVENTO] == SDL_PRESSED ) Per il mouse: Uint8 mouse; mouse = SDL_GetMouseState(mx,my); // mx e my sono puntatori a int // in essi saranno salvate le coordinate del puntatore del mouse // possono essere NULL if ( mouse == SDL_EVENTO ) EVENTI TASTIERA /* The keyboard syms have been cleverly chosen to map to ASCII */ SDLK_UNKNOWN = 0, SDLK_FIRST = 0, SDLK_BACKSPACE = 8, SDLK_TAB = 9, SDLK_CLEAR = 12, SDLK_RETURN = 13, SDLK_PAUSE = 19, SDLK_ESCAPE = 27, SDLK_SPACE = 32, SDLK_EXCLAIM = 33, SDLK_QUOTEDBL = 34, SDLK_HASH = 35, SDLK_DOLLAR = 36, SDLK_AMPERSAND = 38, SDLK_QUOTE = 39, SDLK_LEFTPAREN = 40, SDLK_RIGHTPAREN = 41, SDLK_ASTERISK = 42, SDLK_PLUS = 43, SDLK_COMMA = 44, SDLK_MINUS = 45, SDLK_PERIOD = 46, SDLK_SLASH = 47, SDLK_0 = 48, SDLK_1 = 49, SDLK_2 = 50, SDLK_3 = 51,
SDLK_4 = 52, SDLK_5 = 53, SDLK_6 = 54, SDLK_7 = 55, SDLK_8 = 56, SDLK_9 = 57, SDLK_COLON = 58, SDLK_SEMICOLON = 59, SDLK_LESS = 60, SDLK_EQUALS = 61, SDLK_GREATER = 62, SDLK_QUESTION = 63, SDLK_AT = 64, /* Skip uppercase letters */ SDLK_LEFTBRACKET = 91, SDLK_BACKSLASH = 92, SDLK_RIGHTBRACKET= 93, SDLK_CARET = 94, SDLK_UNDERSCORE = 95, SDLK_BACKQUOTE = 96, SDLK_a = 97, SDLK_b = 98, SDLK_c = 99, SDLK_d = 100, SDLK_e = 101, SDLK_f = 102, SDLK_g = 103, SDLK_h = 104, SDLK_i = 105, SDLK_j = 106, SDLK_k = 107, SDLK_l = 108, SDLK_m = 109, SDLK_n = 110, SDLK_o = 111, SDLK_p = 112, SDLK_q = 113, SDLK_r = 114, SDLK_s = 115, SDLK_t = 116, SDLK_u = 117, SDLK_v = 118, SDLK_w = 119, SDLK_x = 120, SDLK_y = 121,
SDLK_z = 122, SDLK_DELETE = 127, /* End of ASCII mapped keysyms */ /* International keyboard syms */ SDLK_WORLD_0 = 160, /* 0xA0 */ SDLK_WORLD_1 = 161, SDLK_WORLD_2 = 162, SDLK_WORLD_3 = 163, SDLK_WORLD_4 = 164, SDLK_WORLD_5 = 165, SDLK_WORLD_6 = 166, SDLK_WORLD_7 = 167, SDLK_WORLD_8 = 168, SDLK_WORLD_9 = 169, SDLK_WORLD_10 = 170, SDLK_WORLD_11 = 171, SDLK_WORLD_12 = 172, SDLK_WORLD_13 = 173, SDLK_WORLD_14 = 174, SDLK_WORLD_15 = 175, SDLK_WORLD_16 = 176, SDLK_WORLD_17 = 177, SDLK_WORLD_18 = 178, SDLK_WORLD_19 = 179, SDLK_WORLD_20 = 180, SDLK_WORLD_21 = 181, SDLK_WORLD_22 = 182, SDLK_WORLD_23 = 183, SDLK_WORLD_24 = 184, SDLK_WORLD_25 = 185, SDLK_WORLD_26 = 186, SDLK_WORLD_27 = 187, SDLK_WORLD_28 = 188, SDLK_WORLD_29 = 189, SDLK_WORLD_30 = 190, SDLK_WORLD_31 = 191, SDLK_WORLD_32 = 192, SDLK_WORLD_33 = 193, SDLK_WORLD_34 = 194, SDLK_WORLD_35 = 195, SDLK_WORLD_36 = 196, SDLK_WORLD_37 = 197, SDLK_WORLD_38 = 198, SDLK_WORLD_39 = 199, SDLK_WORLD_40 = 200, SDLK_WORLD_41 = 201,
SDLK_WORLD_42 = 202, SDLK_WORLD_43 = 203, SDLK_WORLD_44 = 204, SDLK_WORLD_45 = 205, SDLK_WORLD_46 = 206, SDLK_WORLD_47 = 207, SDLK_WORLD_48 = 208, SDLK_WORLD_49 = 209, SDLK_WORLD_50 = 210, SDLK_WORLD_51 = 211, SDLK_WORLD_52 = 212, SDLK_WORLD_53 = 213, SDLK_WORLD_54 = 214, SDLK_WORLD_55 = 215, SDLK_WORLD_56 = 216, SDLK_WORLD_57 = 217, SDLK_WORLD_58 = 218, SDLK_WORLD_59 = 219, SDLK_WORLD_60 = 220, SDLK_WORLD_61 = 221, SDLK_WORLD_62 = 222, SDLK_WORLD_63 = 223, SDLK_WORLD_64 = 224, SDLK_WORLD_65 = 225, SDLK_WORLD_66 = 226, SDLK_WORLD_67 = 227, SDLK_WORLD_68 = 228, SDLK_WORLD_69 = 229, SDLK_WORLD_70 = 230, SDLK_WORLD_71 = 231, SDLK_WORLD_72 = 232, SDLK_WORLD_73 = 233, SDLK_WORLD_74 = 234, SDLK_WORLD_75 = 235, SDLK_WORLD_76 = 236, SDLK_WORLD_77 = 237, SDLK_WORLD_78 = 238, SDLK_WORLD_79 = 239, SDLK_WORLD_80 = 240, SDLK_WORLD_81 = 241, SDLK_WORLD_82 = 242, SDLK_WORLD_83 = 243, SDLK_WORLD_84 = 244, SDLK_WORLD_85 = 245, SDLK_WORLD_86 = 246, SDLK_WORLD_87 = 247, SDLK_WORLD_88 = 248,
SDLK_WORLD_89 = 249, SDLK_WORLD_90 = 250, SDLK_WORLD_91 = 251, SDLK_WORLD_92 = 252, SDLK_WORLD_93 = 253, SDLK_WORLD_94 = 254, SDLK_WORLD_95 = 255, /* 0xFF */ /* Numeric keypad */ SDLK_KP0 = 256, SDLK_KP1 = 257, SDLK_KP2 = 258, SDLK_KP3 = 259, SDLK_KP4 = 260, SDLK_KP5 = 261, SDLK_KP6 = 262, SDLK_KP7 = 263, SDLK_KP8 = 264, SDLK_KP9 = 265, SDLK_KP_PERIOD = 266, SDLK_KP_DIVIDE = 267, SDLK_KP_MULTIPLY = 268, SDLK_KP_MINUS = 269, SDLK_KP_PLUS = 270, SDLK_KP_ENTER = 271, SDLK_KP_EQUALS = 272, /* Arrows + Home/End pad */ SDLK_UP = 273, SDLK_DOWN = 274, SDLK_RIGHT = 275, SDLK_LEFT = 276, SDLK_INSERT = 277, SDLK_HOME = 278, SDLK_END = 279, SDLK_PAGEUP = 280, SDLK_PAGEDOWN = 281, /* Function keys */ SDLK_F1 = 282, SDLK_F2 = 283, SDLK_F3 = 284, SDLK_F4 = 285, SDLK_F5 = 286, SDLK_F6 = 287, SDLK_F7 = 288, SDLK_F8 = 289,
SDLK_F9 = 290, SDLK_F10 = 291, SDLK_F11 = 292, SDLK_F12 = 293, SDLK_F13 = 294, SDLK_F14 = 295, SDLK_F15 = 296, /* Key state modifier keys */ SDLK_NUMLOCK = 300, SDLK_CAPSLOCK = 301, SDLK_SCROLLOCK = 302, SDLK_RSHIFT = 303, SDLK_LSHIFT = 304, SDLK_RCTRL = 305, SDLK_LCTRL = 306, SDLK_RALT = 307, SDLK_LALT = 308, SDLK_RMETA = 309, SDLK_LMETA = 310, SDLK_LSUPER = 311, /* Left "Windows" key */ SDLK_RSUPER = 312, /* Right "Windows" key */ SDLK_MODE = 313, /* "Alt Gr" key */ SDLK_COMPOSE = 314, /* Multi-key compose key */ /* Miscellaneous function keys */ SDLK_HELP = 315, SDLK_PRINT = 316, SDLK_SYSREQ = 317, SDLK_BREAK = 318, SDLK_MENU = 319, SDLK_POWER = 320, /* Power Macintosh power key */ SDLK_EURO = 321, /* Some european keyboards */ SDLK_UNDO = 322, /* Atari keyboard has Undo */ EVENTI MOUSE #define SDL_BUTTON(X) (SDL_PRESSED
Appendice2 – funzioni di blitting personalizzate Le seguenti sono due funzioni fondamentali in SDL_BlitSurface_transparent; il loro sorgente è in SDL_Example3. La fonte è http://docs.huihoo.com/sdl/1.2/guidevideo.html - SDL Library Documentation Uint32 getpixel(SDL_Surface *surface, int x, int y) ; /* * Imposta il pixel di coordinate (x, y) * ATTENZIONE: la superficie deve essere bloccata prima di eseguire questa funzione */ void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel) ; Utilizzando getpixel e putpixel ho scritto SDL_BlitSurface_transparent, una funzione che mi era necessaria per la realizzazione di un motore di rendering che mi permettesse di disegnare immagini utilizzando la trasparenza. Per farlo è stato sufficiente disegnare l’immagine tralasciando i pixel di colore uguale a quello di (x, y)==(0, 0). Oltre a questo la funzione permette di gestire altri aspetti, come il flip dell’immagine, la rotazione, la scala. int SDL_BlitSurface_transparent(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect, int ang_rotaz, int flip, int decentramento_y, int scala, int dec_scala, int fantasma) /* *src -> source - sorgente *srcrect -> parte(rettangolo) della sorgente da copiare *dst -> destinazione *dstrect -> dove copio nella superficie di dst ang_rotaz -> 0 - 360 rotazione immagine *** DA IMPLEMENTARE *** flip -> 0 none 1 specchio orizzontalmente 2 specchio verticalmente scala -> 1 normale -2 metà 2 doppio decimale -> come inserire 3.1: scala.decimale concretamente disegno ogni volta che l'accumulo dei decimali mi dà 1 .. fantasma -> 0 normale 1 fantasma (immagine "tirata su") 2 + immagine sempre più tirata verso l'alto */ { // SDL_Surface sono già sbloccate
int i,j,decimale=0,aggiunta; int xs, ys, xd, yd, ystmp, ydtmp; int condizione_x, condizione_y; Uint32 color, trpcolor; trpcolor=getpixel(src, 0, 0); if ( (srcrect==NULL) && (dstrect==NULL) ) { xs=0; ystmp=0; xd=0; ydtmp=0+decentramento_y; condizione_x=src->w; condizione_y=src->h; if (flip==1) // flip orizzontale xd+=src->w; } else if (srcrect==NULL) { xs=0; ystmp=0; xd=dstrect->x; ydtmp=dstrect->y+decentramento_y; condizione_x=src->w; condizione_y=src->h; if (flip==1) // flip orizzontale xd+=src->w; } else if (dstrect==NULL) { xs=srcrect->x; ystmp=srcrect->y; xd=0; ydtmp=0+decentramento_y; condizione_x=(srcrect->w + srcrect->x); condizione_y=(srcrect->h + srcrect->y); if (flip==1) // flip orizzontale xd+=srcrect->w; } else {
xs=srcrect->x; ystmp=srcrect->y; xd=dstrect->x; ydtmp=dstrect->y+decentramento_y; condizione_x=(srcrect->w + srcrect->x); condizione_y=(srcrect->h + srcrect->y); if (flip==1) // flip orizzontale xd+=srcrect->w; } for (; xs
else xd+=scala; } return 0; }
Bibliografia Using SDL with Microsoft Visual C++ 5,6 and 7 – di Sam Lantinga, modificato da Lion Kimbro and revisionato da James Turk; SDL Library Documentation - http://docs.huihoo.com/sdl/1.2/guidevideo.html; http://www.gameprog.it; http://www.libsdl.org, sito ufficiale della libreria; http://www.gnu.org/licenses/lgpl.html, citato relativamente alla licenza della libreria SDL.
Puoi anche leggere