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

Gestione del mouse con SDL e tile

Abstract
In questa guida viene illustrata la personalizzazione del puntatore e la gestione del mouse applicata ad un campo di tile 2D generato mediante diverse primitive grafiche implementate all'interno del programma.
Data di stesura: 05/06/2004
Data di pubblicazione: 13/09/2004
Ultima modifica: 04/04/2006
di Davide Coppola Discuti sul forum   Stampa

Premesse

Questa guida richiede una conoscenza discreta del C e di un pò di logica booleana, siccome non è suo compito spiegarvi queste due cose, nel caso non 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.
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <SDL/SDL.h> 
  4. #include <SDL/SDL_image.h> 
  5.  
  6. #define DIM_H 800 
  7. #define DIM_V 600 
  8. #define BUTTON_SX 1 
  9. #define BUTTON_DX 3 
  10.  
  11. //strutture usate 
  12. typedef struct point 
  13.   int x; 
  14.   int y; 
  15. } point; 
  16.  
  17. typedef struct RGB 
  18.   Uint8 R; 
  19.   Uint8 G; 
  20.   Uint8 B; 
  21. } RGB_Color; 
  22.  
  23. //function grafiche 
  24. void draw_pixel(SDL_Surface *surface, int x, int y, RGB_Color s_color); 
  25. void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color); 
  26. void draw_line(SDL_Surface *surface, point p0, point p1, RGB_Color color); 
  27. void draw_square(SDL_Surface *surface, point p, int lato, RGB_Color color); 
  28.  
  29. int main() 
  30.   //superfici utilizzate 
  31.   SDL_Surface *screen; 
  32.   SDL_Surface *screen_backup; 
  33.   SDL_Surface *cursor; 
  34.   SDL_Surface *bg_cursor; 
  35.   SDL_Surface *temp; 
  36.  
  37.   // si inizializza il sistema video 
  38.   if( SDL_Init(SDL_INIT_VIDEO) <0 ) 
  39.     // SDL_GetError() ritorna una descrizione dell'errore 
  40.     printf("Errore init SDL: %s\n", SDL_GetError()); 
  41.     return 1; 
  42.  
  43.   // all'uscita del programma esegui SDL_Quit per risistemare le cose 
  44.   atexit(SDL_Quit); 
  45.  
  46.   //dichiariamo un puntatore ad una superficie ed un int a 32 bit per i flag 

      da assegnarle 
  47.   Uint32 flags = SDL_HWSURFACE|SDL_DOUBLEBUF; 
  48.  
  49.   // per rendere disegnabile la Superficie bisogna utilizzare SDL_SetVideoMode 
  50.   if(!( screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags) )) 
  51.     printf("Problemi con il settaggio dello schermo: %s\n", SDL_GetError()); 
  52.     return 1; 
  53.  
  54.   //nascondiamo il cursore della finestra 
  55.   SDL_ShowCursor(0); 
  56.  
  57.  
  58.   //carica img cursore su superficie temporanea 
  59.   if((temp = IMG_Load("mirino.png"))==NULL) 
  60.     printf("Errore caricamento mirino: %s\n",IMG_GetError()); 
  61.     return 1; 
  62.  
  63.   //Adatta l'img al display attuale e l'assegna ad una superficie 
  64.   cursor = SDL_DisplayFormat(temp); 
  65.  
  66.   //si libera temp 
  67.   SDL_FreeSurface(temp); 
  68.  
  69.   //settiamo trasparente il colore #000000 di cursor 
  70.   SDL_SetColorKey(cursor, SDL_SRCCOLORKEY|SDL_RLEACCEL, SDL_MapRGB(cursor->for

      mat, 0x0, 0x0, 0x0)); 
  71.  
  72.   // settiamo le flag per le nuove superfici da creare 
  73.   flags = SDL_HWSURFACE|SDL_SRCCOLORKEY; 
  74.   //creiamo una superficie uguale a cursor per coprire la scia lasciata dal 

      movimento 
  75.   bg_cursor=SDL_CreateRGBSurface(flags,cursor->w,cursor->h,cursor->format->Bit

      sPerPixel,0,0,0,0); 
  76.   //creiamo una superficie uguale a screen per salvare lo stato quando 

      cambiamo modalità 
  77.   //di visualizzazione da finestra a tutto schermo 
  78.   screen_backup=SDL_CreateRGBSurface(flags,screen->w,screen->h,screen->format-

      >BitsPerPixel,0,0,0,0); 
  79.  
  80.   //variabile per il ciclo principale 
  81.   int done = 0; 
  82.   //contatore 
  83.   int i; 
  84.  
  85.   //limiti della finestra in funzione delle dimensioni del cursore 
  86.   int x_limit = DIM_H-cursor->w; 
  87.   int y_limit = DIM_V-cursor->h; 
  88.  
  89.   //definisco rettangolo delle dimensioni di cursor utilizzato per i Blit di 

      superfici 
  90.   //e per memorizzare la posizione del cursore 
  91.   SDL_Rect rect_cur; 
  92.   rect_cur.x = 0; 
  93.   rect_cur.y = 0; 
  94.   rect_cur.w = cursor->w; 
  95.   rect_cur.h = cursor->h; 
  96.  
  97.   //due punti per le rette della griglia 
  98.   point p1,p2; 
  99.   //3 trutture RGB_Color inizializzate a rosso, giallo e bianco 
  100.   RGB_Color red = {0xFF,0x0,0x0}; 
  101.   RGB_Color yellow = {0xFF,0xFF,0x0}; 
  102.   RGB_Color white = {0xFF,0xFF,0xFF}; 
  103.  
  104.   //lato della cella 
  105.   int passo=50; 
  106.  
  107.   //disegno righe orizzontali 
  108.   p1.x=0; 
  109.   p1.y=0; 
  110.   p2.x=DIM_H-1; 
  111.   p2.y=0; 
  112.  
  113.   for(i=0;i<=DIM_V/passo;i++) 
  114.     draw_line(screen,p1, p2, white); 
  115.     p1.y+=passo; 
  116.     p2.y+=passo; 
  117.  
  118.   //disegno ultima riga orizzontale 
  119.   p1.x=0; 
  120.   p1.y=DIM_V-1; 
  121.   p2.x=DIM_H-1; 
  122.   p2.y=DIM_V-1; 
  123.   draw_line(screen,p1, p2, white); 
  124.  
  125.   //disegno righe verticali 
  126.   p1.x=0; 
  127.   p1.y=0; 
  128.   p2.x=0; 
  129.   p2.y=DIM_V-1; 
  130.  
  131.   for(i=0;i<DIM_H/passo;i++) 
  132.     draw_line(screen,p1, p2, white); 
  133.     p1.x+=passo; 
  134.     p2.x+=passo; 
  135.  
  136.   //disegno ultima riga verticale 
  137.   p1.x=DIM_H-1; 
  138.   p1.y=0; 
  139.   p2.x=DIM_H-1; 
  140.   p2.y=DIM_V-1; 
  141.   draw_line(screen,p1, p2, white); 
  142.  
  143.   //vertice sx di un quadrato 
  144.   point cel; 
  145.   //indici di riga e di colonna della griglia 
  146.   int row, col, old_row=0,old_col=0; 
  147.  
  148.   //dichiariamo una struttura SDL_Event 
  149.   SDL_Event event; 
  150.  
  151.   //game loop, ovvero il loop che viene eseguito finchè non si esce 
  152.   while(!done) 
  153.     // SDL_WaitEvent attende il prossimo evento 
  154.     SDL_WaitEvent(&event); 
  155.  
  156.     //premendo la x della finestra col mouse si esce 
  157.     if( event.type == SDL_QUIT ) 
  158.       done = 1; 
  159.  
  160.     if( event.type == SDL_KEYDOWN ) 
  161.       //ma si esce anche premendo Esc 
  162.       if ( event.key.keysym.sym == SDLK_ESCAPE ) 
  163.         done = 1; 
  164.  
  165.       //premendo spazio si passa dalla modalità schermo intero alla modalità 
  166.       //finestra e viceversa 
  167.       if ( event.key.keysym.sym == SDLK_SPACE ) 
  168.         //salviamo l'immagine attuale in screen_backup 
  169.         SDL_BlitSurface(screen,NULL,screen_backup,NULL); 
  170.         //cambiamo modalità 
  171.         flags^=SDL_FULLSCREEN; 
  172.         screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  173.         //ripristiniamo l'immagine 
  174.         SDL_BlitSurface(screen_backup,NULL,screen,NULL); 
  175.  
  176.     //premiamo il tasto sx del mouse, selezioniamo 1 punto 
  177.     if( event.button.button == BUTTON_SX && event.button.type == SDL_MOUSEBUTT

        ONDOWN ) 
  178.  
  179.       //calcoliamo l'indice di riga e di colonna della cella puntata 
  180.       row=(rect_cur.x+(cursor->w)/2)/passo; 
  181.       col=(rect_cur.y+(cursor->h)/2)/passo; 
  182.  
  183.       //calcoliamo le cordinate del vertice superiore sx della cella 
  184.       cel.x=(row*passo)+(passo/2); 
  185.       cel.y=(col*passo)+(passo/2); 
  186.  
  187.       //disegnamo il punto selezionato 
  188.       draw_cross(screen,cel,yellow); 
  189.  
  190.     //ci spostiamo con il mouse 
  191.     if( event.motion.type == SDL_MOUSEMOTION ) 
  192.       //setto le cordinate del rettangolo del puntatore 
  193.       rect_cur.x=event.button.x; 
  194.       rect_cur.y=event.button.y; 
  195.       rect_cur.w = cursor->w; 
  196.       rect_cur.h = cursor->h; 
  197.  
  198.       //se si vuole andare orizzontalmente oltre la finestra si viene bloccati 
  199.       if(rect_cur.x >= x_limit) 
  200.         rect_cur.x = x_limit; 
  201.       else if(rect_cur.x < 0) 
  202.         rect_cur.x = 0; 
  203.  
  204.       //se si vuole andare verticalmente oltre la finestra si viene bloccati 
  205.       if(rect_cur.y >= y_limit) 
  206.         rect_cur.y = y_limit; 
  207.       else if(rect_cur.y < 0) 
  208.         rect_cur.y = 0; 
  209.  
  210.       //calcoliamo la riga e la colonna su cui ci troviamo 
  211.       row=(rect_cur.x+(cursor->w)/2)/passo; 
  212.       col=(rect_cur.y+(cursor->h)/2)/passo; 
  213.  
  214.       //se ci troviamo in una cella diversa aggiorniamo la selezione 
  215.       if(row!=old_row || col!=old_col) 
  216.         //coloriamo la vecchia cella di bianco 
  217.         cel.x=old_row*passo; 
  218.         cel.y=old_col*passo; 
  219.         draw_square(screen,cel,passo,white); 
  220.  
  221.         //coloriamo la nuova cella di rosso 
  222.         cel.x=row*passo; 
  223.         cel.y=col*passo; 
  224.         draw_square(screen,cel,passo,red); 
  225.  
  226.         //aggiorniamo i vecchi indici di cella 
  227.         old_row=row; 
  228.         old_col=col; 
  229.  
  230.     //recuperiamo la parte di screen prima di muoverci 
  231.     SDL_BlitSurface(screen, &rect_cur, bg_cursor, NULL); 
  232.  
  233.     //disegniamo il cursore alla nuova posizione 
  234.     SDL_BlitSurface(cursor, NULL, screen, &rect_cur); 
  235.  
  236.     //aggiorniamo lo schermo 
  237.     SDL_Flip(screen); 
  238.  
  239.     //disegniamo la parte di screen recuperata per coprire la scia 
  240.     SDL_BlitSurface(bg_cursor, NULL, screen, &rect_cur); 
  241.  
  242.   return 0; 
  243.  
  244. //disegna un pixel sulla superficie desiderata alle cordinate (x,y) con colore 

    R,G,B 
  245. void draw_pixel(SDL_Surface *surface, int x, int y, RGB_Color s_color) 
  246.   //restituisce la mappatura di un colore RGB in base ai bpp (alla definizione

  247.   Uint32 color = SDL_MapRGB(surface->format, s_color.R, s_color.G, s_color.B); 
  248.  
  249.   //byte per pixel 
  250.   int bpp = surface->format->BytesPerPixel; 
  251.  
  252.   //pixel della superficie da colorare 
  253.   Uint8 *pixel = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; 
  254.  
  255.   //verifichiamo il numero di byte usati per la nostra superficie 
  256.   switch (bpp) 
  257.     case 1: // 1 byte =>  8-bpp 
  258.       *pixel = color; 
  259.       break; 
  260.     case 2: // 2 byte => 16-bpp 
  261.       *(Uint16 *)pixel = color; 
  262.       break; 
  263.     case 3: // 3 byte => 24-bpp 
  264.       if(SDL_BYTEORDER == SDL_BIG_ENDIAN) 
  265.         pixel[0] = (color >> 16) & 0xff; 
  266.         pixel[1] = (color >> 8) & 0xff; 
  267.         pixel[2] = color & 0xff; 
  268.       else 
  269.         pixel[0] = color & 0xff; 
  270.         pixel[1] = (color >> 8) & 0xff; 
  271.         pixel[2] = (color >> 16) & 0xff; 
  272.       break; 
  273.     case 4: // 4 byte => 32-bpp 
  274.       *(Uint32 *)pixel = color; 
  275.       break; 
  276.  
  277. //disegna una croce di 5 pixel su di una superficie alle cordinate specificate 
  278. //del colore desiderato 
  279. void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color) 
  280.   //blocchiamo lo schermo se necessario 
  281.   if ( SDL_MUSTLOCK(surface) ) 
  282.     SDL_LockSurface(surface); 
  283.  
  284.   //si disegnano i pixel della croce 
  285.   //IMPORTANTE: non ci sono check sulla posizione! 
  286.   draw_pixel(surface,p.x,p.y,c_color); 
  287.   draw_pixel(surface,p.x+1,p.y,c_color); 
  288.   draw_pixel(surface,p.x-1,p.y,c_color); 
  289.   draw_pixel(surface,p.x,p.y+1,c_color); 
  290.   draw_pixel(surface,p.x,p.y-1,c_color); 
  291.  
  292.   //sblocchiamo lo schermo se necessario 
  293.   if ( SDL_MUSTLOCK(surface) ) 
  294.     SDL_UnlockSurface(surface); 
  295.  
  296. //disegnamo una linea su di una superficie dal punto p0 al punto p1 di colore 

    color 
  297. void draw_line(SDL_Surface *surface,point p0, point p1, RGB_Color color) 
  298.   int i; 
  299.   int comp; 
  300.   int d_a,d_b; 
  301.   int a,b; 
  302.   int inc; 
  303.  
  304.   //blocchiamo lo schermo se necessario 
  305.   if ( SDL_MUSTLOCK(surface) ) 
  306.     SDL_LockSurface(surface); 
  307.  
  308.   //disegnamo un solo punto 
  309.   if(p0.x==p1.x && p0.y==p1.y) 
  310.       draw_pixel(surface,p0.x,p0.y,color); 
  311.    //disegnamo orizzontalmente 
  312.   else if(abs(p1.x-p0.x)>=abs(p1.y-p0.y)) 
  313.     d_a=p1.x-p0.x; 
  314.     d_b=p1.y-p0.y; 
  315.     a=p0.x; 
  316.     b=p1.x; 
  317.  
  318.     //da sx a dx 
  319.     if(p0.x<p1.x) 
  320.       inc=1; 
  321.     //da dx a sx 
  322.     else 
  323.       inc=-1; 
  324.  
  325.     //serve per disegnare negli estremi compresi 
  326.     b+=inc; 
  327.  
  328.     for(i=a;i!=b;i=i+inc) 
  329.       //calcoliamo la y 
  330.       comp=(d_b*(i-a))/(d_a)+p0.y; 
  331.       //disegnamo il pixel alle cordinate attuali 
  332.       draw_pixel(surface,i,comp,color); 
  333.  
  334.   //disegnamo verticalmente 
  335.   else 
  336.     d_a=p1.y-p0.y; 
  337.     d_b=p1.x-p0.x; 
  338.     a=p0.y; 
  339.     b=p1.y; 
  340.  
  341.     //dal basso all'alto 
  342.     if(p0.y<p1.y) 
  343.       inc=1; 
  344.     //dall'alto al basso 
  345.     else 
  346.       inc=-1; 
  347.  
  348.     //serve per disegnare negli estremi compresi 
  349.     b+=inc; 
  350.  
  351.     for(i=a;i!=b;i=i+inc) 
  352.       //calcoliamo la x 
  353.       comp=(d_b*(i-a))/(d_a)+p0.x; 
  354.       //disegnamo il pixel alle cordinate attuali 
  355.       draw_pixel(surface,comp,i,color); 
  356.  
  357.   //sblocchiamo lo schermo se necessario 
  358.   if ( SDL_MUSTLOCK(surface) ) 
  359.     SDL_UnlockSurface(surface); 
  360.  
  361. //disegnamo un quadrato di lato lato a partire dal vertice superiore sinistro 

    del colore color 
  362. void draw_square(SDL_Surface *surface,point p, int lato, RGB_Color color) 
  363.   point temp; 
  364.  
  365.   //lato superiore 
  366.   if(p.x+lato<DIM_H) 
  367.     temp.x=p.x+lato; 
  368.   else 
  369.     temp.x=DIM_H-1; 
  370.   temp.y=p.y; 
  371.   draw_line(surface,p,temp,color); 
  372.  
  373.   //lato sx 
  374.   temp.x=p.x; 
  375.   if(p.y+lato<DIM_V) 
  376.     temp.y=p.y+lato; 
  377.   else 
  378.     temp.y=DIM_V-1; 
  379.   draw_line(surface,p,temp,color); 
  380.  
  381.   //lato inferiore 
  382.   if(p.y+lato<DIM_V) 
  383.     p.y+=lato; 
  384.   else 
  385.     p.y=DIM_V-1; 
  386.   if(p.x+lato<DIM_H) 
  387.     temp.x=p.x+lato; 
  388.   else 
  389.     temp.x=DIM_H-1; 
  390.   temp.y=p.y; 
  391.   draw_line(surface,p,temp,color); 
  392.  
  393.   //lato dx 
  394.   if(p.x+lato<DIM_H) 
  395.     p.x+=lato; 
  396.   else 
  397.     p.x=DIM_H-1; 
  398.   temp.x=p.x; 
  399.   temp.y=p.y-lato; 
  400.   draw_line(surface,p,temp,color); 

