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 possedeste 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
Per questa guida ho realizzato un programma che grazie ad alcune primitive grafiche,
ovvero il disegno di punto, linea, croce e quadrato, traccia una griglia sullo schermo, e
gestisce poi gli eventi del mouse all'interno di questa griglia, andando cioè a riconoscere
la casella (tile) sulla quale si trova il puntatore.
Al passaggio del puntatore la casella attraversata modifica il suo colore ed in seguito ad un
click sinistro viene disegnata una crocetta gialla nel centro della casella, questo per
verificare l'esatto riconoscimento della casella.
La tecnica di riconoscimento della posizione del mouse può essere tranquillamente adottata in
ogni gioco 2D che utilizza il sistema di coordinate cartesiane.
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 nei prossimi paragrafi.
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#define DIM_H 800
#define DIM_V 600
#define BUTTON_SX 1
#define BUTTON_DX 3
//strutture usate
typedef struct point
{
int x;
int y;
} point;
typedef struct RGB
{
Uint8 R;
Uint8 G;
Uint8 B;
} RGB_Color;
//function grafiche
void draw_pixel(SDL_Surface *surface, int x, int y, RGB_Color s_color);
void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color);
void draw_line(SDL_Surface *surface, point p0, point p1, RGB_Color color);
void draw_square(SDL_Surface *surface, point p, int lato, RGB_Color color);
int main()
{
//superfici utilizzate
SDL_Surface *screen;
SDL_Surface *screen_backup;
SDL_Surface *cursor;
SDL_Surface *bg_cursor;
SDL_Surface *temp;
// si inizializza il sistema video
if( SDL_Init(SDL_INIT_VIDEO) <0 )
{
// SDL_GetError() ritorna una descrizione dell'errore
printf("Errore init SDL: %s\n", SDL_GetError());
return 1;
}
// all'uscita del programma esegui SDL_Quit per risistemare le cose
atexit(SDL_Quit);
//dichiariamo un puntatore ad una superficie ed un int a 32 bit per i flag
da assegnarle
Uint32 flags = SDL_HWSURFACE|SDL_DOUBLEBUF;
// per rendere disegnabile la Superficie bisogna utilizzare SDL_SetVideoMode
if(!( screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags) ))
{
printf("Problemi con il settaggio dello schermo: %s\n", SDL_GetError());
return 1;
}
//nascondiamo il cursore della finestra
SDL_ShowCursor(0);
//carica img cursore su superficie temporanea
if((temp = IMG_Load("mirino.png"))==NULL)
{
printf("Errore caricamento mirino: %s\n",IMG_GetError());
return 1;
}
//Adatta l'img al display attuale e l'assegna ad una superficie
cursor = SDL_DisplayFormat(temp);
//si libera temp
SDL_FreeSurface(temp);
//settiamo trasparente il colore #000000 di cursor
SDL_SetColorKey(cursor, SDL_SRCCOLORKEY|SDL_RLEACCEL, SDL_MapRGB(cursor->for
mat, 0x0, 0x0, 0x0));
// settiamo le flag per le nuove superfici da creare
flags = SDL_HWSURFACE|SDL_SRCCOLORKEY;
//creiamo una superficie uguale a cursor per coprire la scia lasciata dal
movimento
bg_cursor=SDL_CreateRGBSurface(flags,cursor->w,cursor->h,cursor->format->Bit
sPerPixel,0,0,0,0);
//creiamo una superficie uguale a screen per salvare lo stato quando
cambiamo modalità
//di visualizzazione da finestra a tutto schermo
screen_backup=SDL_CreateRGBSurface(flags,screen->w,screen->h,screen->format-
>BitsPerPixel,0,0,0,0);
//variabile per il ciclo principale
int done = 0;
//contatore
int i;
//limiti della finestra in funzione delle dimensioni del cursore
int x_limit = DIM_H-cursor->w;
int y_limit = DIM_V-cursor->h;
//definisco rettangolo delle dimensioni di cursor utilizzato per i Blit di
superfici
//e per memorizzare la posizione del cursore
SDL_Rect rect_cur;
rect_cur.x = 0;
rect_cur.y = 0;
rect_cur.w = cursor->w;
rect_cur.h = cursor->h;
//due punti per le rette della griglia
point p1,p2;
//3 trutture RGB_Color inizializzate a rosso, giallo e bianco
RGB_Color red = {0xFF,0x0,0x0};
RGB_Color yellow = {0xFF,0xFF,0x0};
RGB_Color white = {0xFF,0xFF,0xFF};
//lato della cella
int passo=50;
//disegno righe orizzontali
p1.x=0;
p1.y=0;
p2.x=DIM_H-1;
p2.y=0;
for(i=0;i<=DIM_V/passo;i++)
{
draw_line(screen,p1, p2, white);
p1.y+=passo;
p2.y+=passo;
}
//disegno ultima riga orizzontale
p1.x=0;
p1.y=DIM_V-1;
p2.x=DIM_H-1;
p2.y=DIM_V-1;
draw_line(screen,p1, p2, white);
//disegno righe verticali
p1.x=0;
p1.y=0;
p2.x=0;
p2.y=DIM_V-1;
for(i=0;i<DIM_H/passo;i++)
{
draw_line(screen,p1, p2, white);
p1.x+=passo;
p2.x+=passo;
}
//disegno ultima riga verticale
p1.x=DIM_H-1;
p1.y=0;
p2.x=DIM_H-1;
p2.y=DIM_V-1;
draw_line(screen,p1, p2, white);
//vertice sx di un quadrato
point cel;
//indici di riga e di colonna della griglia
int row, col, old_row=0,old_col=0;
//dichiariamo una struttura SDL_Event
SDL_Event event;
//game loop, ovvero il loop che viene eseguito finchè non si esce
while(!done)
{
// SDL_WaitEvent attende il prossimo evento
SDL_WaitEvent(&event);
//premendo la x della finestra col mouse si esce
if( event.type == SDL_QUIT )
done = 1;
if( event.type == SDL_KEYDOWN )
{
//ma si esce anche premendo Esc
if ( event.key.keysym.sym == SDLK_ESCAPE )
done = 1;
//premendo spazio si passa dalla modalità schermo intero alla modalità
//finestra e viceversa
if ( event.key.keysym.sym == SDLK_SPACE )
{
//salviamo l'immagine attuale in screen_backup
SDL_BlitSurface(screen,NULL,screen_backup,NULL);
//cambiamo modalità
flags^=SDL_FULLSCREEN;
screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags);
//ripristiniamo l'immagine
SDL_BlitSurface(screen_backup,NULL,screen,NULL);
}
}
//premiamo il tasto sx del mouse, selezioniamo 1 punto
if( event.button.button == BUTTON_SX && event.button.type == SDL_MOUSEBUTT
ONDOWN )
{
//calcoliamo l'indice di riga e di colonna della cella puntata
row=(rect_cur.x+(cursor->w)/2)/passo;
col=(rect_cur.y+(cursor->h)/2)/passo;
//calcoliamo le cordinate del vertice superiore sx della cella
cel.x=(row*passo)+(passo/2);
cel.y=(col*passo)+(passo/2);
//disegnamo il punto selezionato
draw_cross(screen,cel,yellow);
}
//ci spostiamo con il mouse
if( event.motion.type == SDL_MOUSEMOTION )
{
//setto le cordinate del rettangolo del puntatore
rect_cur.x=event.button.x;
rect_cur.y=event.button.y;
rect_cur.w = cursor->w;
rect_cur.h = cursor->h;
//se si vuole andare orizzontalmente oltre la finestra si viene bloccati
if(rect_cur.x >= x_limit)
rect_cur.x = x_limit;
else if(rect_cur.x < 0)
rect_cur.x = 0;
//se si vuole andare verticalmente oltre la finestra si viene bloccati
if(rect_cur.y >= y_limit)
rect_cur.y = y_limit;
else if(rect_cur.y < 0)
rect_cur.y = 0;
//calcoliamo la riga e la colonna su cui ci troviamo
row=(rect_cur.x+(cursor->w)/2)/passo;
col=(rect_cur.y+(cursor->h)/2)/passo;
//se ci troviamo in una cella diversa aggiorniamo la selezione
if(row!=old_row || col!=old_col)
{
//coloriamo la vecchia cella di bianco
cel.x=old_row*passo;
cel.y=old_col*passo;
draw_square(screen,cel,passo,white);
//coloriamo la nuova cella di rosso
cel.x=row*passo;
cel.y=col*passo;
draw_square(screen,cel,passo,red);
//aggiorniamo i vecchi indici di cella
old_row=row;
old_col=col;
}
}
//recuperiamo la parte di screen prima di muoverci
SDL_BlitSurface(screen, &rect_cur, bg_cursor, NULL);
//disegniamo il cursore alla nuova posizione
SDL_BlitSurface(cursor, NULL, screen, &rect_cur);
//aggiorniamo lo schermo
SDL_Flip(screen);
//disegniamo la parte di screen recuperata per coprire la scia
SDL_BlitSurface(bg_cursor, NULL, screen, &rect_cur);
}
return 0;
}
//disegna un pixel sulla superficie desiderata alle cordinate (x,y) con colore
R,G,B
void draw_pixel(SDL_Surface *surface, int x, int y, RGB_Color s_color)
{
//restituisce la mappatura di un colore RGB in base ai bpp (alla definizione
)
Uint32 color = SDL_MapRGB(surface->format, s_color.R, s_color.G, s_color.B);
//byte per pixel
int bpp = surface->format->BytesPerPixel;
//pixel della superficie da colorare
Uint8 *pixel = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
//verifichiamo il numero di byte usati per la nostra superficie
switch (bpp)
{
case 1: // 1 byte => 8-bpp
*pixel = color;
break;
case 2: // 2 byte => 16-bpp
*(Uint16 *)pixel = color;
break;
case 3: // 3 byte => 24-bpp
if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
{
pixel[0] = (color >> 16) & 0xff;
pixel[1] = (color >> 8) & 0xff;
pixel[2] = color & 0xff;
}
else
{
pixel[0] = color & 0xff;
pixel[1] = (color >> 8) & 0xff;
pixel[2] = (color >> 16) & 0xff;
}
break;
case 4: // 4 byte => 32-bpp
*(Uint32 *)pixel = color;
break;
}
}
//disegna una croce di 5 pixel su di una superficie alle cordinate specificate
//del colore desiderato
void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color)
{
//blocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
//si disegnano i pixel della croce
//IMPORTANTE: non ci sono check sulla posizione!
draw_pixel(surface,p.x,p.y,c_color);
draw_pixel(surface,p.x+1,p.y,c_color);
draw_pixel(surface,p.x-1,p.y,c_color);
draw_pixel(surface,p.x,p.y+1,c_color);
draw_pixel(surface,p.x,p.y-1,c_color);
//sblocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
//disegnamo una linea su di una superficie dal punto p0 al punto p1 di colore
color
void draw_line(SDL_Surface *surface,point p0, point p1, RGB_Color color)
{
int i;
int comp;
int d_a,d_b;
int a,b;
int inc;
//blocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
//disegnamo un solo punto
if(p0.x==p1.x && p0.y==p1.y)
draw_pixel(surface,p0.x,p0.y,color);
//disegnamo orizzontalmente
else if(abs(p1.x-p0.x)>=abs(p1.y-p0.y))
{
d_a=p1.x-p0.x;
d_b=p1.y-p0.y;
a=p0.x;
b=p1.x;
//da sx a dx
if(p0.x<p1.x)
inc=1;
//da dx a sx
else
inc=-1;
//serve per disegnare negli estremi compresi
b+=inc;
for(i=a;i!=b;i=i+inc)
{
//calcoliamo la y
comp=(d_b*(i-a))/(d_a)+p0.y;
//disegnamo il pixel alle cordinate attuali
draw_pixel(surface,i,comp,color);
}
}
//disegnamo verticalmente
else
{
d_a=p1.y-p0.y;
d_b=p1.x-p0.x;
a=p0.y;
b=p1.y;
//dal basso all'alto
if(p0.y<p1.y)
inc=1;
//dall'alto al basso
else
inc=-1;
//serve per disegnare negli estremi compresi
b+=inc;
for(i=a;i!=b;i=i+inc)
{
//calcoliamo la x
comp=(d_b*(i-a))/(d_a)+p0.x;
//disegnamo il pixel alle cordinate attuali
draw_pixel(surface,comp,i,color);
}
}
//sblocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
//disegnamo un quadrato di lato lato a partire dal vertice superiore sinistro
del colore color
void draw_square(SDL_Surface *surface,point p, int lato, RGB_Color color)
{
point temp;
//lato superiore
if(p.x+lato<DIM_H)
temp.x=p.x+lato;
else
temp.x=DIM_H-1;
temp.y=p.y;
draw_line(surface,p,temp,color);
//lato sx
temp.x=p.x;
if(p.y+lato<DIM_V)
temp.y=p.y+lato;
else
temp.y=DIM_V-1;
draw_line(surface,p,temp,color);
//lato inferiore
if(p.y+lato<DIM_V)
p.y+=lato;
else
p.y=DIM_V-1;
if(p.x+lato<DIM_H)
temp.x=p.x+lato;
else
temp.x=DIM_H-1;
temp.y=p.y;
draw_line(surface,p,temp,color);
//lato dx
if(p.x+lato<DIM_H)
p.x+=lato;
else
p.x=DIM_H-1;
temp.x=p.x;
temp.y=p.y-lato;
draw_line(surface,p,temp,color);
}
Spiegazioni del sorgente - il main
#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#define DIM_H 800
#define DIM_V 600
#define BUTTON_SX 1
#define BUTTON_DX 3
//strutture usate
typedef struct point
{
int x;
int y;
} point;
typedef struct RGB
{
Uint8 R;
Uint8 G;
Uint8 B;
} RGB_Color;
//function grafiche
void draw_pixel(SDL_Surface *surface, int x, int y, RGB_Color s_color);
void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color);
void draw_line(SDL_Surface *surface, point p0, point p1, RGB_Color color);
void draw_square(SDL_Surface *surface, point p, int lato, RGB_Color color);
Come al solito nella prima parte del sorgente sono presenti i vari include, i
define, i prototipi delle function utilizzate ed in questo caso anche due definizioni di
strutture.
La prima, point, serve a descrivere le coordinate di un punto, mentre la seconda, RGB,
contiene i valori delle componenti che costituiscono un colore RGB.
int main()
{
//superfici utilizzate
SDL_Surface *screen;
SDL_Surface *screen_backup;
SDL_Surface *cursor;
SDL_Surface *bg_cursor;
SDL_Surface *temp;
Inizializziamo diverse superfici, vedremo nel corso del sorgente come utilizzarle.
// si inizializza il sistema video
if( SDL_Init(SDL_INIT_VIDEO) <0 )
{
// SDL_GetError() ritorna una descrizione dell'errore
printf("Errore init SDL: %s\n", SDL_GetError());
return 1;
}
Richiediamo l'attivazione del sottosistema video di SDL, controllando che non ci siano
errori.
// all'uscita del programma esegui SDL_Quit per risistemare le cose
atexit(SDL_Quit);
Assegnamo alla function SDL_Quit la gestione dell'uscita del programma.
//dichiariamo un puntatore ad una superficie ed un int a 32 bit per i flag
da assegnarle
Uint32 flags = SDL_HWSURFACE|SDL_DOUBLEBUF;
// per rendere disegnabile la Superficie bisogna utilizzare SDL_SetVideoMode
if(!( screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags) ))
{
printf("Problemi con il settaggio dello schermo: %s\n", SDL_GetError());
return 1;
}
Associamo lo schermo alla superficie screen impostando i flag appropriati.
//nascondiamo il cursore della finestra
SDL_ShowCursor(0);
Tramite la chiamata alla function SDL_ShowCursor con 0 come parametro disattiviamo
la visualizzazione del cursore del mouse.
Questo ci permetterà di assegnare al movimento del mouse un'immagine in modo da poter utilizzare
un puntatore personalizzato.
//carica img cursore su superficie temporanea
if((temp = IMG_Load("mirino.png"))==NULL)
{
printf("Errore caricamento mirino: %s\n",IMG_GetError());
return 1;
}
//Adatta l'img al display attuale e l'assegna ad una superficie
cursor = SDL_DisplayFormat(temp);
//si libera temp
SDL_FreeSurface(temp);
Carichiamo l'immagine del mirino (il nostro cursore) assegnandola ad una superficie temporanea
per adattarla poi al display attuale ed assegnarla alla superficie definitiva cursor (riga 73).
Fatto ciò deallochiamo la superficie temporanea per poterla riutilizzare alla stessa maniera con le
altre immagini (riga 76).
//settiamo trasparente il colore #000000 di cursor
SDL_SetColorKey(cursor, SDL_SRCCOLORKEY|SDL_RLEACCEL, SDL_MapRGB(cursor->for
mat, 0x0, 0x0, 0x0));
Rendiamo trasparente il colore #000000, ovvero il nero, nella superficie cursor.
// settiamo le flag per le nuove superfici da creare
flags = SDL_HWSURFACE|SDL_SRCCOLORKEY;
//creiamo una superficie uguale a cursor per coprire la scia lasciata dal
movimento
bg_cursor=SDL_CreateRGBSurface(flags,cursor->w,cursor->h,cursor->format->Bit
sPerPixel,0,0,0,0);
Creiamo una superficie delle stesse dimensioni di quella cursor, da utilizzare in seguito per
coprire la scia lasciata dallo spostamento del mouse.
//creiamo una superficie uguale a screen per salvare lo stato quando
cambiamo modalità
//di visualizzazione da finestra a tutto schermo
screen_backup=SDL_CreateRGBSurface(flags,screen->w,screen->h,screen->format-
>BitsPerPixel,0,0,0,0);
Creiamo una superficie delle dimensioni di screen, in modo da salvare lo stato dello schermo
nel passaggio da modalità finestra a modalità tutto schermo.
//variabile per il ciclo principale
int done = 0;
//contatore
int i;
Dichiariamo il flag di fine game loop ed un contatore da usare per i cicli seguenti.
//limiti della finestra in funzione delle dimensioni del cursore
int x_limit = DIM_H-cursor->w;
int y_limit = DIM_V-cursor->h;
Impostiamo dei limiti nello spostamento del cursore del mouse, tali limiti saranno
controllati durante la gestione degli eventi come vedremo in seguito.
//definisco rettangolo delle dimensioni di cursor utilizzato per i Blit di
superfici
//e per memorizzare la posizione del cursore
SDL_Rect rect_cur;
rect_cur.x = 0;
rect_cur.y = 0;
rect_cur.w = cursor->w;
rect_cur.h = cursor->h;
Definiamo un rettangolo che ha le dimensioni del cursore che utilizzeremo per mantenere
le coordinate di questo elemento grafico in modo da poterlo disegnare su screen.
//due punti per le rette della griglia
point p1,p2;
Dichiariamo due strutture point che utilizzeremo per indicare il primo e l'ultimo punto
di ogni linea della griglia.
//3 trutture RGB_Color inizializzate a rosso, giallo e bianco
RGB_Color red = {0xFF,0x0,0x0};
RGB_Color yellow = {0xFF,0xFF,0x0};
RGB_Color white = {0xFF,0xFF,0xFF};
Dichiariamo tre strutture RGB inizializzandole con i valori esadecimali dei colori rosso,
giallo e bianco.
//lato della cella
int passo=50;
La variabile passo indica il lato di ogni cella.
//disegno righe orizzontali
p1.x=0;
p1.y=0;
p2.x=DIM_H-1;
p2.y=0;
for(i=0;i<=DIM_V/passo;i++)
{
draw_line(screen,p1, p2, white);
p1.y+=passo;
p2.y+=passo;
}
//disegno ultima riga orizzontale
p1.x=0;
p1.y=DIM_V-1;
p2.x=DIM_H-1;
p2.y=DIM_V-1;
draw_line(screen,p1, p2, white);
//disegno righe verticali
p1.x=0;
p1.y=0;
p2.x=0;
p2.y=DIM_V-1;
for(i=0;i<DIM_H/passo;i++)
{
draw_line(screen,p1, p2, white);
p1.x+=passo;
p2.x+=passo;
}
//disegno ultima riga verticale
p1.x=DIM_H-1;
p1.y=0;
p2.x=DIM_H-1;
p2.y=DIM_V-1;
draw_line(screen,p1, p2, white);
Queste righe gestiscono il disegno della griglia mediante due cicli for separati, da
notare che il disegno dell'ultima linea orizzontale e dell'ultima riga verticale viene gestito
separatamente dai cicli, visto che si deve disegnare un pixel prima delle dimensioni della
finestra.
//vertice sx di un quadrato
point cel;
Dichiariamo una struttura point per memorizzare il vertice della cella nella quale si
trova il puntatore del mouse.
//indici di riga e di colonna della griglia
int row, col, old_row=0,old_col=0;
Dichiariamo quattro interi che utilizzeremo per memorizzare gli indici di riga e di colonna
nel quale si trova il puntatore del mouse al passo attuale e per memorizzare la posizione
all'iterazione precedente.
//dichiariamo una struttura SDL_Event
SDL_Event event;
Dichiariamo una struttura SDL_Event per gestire gli eventi.
//game loop, ovvero il loop che viene eseguito finchè non si esce
while(!done)
{
Entriamo nel game loop, ne usciremo quando la variabile done sarà settata ad 1.
// SDL_WaitEvent attende il prossimo evento
SDL_WaitEvent(&event);
Utilizziamo SDL_Wait per procedere solo quando ci sono eventi da gestire, questo è
possibile perchè dobbiamo gestire solo gli eventi del mouse e la pressione di tasti funzione
(ESC e spazio).
Tale tecnica permette di ottenere un basso utilizzo del processore (con picchi intorno il 50% e
valori tendenti allo 0 durante l'inutilizzo del programma) e delle buone prestazioni grafiche.
Ovviamente una gestione del genere non è ipotizzabile in uno scenario con animazioni, in tal
caso si deve passare ad utilizzare SDL_PoolEvent (abbinandola ad un SDL_Delay(1) alla fine
del game loop) oppure si deve assegnare la gestione del mouse ad un thread a parte.
//premendo la x della finestra col mouse si esce
if( event.type == SDL_QUIT )
done = 1;
if( event.type == SDL_KEYDOWN )
{
//ma si esce anche premendo Esc
if ( event.key.keysym.sym == SDLK_ESCAPE )
done = 1;
Usciamo dal programma quando si clicka con il mouse sulla x della finestra oppure quando
si preme il tasto ESC.
//premendo spazio si passa dalla modalità schermo intero alla modalità
//finestra e viceversa
if ( event.key.keysym.sym == SDLK_SPACE )
{
//salviamo l'immagine attuale in screen_backup
SDL_BlitSurface(screen,NULL,screen_backup,NULL);
//cambiamo modalità
flags^=SDL_FULLSCREEN;
screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags);
//ripristiniamo l'immagine
SDL_BlitSurface(screen_backup,NULL,screen,NULL);
}
}
Quando viene premuto il tasto SPAZIO cambiamo modalità passando da quella finestra
a quella a tutto schermo e viceversa.
Notate come in questo caso si debba salvare lo stato attuale dello schermo per non perdere
lo schema inizialmente creato.
//premiamo il tasto sx del mouse, selezioniamo 1 punto
if( event.button.button == BUTTON_SX && event.button.type == SDL_MOUSEBUTT
ONDOWN )
{
Con questo if andiamo a gestire il click sinistro del mouse.
//calcoliamo l'indice di riga e di colonna della cella puntata
row=(rect_cur.x+(cursor->w)/2)/passo;
col=(rect_cur.y+(cursor->h)/2)/passo;
In queste due righe vengono calcolati gli indici di riga e di colonna che ci permettono
di identificare la cella nella quale si trova il puntatore del mouse all'interno della griglia.
Per calcolare gli indici di riga e colonna basta dividere le coordinate del cursore per
le corrispettive dimensioni della cella.
Nel nostro caso andiamo a sommare alle coordinate che identificano il cursore (rect_cur.x e
rect_cur.y) la metà delle corrispettive dimensioni dell'immagine che lo costituisce, questo perchè
vogliamo conoscere la posizione del centro del mirino e non dell'angolo in alto a sinistra
identificato dalle coordinate memorizzate in rect_cur.
//calcoliamo le cordinate del vertice superiore sx della cella
cel.x=(row*passo)+(passo/2);
cel.y=(col*passo)+(passo/2);
Queste due righe di codice si occupano di calcolare le coordinate del centro della cella
moltiplicando i corrispettivi indici per le dimensioni, ottenendo così il vertice in alto a sinistra,
andando poi a sommare al prodotto ottenuto metà della corrispettiva dimensione.
//disegnamo il punto selezionato
draw_cross(screen,cel,yellow);
}
Calcolato il baricentro della cella andiamo a disegnarvi una croce gialla per indicare
l'avvenuto riconoscimento.
//ci spostiamo con il mouse
if( event.motion.type == SDL_MOUSEMOTION )
{
//setto le cordinate del rettangolo del puntatore
rect_cur.x=event.button.x;
rect_cur.y=event.button.y;
rect_cur.w = cursor->w;
rect_cur.h = cursor->h;
Ad ogni spostamento del mouse andiamo ad aggiornare il rettangolo che identifica
l'immagine del cursore memorizzando la nuova posizione e riassegnando i valori di larghezza
(rect_cur.w) e di altezza (rect_cur.h) per sopperire ad eventuali modifiche effettuate sulla
struttura da function come SDL_BlitSurface.
//se si vuole andare orizzontalmente oltre la finestra si viene bloccati
if(rect_cur.x >= x_limit)
rect_cur.x = x_limit;
else if(rect_cur.x < 0)
rect_cur.x = 0;
//se si vuole andare verticalmente oltre la finestra si viene bloccati
if(rect_cur.y >= y_limit)
rect_cur.y = y_limit;
else if(rect_cur.y < 0)
rect_cur.y = 0;
Prima di procedere con eventuali azioni andiamo a verificare che non vi siano stati superamenti
dei limiti della finestra da parte della nostra immagine.
//calcoliamo la riga e la colonna su cui ci troviamo
row=(rect_cur.x+(cursor->w)/2)/passo;
col=(rect_cur.y+(cursor->h)/2)/passo;
Calcoliamo gli indici di riga e di colonna come abbiamo già visto alle righe 199 e 200.
//se ci troviamo in una cella diversa aggiorniamo la selezione
if(row!=old_row || col!=old_col)
{
Grazie a questo if verifichiamo se ci siamo spostati di cella con il mouse, ovvero se
uno degli attuali indici di riga e di colonna è diverso da quelli calcolati alla precedente
iterazione.
//coloriamo la vecchia cella di bianco
cel.x=old_row*passo;
cel.y=old_col*passo;
draw_square(screen,cel,passo,white);
//coloriamo la nuova cella di rosso
cel.x=row*passo;
cel.y=col*passo;
draw_square(screen,cel,passo,red);
//aggiorniamo i vecchi indici di cella
old_row=row;
old_col=col;
}
}
In caso affermativo dobbiamo spostare la cella evidenziata, quindi ricoloriamo
innanzitutto la vecchia cella di bianco (239-241), poi coloriamo la nuova cella di rosso
(244-246) ed infine aggiorniamo il valore dei vecchi indici di cella con i correnti, in modo
da poter ripetere il procedimento al passo successivo.
//recuperiamo la parte di screen prima di muoverci
SDL_BlitSurface(screen, &rect_cur, bg_cursor, NULL);
//disegniamo il cursore alla nuova posizione
SDL_BlitSurface(cursor, NULL, screen, &rect_cur);
//aggiorniamo lo schermo
SDL_Flip(screen);
//disegniamo la parte di screen recuperata per coprire la scia
SDL_BlitSurface(bg_cursor, NULL, screen, &rect_cur);
}
Per concludere gestiamo il disegno del puntatore del mouse grazie le coordinate
calcolate dal nostro gestore degli eventi.
Per eliminare l'effetto scia lasciato dallo spostamento del mouse si utilizza una semplice
tecnica, ovvero si memorizza la porzione di screen sulla quale verrà spostato il cursore su di una
superficie di backup (riga 255), poi si effettua lo spostamento del cursore (riga 258) e si procede
ad aggiornare lo schermo chiamando SDL_Flip (261), infine si disegna la porzione di screen
precedentemente salvata nella superficie di backup (264) in modo che al prossimo aggiornamento
video la superficie venga risistemata.
Terminato il game loop non ci resta altro da fare che uscire, volendo si sarebbe potuto
deallocare le superfici utilizzate, ma visto che non dobbiamo fare altro che uscire, lasciamo il
compito di gestire ciò ad SDL.
Spiegazioni del sorgente - le function
Nei prossimi paragrafi sono riportate le spiegazioni relative alle varie function
utilizzate, non essendoci argomenti strettamente legati ad SDL, le spiegazioni saranno
alquanto sbrigative vista anche la semplicità del codice.
draw_pixel
La prima function si occupa di disegnare un pixel su di una superficie.
Siccome tale function è già stata abbondantemente descritta nella mia guida "Seconda
introduzione alla programmazione con SDL"
[1], passiamo oltre.
draw_cross
//disegna una croce di 5 pixel su di una superficie alle cordinate specificate
//del colore desiderato
void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color)
{
//blocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
//si disegnano i pixel della croce
//IMPORTANTE: non ci sono check sulla posizione!
draw_pixel(surface,p.x,p.y,c_color);
draw_pixel(surface,p.x+1,p.y,c_color);
draw_pixel(surface,p.x-1,p.y,c_color);
draw_pixel(surface,p.x,p.y+1,c_color);
draw_pixel(surface,p.x,p.y-1,c_color);
//sblocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
La function si occupa di disegnare una croce del colore desiderato con centro alle
coordinate (x,y). In pratica viene disegnato il pixel alla posizione (x,y) e poi quattro pixel
alle quattro direzioni che lo circondano.
Visto che andiamo ad accedere direttamente ai pixel della superficie mediante la
function draw_pixel, andiamo a bloccare la superficie (righe 316-317) ed a sbloccarla una volta
terminate le operazioni di disegno (righe 328-329).
draw_line
//disegnamo una linea su di una superficie dal punto p0 al punto p1 di colore
color
void draw_line(SDL_Surface *surface,point p0, point p1, RGB_Color color)
{
int i;
int comp;
int d_a,d_b;
int a,b;
int inc;
Inizialmente dichiariamo un po' di variabili: 'ì ci servirà da contatore, 'comp'
conterrà una coordinata calcolata, 'd_à e 'd_b' sono due delta (differenze) tra le coordinate
del primo e dell'ultimo punto della retta, 'à e 'b' sono due coordinate del primo e dell'ultimo
punto della retta ed 'inc' indicherà la direzione del disegno.
//blocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_LockSurface(surface);
Ovviamente blocchiamo l'accesso alla superficie per andare ad operare sui pixel.
//disegnamo un solo punto
if(p0.x==p1.x && p0.y==p1.y)
draw_pixel(surface,p0.x,p0.y,color);
Nel caso le coordinate dei due punti sono uguali dobbiamo disegnare un pixel solo,
pertanto lo disegniamo e terminiamo.
//disegnamo orizzontalmente
else if(abs(p1.x-p0.x)>=abs(p1.y-p0.y))
{
d_a=p1.x-p0.x;
d_b=p1.y-p0.y;
a=p0.x;
b=p1.x;
Nel caso in cui la distanza orizzontale dei due punti punti è maggiore di quella
verticale andiamo a disegnare orizzontalmente, ovvero partendo dalla coordinata x del primo
punto (assegnata ad a), fino ad arrivare alla coordinata x del secondo punto (assegnata a b)
, quindi calcoliamo di volta in volta la y.
Utilizziamo questa distinzione perchè così facendo abbiamo sicuramente una retta
costituita da più punti e quindi più precisa (ovviamente non siamo a livelli dell'antialiasing,
ma il risultato è abbastanza accettabile!).
//da sx a dx
if(p0.x<p1.x)
inc=1;
//da dx a sx
else
inc=-1;
//serve per disegnare negli estremi compresi
b+=inc;
Determiniamo la direzione dello spostamento a seconda che la coordinata x del primo
punto sia maggiore o minore di quella del secondo ed incrementiamo l'estremo b di inc, in
modo da includere anche l'ultimo punto nel disegno.
for(i=a;i!=b;i=i+inc)
{
//calcoliamo la y
comp=(d_b*(i-a))/(d_a)+p0.y;
//disegnamo il pixel alle cordinate attuali
draw_pixel(surface,i,comp,color);
}
}
Il ciclo for si occupa di scorrere da 'à a 'b' calcolando di volta in volta la
coordinata del punto y della retta voluta, tale calcolo (riga 369) deriva dalla formula della
retta passante per due punti ovvero (x-x0)/(x1-x0)=(y-y0)/(y1-y0).
//disegnamo verticalmente
else
{
d_a=p1.y-p0.y;
d_b=p1.x-p0.x;
a=p0.y;
b=p1.y;
//dal basso all'alto
if(p0.y<p1.y)
inc=1;
//dall'alto al basso
else
inc=-1;
//serve per disegnare negli estremi compresi
b+=inc;
for(i=a;i!=b;i=i+inc)
{
//calcoliamo la x
comp=(d_b*(i-a))/(d_a)+p0.x;
//disegnamo il pixel alle cordinate attuali
draw_pixel(surface,comp,i,color);
}
}
Lo stesso procedimento visto per il disegno orizzontale si ripete per quello
verticale, caso che si presenta quando la differenza verticale dei due punti è maggiore
di quella orizzontale.
Ovviamente quello che viene calcolato questa volta è la componente x del punto della retta.
//sblocchiamo lo schermo se necessario
if ( SDL_MUSTLOCK(surface) )
SDL_UnlockSurface(surface);
}
Terminato il disegno dei vari punti non ci resta che sbloccare la superficie
precedentemente bloccata.
draw_square
//disegnamo un quadrato di lato lato a partire dal vertice superiore sinistro
del colore color
void draw_square(SDL_Surface *surface,point p, int lato, RGB_Color color)
{
point temp;
Dichiariamo una struttura point per avere un secondo estremo del lato del quadrato
che andremo a disegnare.
//lato superiore
if(p.x+lato<DIM_H)
temp.x=p.x+lato;
else
temp.x=DIM_H-1;
temp.y=p.y;
draw_line(surface,p,temp,color);
//lato sx
temp.x=p.x;
if(p.y+lato<DIM_V)
temp.y=p.y+lato;
else
temp.y=DIM_V-1;
draw_line(surface,p,temp,color);
//lato inferiore
if(p.y+lato<DIM_V)
p.y+=lato;
else
p.y=DIM_V-1;
if(p.x+lato<DIM_H)
temp.x=p.x+lato;
else
temp.x=DIM_H-1;
temp.y=p.y;
draw_line(surface,p,temp,color);
//lato dx
if(p.x+lato<DIM_H)
p.x+=lato;
else
p.x=DIM_H-1;
temp.x=p.x;
temp.y=p.y-lato;
draw_line(surface,p,temp,color);
}
Di volta in volta andiamo a disegnare i 4 lati che costituiscono il quadrato
facendo variare le posizioni del punto iniziale 'p' e del punto di appoggio 'temp'
stando attendi a non andare oltre le dimensioni dello schermo.
Compilazione
Per compilare il nostro sorgente è necessario utilizzare come segue il gcc:
g++ -O2 -s -o mouse_tile mouse_tile.cpp `sdl-config --cflags --libs` -lSDL_image
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 mouse_tile.
Il comando `sdl-config --cflags --libs` ci darà tutte le opzioni necessarie per includere
ed utilizzare la libreria SDL.
Infine "-lSDL_image" ci permette di usare la libreria SDL_Image.
Volendo è possibile anche effettuare la compilazione lanciando il comando
make
.