Spiegazioni del sorgente - il main

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <SDL/SDL.h> 
  4. #include <SDL/SDL_image.h> 
  5.  
  6. #define DIM_H 800 
  7. #define DIM_V 600 
  8. #define BUTTON_SX 1 
  9. #define BUTTON_DX 3 
  10.  
  11. //strutture usate 
  12. typedef struct point 
  13.   int x; 
  14.   int y; 
  15. } point; 
  16.  
  17. typedef struct RGB 
  18.   Uint8 R; 
  19.   Uint8 G; 
  20.   Uint8 B; 
  21. } RGB_Color; 
  22.  
  23. //function grafiche 
  24. void draw_pixel(SDL_Surface *surface, int x, int y, RGB_Color s_color); 
  25. void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color); 
  26. void draw_line(SDL_Surface *surface, point p0, point p1, RGB_Color color); 
  27. 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.
  1. int main() 
  2.   //superfici utilizzate 
  3.   SDL_Surface *screen; 
  4.   SDL_Surface *screen_backup; 
  5.   SDL_Surface *cursor; 
  6.   SDL_Surface *bg_cursor; 
  7.   SDL_Surface *temp; 
Inizializziamo diverse superfici, vedremo nel corso del sorgente come utilizzarle.
  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; 
Richiediamo l'attivazione del sottosistema video di SDL, controllando che non ci siano errori.
  1.   // all'uscita del programma esegui SDL_Quit per risistemare le cose 
  2.   atexit(SDL_Quit); 
Assegnamo alla function SDL_Quit la gestione dell'uscita del programma.
  1.   //dichiariamo un puntatore ad una superficie ed un int a 32 bit per i flag 

      da assegnarle 
  2.   Uint32 flags = SDL_HWSURFACE|SDL_DOUBLEBUF; 
  3.  
  4.   // per rendere disegnabile la Superficie bisogna utilizzare SDL_SetVideoMode 
  5.   if(!( screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags) )) 
  6.     printf("Problemi con il settaggio dello schermo: %s\n", SDL_GetError()); 
  7.     return 1; 
Associamo lo schermo alla superficie screen impostando i flag appropriati.
  1.   //nascondiamo il cursore della finestra 
  2.   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.
  1.   //carica img cursore su superficie temporanea 
  2.   if((temp = IMG_Load("mirino.png"))==NULL) 
  3.     printf("Errore caricamento mirino: %s\n",IMG_GetError()); 
  4.     return 1; 
  5.  
  6.   //Adatta l'img al display attuale e l'assegna ad una superficie 
  7.   cursor = SDL_DisplayFormat(temp); 
  8.  
  9.   //si libera temp 
  10.   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).
  1.   //settiamo trasparente il colore #000000 di cursor 
  2.   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.
  1.   // settiamo le flag per le nuove superfici da creare 
  2.   flags = SDL_HWSURFACE|SDL_SRCCOLORKEY; 
  3.   //creiamo una superficie uguale a cursor per coprire la scia lasciata dal 

      movimento 
  4.   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.
  1.   //creiamo una superficie uguale a screen per salvare lo stato quando 

      cambiamo modalità 
  2.   //di visualizzazione da finestra a tutto schermo 
  3.   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.
  1.   //variabile per il ciclo principale 
  2.   int done = 0; 
  3.   //contatore 
  4.   int i; 
Dichiariamo il flag di fine game loop ed un contatore da usare per i cicli seguenti.
  1.   //limiti della finestra in funzione delle dimensioni del cursore 
  2.   int x_limit = DIM_H-cursor->w; 
  3.   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.
  1.   //definisco rettangolo delle dimensioni di cursor utilizzato per i Blit di 

      superfici 
  2.   //e per memorizzare la posizione del cursore 
  3.   SDL_Rect rect_cur; 
  4.   rect_cur.x = 0; 
  5.   rect_cur.y = 0; 
  6.   rect_cur.w = cursor->w; 
  7.   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.
  1.   //due punti per le rette della griglia 
  2.   point p1,p2; 
Dichiariamo due strutture point che utilizzeremo per indicare il primo e l'ultimo punto di ogni linea della griglia.
  1.   //3 trutture RGB_Color inizializzate a rosso, giallo e bianco 
  2.   RGB_Color red = {0xFF,0x0,0x0}; 
  3.   RGB_Color yellow = {0xFF,0xFF,0x0}; 
  4.   RGB_Color white = {0xFF,0xFF,0xFF}; 
Dichiariamo tre strutture RGB inizializzandole con i valori esadecimali dei colori rosso, giallo e bianco.
  1.   //lato della cella 
  2.   int passo=50; 
La variabile passo indica il lato di ogni cella.
  1.   //disegno righe orizzontali 
  2.   p1.x=0; 
  3.   p1.y=0; 
  4.   p2.x=DIM_H-1; 
  5.   p2.y=0; 
  6.  
  7.   for(i=0;i<=DIM_V/passo;i++) 
  8.     draw_line(screen,p1, p2, white); 
  9.     p1.y+=passo; 
  10.     p2.y+=passo; 
  11.  
  12.   //disegno ultima riga orizzontale 
  13.   p1.x=0; 
  14.   p1.y=DIM_V-1; 
  15.   p2.x=DIM_H-1; 
  16.   p2.y=DIM_V-1; 
  17.   draw_line(screen,p1, p2, white); 
  18.  
  19.   //disegno righe verticali 
  20.   p1.x=0; 
  21.   p1.y=0; 
  22.   p2.x=0; 
  23.   p2.y=DIM_V-1; 
  24.  
  25.   for(i=0;i<DIM_H/passo;i++) 
  26.     draw_line(screen,p1, p2, white); 
  27.     p1.x+=passo; 
  28.     p2.x+=passo; 
  29.  
  30.   //disegno ultima riga verticale 
  31.   p1.x=DIM_H-1; 
  32.   p1.y=0; 
  33.   p2.x=DIM_H-1; 
  34.   p2.y=DIM_V-1; 
  35.   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.
  1.   //vertice sx di un quadrato 
  2.   point cel; 
Dichiariamo una struttura point per memorizzare il vertice della cella nella quale si trova il puntatore del mouse.
  1.   //indici di riga e di colonna della griglia 
  2.   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.
  1.   //dichiariamo una struttura SDL_Event 
  2.   SDL_Event event; 
Dichiariamo una struttura SDL_Event per gestire gli eventi.
  1.   //game loop, ovvero il loop che viene eseguito finchè non si esce 
  2.   while(!done) 
Entriamo nel game loop, ne usciremo quando la variabile done sarà settata ad 1.
  1.     // SDL_WaitEvent attende il prossimo evento 
  2.     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.
  1.     //premendo la x della finestra col mouse si esce 
  2.     if( event.type == SDL_QUIT ) 
  3.       done = 1; 
  4.  
  5.     if( event.type == SDL_KEYDOWN ) 
  6.       //ma si esce anche premendo Esc 
  7.       if ( event.key.keysym.sym == SDLK_ESCAPE ) 
  8.         done = 1; 
Usciamo dal programma quando si clicka con il mouse sulla x della finestra oppure quando si preme il tasto ESC.
  1.  
  2.       //premendo spazio si passa dalla modalità schermo intero alla modalità 
  3.       //finestra e viceversa 
  4.       if ( event.key.keysym.sym == SDLK_SPACE ) 
  5.         //salviamo l'immagine attuale in screen_backup 
  6.         SDL_BlitSurface(screen,NULL,screen_backup,NULL); 
  7.         //cambiamo modalità 
  8.         flags^=SDL_FULLSCREEN; 
  9.         screen = SDL_SetVideoMode(DIM_H, DIM_V, 0,flags); 
  10.         //ripristiniamo l'immagine 
  11.         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.
  1.     //premiamo il tasto sx del mouse, selezioniamo 1 punto 
  2.     if( event.button.button == BUTTON_SX && event.button.type == SDL_MOUSEBUTT

        ONDOWN ) 
Con questo if andiamo a gestire il click sinistro del mouse.
  1.       //calcoliamo l'indice di riga e di colonna della cella puntata 
  2.       row=(rect_cur.x+(cursor->w)/2)/passo; 
  3.       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.

  1.       //calcoliamo le cordinate del vertice superiore sx della cella 
  2.       cel.x=(row*passo)+(passo/2); 
  3.       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.
  1.       //disegnamo il punto selezionato 
  2.       draw_cross(screen,cel,yellow); 
Calcolato il baricentro della cella andiamo a disegnarvi una croce gialla per indicare l'avvenuto riconoscimento.
  1.     //ci spostiamo con il mouse 
  2.     if( event.motion.type == SDL_MOUSEMOTION ) 
  3.       //setto le cordinate del rettangolo del puntatore 
  4.       rect_cur.x=event.button.x; 
  5.       rect_cur.y=event.button.y; 
  6.       rect_cur.w = cursor->w; 
  7.       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.
  1.       //se si vuole andare orizzontalmente oltre la finestra si viene bloccati 
  2.       if(rect_cur.x >= x_limit) 
  3.         rect_cur.x = x_limit; 
  4.       else if(rect_cur.x < 0) 
  5.         rect_cur.x = 0; 
  6.  
  7.       //se si vuole andare verticalmente oltre la finestra si viene bloccati 
  8.       if(rect_cur.y >= y_limit) 
  9.         rect_cur.y = y_limit; 
  10.       else if(rect_cur.y < 0) 
  11.         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.
  1.       //calcoliamo la riga e la colonna su cui ci troviamo 
  2.       row=(rect_cur.x+(cursor->w)/2)/passo; 
  3.       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.
  1.       //se ci troviamo in una cella diversa aggiorniamo la selezione 
  2.       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.
  1.         //coloriamo la vecchia cella di bianco 
  2.         cel.x=old_row*passo; 
  3.         cel.y=old_col*passo; 
  4.         draw_square(screen,cel,passo,white); 
  5.  
  6.         //coloriamo la nuova cella di rosso 
  7.         cel.x=row*passo; 
  8.         cel.y=col*passo; 
  9.         draw_square(screen,cel,passo,red); 
  10.  
  11.         //aggiorniamo i vecchi indici di cella 
  12.         old_row=row; 
  13.         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.
  1.     //recuperiamo la parte di screen prima di muoverci 
  2.     SDL_BlitSurface(screen, &rect_cur, bg_cursor, NULL); 
  3.  
  4.     //disegniamo il cursore alla nuova posizione 
  5.     SDL_BlitSurface(cursor, NULL, screen, &rect_cur); 
  6.  
  7.     //aggiorniamo lo schermo 
  8.     SDL_Flip(screen); 
  9.  
  10.     //disegniamo la parte di screen recuperata per coprire la scia 
  11.     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.

  1.  
  2.   return 0; 
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

  1. //disegna una croce di 5 pixel su di una superficie alle cordinate specificate 
  2. //del colore desiderato 
  3. void draw_cross(SDL_Surface *surface, point p, RGB_Color c_color) 
  4.   //blocchiamo lo schermo se necessario 
  5.   if ( SDL_MUSTLOCK(surface) ) 
  6.     SDL_LockSurface(surface); 
  7.  
  8.   //si disegnano i pixel della croce 
  9.   //IMPORTANTE: non ci sono check sulla posizione! 
  10.   draw_pixel(surface,p.x,p.y,c_color); 
  11.   draw_pixel(surface,p.x+1,p.y,c_color); 
  12.   draw_pixel(surface,p.x-1,p.y,c_color); 
  13.   draw_pixel(surface,p.x,p.y+1,c_color); 
  14.   draw_pixel(surface,p.x,p.y-1,c_color); 
  15.  
  16.   //sblocchiamo lo schermo se necessario 
  17.   if ( SDL_MUSTLOCK(surface) ) 
  18.     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

  1. //disegnamo una linea su di una superficie dal punto p0 al punto p1 di colore 

    color 
  2. void draw_line(SDL_Surface *surface,point p0, point p1, RGB_Color color) 
  3.   int i; 
  4.   int comp; 
  5.   int d_a,d_b; 
  6.   int a,b; 
  7.   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.
  1.   //blocchiamo lo schermo se necessario 
  2.   if ( SDL_MUSTLOCK(surface) ) 
  3.     SDL_LockSurface(surface); 
Ovviamente blocchiamo l'accesso alla superficie per andare ad operare sui pixel.
  1.   //disegnamo un solo punto 
  2.   if(p0.x==p1.x && p0.y==p1.y) 
  3.       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.
  1.    //disegnamo orizzontalmente 
  2.   else if(abs(p1.x-p0.x)>=abs(p1.y-p0.y)) 
  3.     d_a=p1.x-p0.x; 
  4.     d_b=p1.y-p0.y; 
  5.     a=p0.x; 
  6.     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!).

  1.     //da sx a dx 
  2.     if(p0.x<p1.x) 
  3.       inc=1; 
  4.     //da dx a sx 
  5.     else 
  6.       inc=-1; 
  7.  
  8.     //serve per disegnare negli estremi compresi 
  9.     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.
  1.     for(i=a;i!=b;i=i+inc) 
  2.       //calcoliamo la y 
  3.       comp=(d_b*(i-a))/(d_a)+p0.y; 
  4.       //disegnamo il pixel alle cordinate attuali 
  5.       draw_pixel(surface,i,comp,color); 
  6.  
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).
  1.   //disegnamo verticalmente 
  2.   else 
  3.     d_a=p1.y-p0.y; 
  4.     d_b=p1.x-p0.x; 
  5.     a=p0.y; 
  6.     b=p1.y; 
  7.  
  8.     //dal basso all'alto 
  9.     if(p0.y<p1.y) 
  10.       inc=1; 
  11.     //dall'alto al basso 
  12.     else 
  13.       inc=-1; 
  14.  
  15.     //serve per disegnare negli estremi compresi 
  16.     b+=inc; 
  17.  
  18.     for(i=a;i!=b;i=i+inc) 
  19.       //calcoliamo la x 
  20.       comp=(d_b*(i-a))/(d_a)+p0.x; 
  21.       //disegnamo il pixel alle cordinate attuali 
  22.       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.
  1.   //sblocchiamo lo schermo se necessario 
  2.   if ( SDL_MUSTLOCK(surface) ) 
  3.     SDL_UnlockSurface(surface); 
Terminato il disegno dei vari punti non ci resta che sbloccare la superficie precedentemente bloccata.

draw_square

  1. //disegnamo un quadrato di lato lato a partire dal vertice superiore sinistro 

    del colore color 
  2. void draw_square(SDL_Surface *surface,point p, int lato, RGB_Color color) 
  3.   point temp; 
Dichiariamo una struttura point per avere un secondo estremo del lato del quadrato che andremo a disegnare.
  1.   //lato superiore 
  2.   if(p.x+lato<DIM_H) 
  3.     temp.x=p.x+lato; 
  4.   else 
  5.     temp.x=DIM_H-1; 
  6.   temp.y=p.y; 
  7.   draw_line(surface,p,temp,color); 
  8.  
  9.   //lato sx 
  10.   temp.x=p.x; 
  11.   if(p.y+lato<DIM_V) 
  12.     temp.y=p.y+lato; 
  13.   else 
  14.     temp.y=DIM_V-1; 
  15.   draw_line(surface,p,temp,color); 
  16.  
  17.   //lato inferiore 
  18.   if(p.y+lato<DIM_V) 
  19.     p.y+=lato; 
  20.   else 
  21.     p.y=DIM_V-1; 
  22.   if(p.x+lato<DIM_H) 
  23.     temp.x=p.x+lato; 
  24.   else 
  25.     temp.x=DIM_H-1; 
  26.   temp.y=p.y; 
  27.   draw_line(surface,p,temp,color); 
  28.  
  29.   //lato dx 
  30.   if(p.x+lato<DIM_H) 
  31.     p.x+=lato; 
  32.   else 
  33.     p.x=DIM_H-1; 
  34.   temp.x=p.x; 
  35.   temp.y=p.y-lato; 
  36.   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.

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 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/09/sdl_mouse_tile/sdl_mouse_tile.zip (5Kb)
  3. "Programmare in C Guida completa" di Peter Aitken, Bradley L. Jones edizioni Apogeo
    http://www.apogeonline.com/libri/88-7303-850-6/scheda
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

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