Środowisko sterowane zdarzeniami

Powrót do spisu treści

Wymagane jest zapoznanie się z następującymi podrozdziałami:

P019 - Pierwszy program dla Windows
OL031 - instalacja biblioteki SDL w środowisku Dev-C++
OL032 - inicjalizacja biblioteki SDL
OL033 - powierzchnie graficzne w SDL
OL034 - przygotowanie plików własnej biblioteki graficznej
OL035 - rysowanie po powierzchni graficznej
OL036 - algorytm Bresenhama rysowania odcinka
OL037 - obcinanie grafiki do prostokąta
OL038 - podstawowe algorytmy wypełniania obszarów
OL039 - algorytm Smitha
UWAGA!!!

Bieżące opracowanie NIE JEST KURSEM programowania biblioteki SDL. Są to materiały przeznaczone do zajęć na kole informatycznym w I LO w Tarnowie.

Przed pracą z tym rozdziałem utwórz w środowisku Dev-C++ nowy projekt SDL i dołącz do niego pliki SDL_gfx.cpp oraz SDL_gfx.h. Jeśli nie przeczytałeś zalecanych rozdziałów, koniecznie zrób to, a wszystko stanie się jasne. W przeciwnym razie odpuść sobie również ten rozdział.Opis funkcji bibliotecznych znajdziesz w podsumowaniu SDL006.

 

Artykuł w PRZEBUDOWIE


SDLWspółczesne systemy operacyjne Windows, Linux, Mac-OS, Amiga-OS itp. wspierają model sterowania oparty na obsłudze zdarzeń (ang. event driven programming model). Polega to na tym, iż aplikacja nie pracuje na komputerze przez cały czas, lecz "budzi się" w momencie pojawienia się określonego zdarzenia - naciśnięcie klawisza, ruch myszką, kliknięcie przycisku myszki, itp. System tworzy dla aplikacji odpowiednią wiadomość, a aplikacja po odczytaniu tej wiadomości może podjąć właściwe działania. W czasie gdy nie nie ma zdarzeń dla aplikacji system operacyjny zajmuje się obsługą innych aplikacji, które mogą pracować równolegle w systemach wielozadaniowych (ang. multitasking systems). Obsługa zdarzeń pozwala naszej aplikacji komunikować się z użytkownikiem, a zatem może ona odpowiednio reagować na jego działania.

Podane w tym rozdziale informacje traktuj jako instrukcję, do której można później wielokrotnie wracać (podobnie jak rozdział o strukturze SDL_Surface). Nie musisz wszystkiego opanowywać od razu. Raczej rób to stopniowo w miarę potrzeb.

SDL_Event

Najbardziej podstawową strukturą danych, związaną z obsługą zdarzeń w SDL, jest struktura SDL_Event. Jest to unia wszystkich struktur zdarzeń i posiada następującą definicję:

typedef union
{
  Uint8 type;
  SDL_ActiveEvent active;
  SDL_KeyboardEvent key;
  SDL_MouseMotionEvent motion;
  SDL_MouseButtonEvent button;
  SDL_JoyAxisEvent jaxis;
  SDL_JoyBallEvent jball;
  SDL_JoyHatEvent jhat;
  SDL_JoyButtonEvent jbutton;
  SDL_ResizeEvent resize;
  SDL_QuitEvent quit;
  SDL_UserEvent user;
  SDL_SywWMEvent syswm;
} SDL_Event;

Najprościej mówiąc unia jest strukturą danych, która w danej chwili może przechowywać w przydzielonym jej obszarze pamięci jeden, dowolny ze swoich elementów. Na przykład załóżmy, iż zdefiniujemy sobie taką oto unię:

...
union klika
{
  int    i; // obiekt typu liczba całkowita
  double d; // obiekt typu liczba zmiennoprzecinkowa podwójnej recyzji
  char   c; // obiekt typu znak
} zmienna;
...

Utworzona zmienna może zawierać albo liczbę całkowitą, albo liczbę zmiennoprzecinkową, albo znak. Naraz w zmiennej można przechowywać tylko jeden z tych obiektów, ponieważ zajmują one ten sam obszar pamięci - zachodzą na siebie. Jeśli zatem wpiszemy inny obiekt, to nadpisze on obszar pamięci obiektu, który znajdował się w unii poprzednio. Robimy to tak:

...
zmienna.i = 15;            // w unii umieszczamy liczbę całkowitą
cout << zmienna.i << endl; // do zawartości unii odwołujemy się przez pole zmienna.i
zmienna.d = 12.567;        // teraz w unii znajdzie się liczba  zmiennoprzecinkowa
cout << zmienna.d << endl; // do zawartości unii odwołujemy się przez pole zmienna.d
...

W przypadku unii SDL_Event może ona zawierać jedną z wymienionych w deklaracji struktur danych. O tym, która z nich w danym momencie jest w unii decyduje zawartość pola type (pole to z kolei występuje w każdej strukturze zdarzeń, zatem nie nadpisuje ono żadnego innego pola danych tych struktur). Pole type przyjmuje jedną z podanych poniżej wartości:

Zawartość pola type Struktura zdarzenia Opis
SDL_ACTIVEEVENT SDL_ActiveEvent Widoczność okna aplikacji oraz stan skupienia
SDL_KEYDOWN SDL_KeyboardEvent Naciśnięcie klawisza na klawiaturze
SDL_KEYDUP SDL_KeyboardEvent Zwolnienie klawisza na klawiaturze
SDL_MOUSEMOTION SDL_MouseMotionEvent Ruch kursora myszki
SDL_MOUSEBUTTONDOWN SDL_MouseButtonEvent Naciśnięcie przycisku myszki
SDL_MOUSEBUTTONUP SDL_MouseButtonEvent Zwolnienie przycisku myszki
SDL_JOYAXISMOTION SDL_JoyAxisEvent Zdarzenia związane z obsługą joysticka
SDL_JOYBALLMOTION SDL_JoyBallEvent
SDL_JOYHATMOTION SDL_JoyHatEvent
SDL_JOYBUTTONDOWN SDL_JoyButtonEvent
SDL_JOYBUTTONUP SDL_JoyButtonEvent
SDL_QUIT SDL_QuitEvent Zakończenie pracy aplikacji
SDL_SYSWMEVENT SDL_SysWMEvent Zdarzenie specyficzne dla systemu operacyjnego
SDL_VIDEORESIZE SDL_ResizeEvent Zmiana rozmiaru okna aplikacji
SDL_USEREVENT SDL_UserEvent Zdarzenie generowane przez aplikację SDL

Każda wartość pola type określa bieżącą zawartość unii SDL_Event. Na przykład, jeśli w polu type znajdziemy wartość SDL_KEYDOWN, to unia przechowuje strukturę SDL_KeyboardEvent i będziemy do jej pól odwoływać się poprzez pole key. Poniżej opisujemy niektóre ze struktur, z których będziemy korzystać w trakcie kursu. Opis pozostałych struktur znajdziesz w dokumentacji biblioteki SDL.

SDL_ActiveEvent

Struktura SDL_ActiveEvent pojawia się w unii SDL_Event przy zdarzeniach typu SDL_ACTIVEEVENT.

typedef struct
{
  Uint8 type;
  Uint8 gain;
  Uint8 state;
} SDL_ActiveEvent;

Zawiera trzy pola:

type  -  zawiera wartość SDL_ACTIVEEVENT. Jest to to samo pole, co pole type unii SDL_Event (dlaczego?)
gain  - zawiera 0, jeśli zdarzenie oznacza utratę i 1, jeśli zdarzenie oznacza zysk.
state  - zawiera jedną z wartości:
SDL_APPMOUSEFOCUS  - myszka pojawia się nad oknem (gain = 1) lub opuszcza okno aplikacji (gain = 0)
SDL_APPINPUTFOCUS  - okienko uzyskuje (gain = 1) lub traci (gain = 0) skupienie wejścia dla klawiatury (ang. keyboard input focus).
SDL_APPACTIVE  - aplikacja zostaje przywrócona (gain = 1) ze stanu minimalizacji lub przechodzi w ten stan (gain = 0)

Bieżący stan aplikacji SDL program może odczytać wywołując funkcję biblioteczną:

Uint8 SDL_GetAppState(void);

Wynik jest bitową kombinacją stałych (można go testować operacją wynik & stała):

SDL_APPMOUSEFOCUS  - aplikacja posiada skupienie myszki
SDL_APPINPUTFOCUS  - aplikacja posiada skupienie klawiatury
SDL_APPACTIVE  - okno aplikacji jest widoczne na ekranie

SDL_KeyboardEvent

Jeśli wystąpi zdarzenie związane z klawiaturą (naciśnięcie lub zwolnienie określonego klawisza), w unii SDL_Event umieszczona zostanie struktura SDL_KeyboardEvent:

typedef struct
{
  Uint8 type;
  Uint8 state;
  SDL_keysym keysym;
} SDL_KeyboardEvent;

Pola tej struktury są następujące:

type  -  zawiera wartość SDL_KEYDOWN, jeśli zdarzenie związane jest z naciśnięciem klawisza lub SDL_KEYUP przy zdarzeniu związanym ze zwolnieniem klawisza.
state  - zawiera wartość SDL_PRESSED przy wciśniętym klawiszu lub SDL_RELEASED przy zwolnionym klawiszu. Pole state zawiera w sumie tę samą informację co pole type - używa jednakże innych wartości stałych.
keysym  - struktura zawiera informację o naciśniętym klawiszu:
typedef struct
{
  Uint8 scancode;
  SDLKey sym;
  SDLMod mod;
  Uint16 unicode;
} SDL_keysym;
scancode  - określa tzw. kod matrycowy klawisza, czyli stan linii skanujących klawiatury. Ponieważ wartość ta zależy od typu komputera (jest inna dla IBM-PC, Maca i Amigi), raczej z niej nie będziemy korzystać.
sym  -

zawiera stałą definiującą naciśnięty klawisz. Definicje klawiszy dotyczą wszystkich obsługiwanych przez SDL komputerów, stąd może być więcej wartości niż klawiszy na klawiaturze IBM-PC. Pełne definicje znajdziesz w instrukcji dołączanej do dystrybucji SDL. Jednakże nie stosuj w swoich programach klawiszy specyficznych dla określonego komputera, gdyż stracisz przenośność tych programów. Zawartość pola sym jest wykorzystywana np. przy oczekiwaniu na określony klawisz.

Definicja SDLKey Znak ASCII Opis
SDLK_BACKSPACE '\b' kasowanie
SDLK_TAB '\t' tabulacja
SDLK_RETURN '\r' powrót karetki
SDLK_ESCAPE '^[' wycofanie
SDLK_SPACE ' ' spacja
SDLK_EXCLAIM '!' wykrzyknik
SDLK_QUOTEDBL '"' cudzysłów
SDLK_HASH '#' kratka
SDLK_DOLLAR '$' dolar
SDLK_AMPERSAND '&' łącznik
SDLK_QUOTE ''' apostrof
SDLK_LEFTPAREN '(' lewy nawias
SDLK_RIGHTPAREN ')' prawy nawias
SDLK_ASTERISK '*' gwiazdka
SDLK_PLUS '+' znak plus
SDLK_COMMA ',' przecinek
SDLK_MINUS '-' znak minus
SDLK_PERIOD '.' kropka
SDLK_SLASH '/' ukośnik
SDLK_0 '0' 0
SDLK_1 '1' 1
SDLK_2 '2' 2
SDLK_3 '3' 3
SDLK_4 '4' 4
SDLK_5 '5' 5
SDLK_6 '6' 6
SDLK_7 '7' 7
SDLK_8 '8' 8
SDLK_9 '9' 9
SDLK_COLON ':' przecinek
SDLK_SEMICOLON ';' średnik
SDLK_LESS '<' znak mniejszości
SDLK_EQUALS '=' znak równości
SDLK_GREATER '>' znak większości
SDLK_QUESTION '?' pytajnik
SDLK_AT '@' małpa
SDLK_LEFTBRACKET '[' lewa klamra
SDLK_BACKSLASH '\' odwrotny ukośnik
SDLK_RIGHTBRACKET ']' prawa klamra
SDLK_CARET '^' ptaszek
SDLK_UNDERSCORE '_' podkreślenie
SDLK_BACKQUOTE '`' apostrof
SDLK_a 'a' a
SDLK_b 'b' b
SDLK_c 'c' c
SDLK_d 'd' d
SDLK_e 'e' e
SDLK_f 'f' f
SDLK_g 'g' g
SDLK_h 'h' h
SDLK_i 'i' i
SDLK_j 'j' j
SDLK_k 'k' k
SDLK_l 'l' l
SDLK_m 'm' m
SDLK_n 'n' n
SDLK_o 'o' o
SDLK_p 'p' p
Definicja SDLKey Znak ASCII Opis
SDLK_q 'q' q
SDLK_r 'r' r
SDLK_s 's' s
SDLK_t 't' t
SDLK_u 'u' u
SDLK_v 'v' v
SDLK_w 'w' w
SDLK_x 'x' x
SDLK_y 'y' y
SDLK_z 'z' z
SDLK_DELETE '^?' usuwanie
SDLK_KP0   Klawisze
cyfrowe
na tabliczce
numerycznej
SDLK_KP1  
SDLK_KP2  
SDLK_KP3  
SDLK_KP4  
SDLK_KP5  
SDLK_KP6  
SDLK_KP7  
SDLK_KP8  
SDLK_KP9  
SDLK_KP_PERIOD '.' Pozostałe
klawisze
tabliczki
numerycznej
SDLK_KP_DIVIDE '/'
SDLK_KP_MULTIPLY '*'
SDLK_KP_MINUS '-'
SDLK_KP_PLUS '+'
SDLK_KP_ENTER '\r'
SDLK_KP_EQUALS '='
SDLK_UP   strzałka w górę
SDLK_DOWN   strzałka w dół
SDLK_RIGHT   strzałka w prawo
SDLK_LEFT   strzałka w lewo
SDLK_INSERT   wstawianie
SDLK_HOME   na początek
SDLK_END   na koniec
SDLK_PAGEUP   strona w górę
SDLK_PAGEDOWN   strona w dół
SDLK_F1   Klawisze
funkcyjne
SDLK_F2  
SDLK_F3  
SDLK_F4  
SDLK_F5  
SDLK_F6  
SDLK_F7  
SDLK_F8  
SDLK_F9  
SDLK_F10  
SDLK_F11  
SDLK_F12  
SDLK_NUMLOCK   Num Lock
SDLK_CAPSLOCK   Caps Lock
SDLK_SCROLLOCK   Scroll Lock
SDLK_RSHIFT   Prawy SHIFT
SDLK_LSHIFT   Lewy SHIFT
SDLK_RCTRL   Prawy CTRL
SDLK_LCTRL   Lewy CTRL
SDLK_RALT   Prawy ALT
SDLK_LALT   Lewy ALT
mod  - pole przechowuje stan klawiszy kontrolnych wg poniższej tabelki:
Definicja SDLMod Wartość Opis
KMOD_NONE 0x0000 Nie naciśnięto klawiszy kontrolnych
KMOD_LSHIFT 0x0001 Naciśnięty lewy SHIFT
KMOD_RSHIFT 0x0002 Naciśnięty prawy SHIFT
KMOD_LCTRL 0x0040 Naciśnięto lewy CTRL
KMOD_RCTRL 0x0080 Naciśnięto prawy CTRL
KMOD_LALT  0x0100 Naciśnięto lewy ALT
KMOD_RALT  0x0200 Naciśnięto prawy ALT
KMOD_NUM   0x1000 Naciśnięto NUMLOCK
KMOD_CAPS  0x2000 Naciśnięto CAPSLOCK

Do testowania stanu pola mod można dla wygody korzystać z poniższych stałych:

#define KMOD_CTRL  (KMOD_LCTRL|KMOD_RCTRL)
#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT)
#define KMOD_ALT   (KMOD_LALT|KMOD_RALT)
#define KMOD_META  (KMOD_LMETA|KMOD_RMETA)
unicode  - zawiera kod UNICODE wciśniętego klawisza - pole to posiada znaczenie tylko wtedy, gdy włączono tłumaczenie kodów klawiszy na system UNICODE (kody znaków są 16 bitowe) przy pomocy wywołania funkcji SDL_EnableUNICODE(1). Opcja tłumaczenia kodu klawisza na UNICODE nie jest uaktywniana automatycznie, ponieważ wymaga nieco przetwarzania przy każdym naciśnięciu klawisza.

SDL_MouseMotionEvent

Ruchy kursora myszki po obszarze okna aplikacji powodują powstanie zdarzenia typu SDL_MOUSEMOTION.  W unii SDL_Event będzie wtedy umieszczana struktura SDL_MouseMotionEent o następującej definicji:

typedef struct
{
  Uint8 type;
  Uint8 state;
  Uint16 x, y;
  Sint16 xrel, yrel;
} SDL_MouseMotionEvent;
type  -  zawiera wartość SDL_MOUSEMOTION
state  - zawiera informację o bieżącym stanie klawiszy myszki. Klawisze są reprezentowane przez bity pola state i można je odczytać za pomocą wyrażenia state & SDL_BUTTON(x), gdzieSDL_BUTTON() to makro zdefiniowane w bibliotece SDL a x oznacza numer klawisza myszki. Numery klawiszy myszki podajemy poniżej:
Stała klawisza Numer Opis
SDL_BUTTON_LEFT 1 Lewy przycisk myszki
SDL_BUTTON_MIDDLE 2 Środkowy przycisk myszki
SDL_BUTTON_RIGHT 3 Prawy przycisk myszki
SDL_BUTTON_WHEELUP 4 Rolka w górę
SDL_BUTTON_WHEELDOWN 5 Rolka w dół
x, y  - współrzędne kursora myszki w oknie aplikacji
xrel, yrel  - przesunięcie myszki względem ostatniej pozycji x,y raportowanej przez poprzednie zdarzenie SDL_MOUSEMOTION.

SDL_MouseButtonEvent

Struktura SDL_MouseButtonEvent pojawia się w unii SDL_Event jeśli zostanie naciśnięty lub zwolniony klawisz myszki.

typedef struct
{
  Uint8 type;
  Uint8 button;
  Uint8 state;
  Uint16 x, y;
} SDL_MouseButtonEvent;
type  -  zawiera wartość SDL_MOUSEBUTTONDOWN przy naciśnięciu klawisza myszki lub SDL_MOUSEBUTTONUP przy jego zwolnieniu.
button  - zawiera numer naciśniętego klawisza myszki:
Stała klawisza Numer Opis
SDL_BUTTON_LEFT 1 Lewy przycisk myszki
SDL_BUTTON_MIDDLE 2 Środkowy przycisk myszki
SDL_BUTTON_RIGHT 3 Prawy przycisk myszki
SDL_BUTTON_WHEELUP 4 Rolka w górę
SDL_BUTTON_WHEELDOWN 5 Rolka w dół
state  - zawiera stałą SDL_PRESSED dla naciśniętego przycisku myszki lub SDL_RELEASED dla zwolnionego (podobnie jak dla zdarzeń klawiatury).
x,y  - współrzędne kursora myszki w momencie wystąpienia zdarzenia

SDL_QuitEvent

Struktura SDL_QuitEvent jest umieszczana w unii SDL_Event przy zdarzeniu oznaczającym zakończenie pracy aplikacji SDL. Oczywiście twoja aplikacja nie musi się wcale zgodzić na zakończenie - dlatego jest to tylko zdarzenie, informacja, a nie fakt zakończenia działania programu. Struktura SDL_QuitEvent posiada bardzo prostą definicję:

typedef struct
{
  Uint8 type;
} SDL_QuitEvent;
type  -  zawiera wartość SDL_QUIT

SDL_ResizeEvent

Jeśli przy tworzeniu powierzchni graficznej za pomocą funkcji SDL_SetVideoMode() przekazano w parametrach stałą SDL_RESIZABLE, to okno aplikacji można skalować. Po każdej zmianie rozmiaru okna tworzone jest zdarzenie SDL_VIDEORESIZE, a unia SDL_Events zostanie wypełniona strukturą SDL_ResizeEvent. W odpowiedzi na to zdarzenie aplikacja powinna zmienić rozmiar powierzchni graficznej przy pomocy SDL_SetVideoMode().

typedef struct
{
  Uint8 type;
  int w, h;
} SDL_ResizeEvent;
type  -  zawiera wartość SDL_VIDEORESIZE
w  - nowa szerokość powierzchni graficznej
h  - nowa wysokość powierzchni graficznej

Obsługa zdarzeń

System obsługi zdarzeń (ang. event handling) jest inicjowany w bibliotece SDL przy wywołaniu funkcji:

SDL_Init(SDL_INIT_VIDEO);

Powstające w czasie pracy aplikacji SDL zdarzenia są wstawiane do kolejki zdarzeń (ang. event queue). W kolejce tej pozostają aż do momentu, gdy aplikacja odczyta je za pomocą funkcji:

int SDL_PollEvent(SDL_Event * event);
int SDL_WaitEvent(SDL_Event * event);

Funkcja SDL_PollEvent() sprawdza, czy w kolejce zdarzeń są jakieś czekające na obsługę zdarzenia. Jeśli tak, to funkcja zwraca 1, wypełnia unię event odpowiednią strukturą zdarzenia i usuwa zdarzenie z kolejki. W przeciwnym razie zwraca 0. Można ją zatem wykorzystywać w programie do cyklicznego sprawdzania, czy użytkownik wykonał jakąś akcję i odpowiednio ją obsłużyć.

Funkcja SDL_WaitEvent() czeka przez nieokreśloną ilość czasu, aż w kolejce pojawi się jakieś zdarzenie. Jeśli w czasie czekania wystąpił jakiś błąd, zwraca 0. W przeciwnym razie umieszcza w unii event strukturę zdarzenia i zwraca 1. Zdarzenie usuwane jest z kolejki zdarzeń. W trakcie czekania na zdarzenie aplikacja SDL nie zajmuje wiele zasobów procesora komputera, które mogą być wykorzystywane przez inne uruchomione aplikacje.

Oczywiście w bibliotece SDL jest więcej funkcji obsługi zdarzeń, lecz albo nie będziemy z nich korzystać w trakcie naszych lekcji, albo podamy je później, gdy staną się potrzebne. Na razie wystarczą nam w zupełności te dwie.

Teraz czas na przykłady. Napiszemy prosty program, który będzie reagował na lewy i prawy przycisk myszki oraz na zamknięcie okna aplikacji - albo przez kliknięcie ikony zamykania na pasku tytułowym, albo poprzez naciśnięcie klawisza ESC. Przy naciśnięciu lewego przycisku myszki program będzie rysował zielony prostokąt, a przy naciśnięciu prawego przycisku myszki czerwony prostokąt. Zwróć uwagę, iż w programie pozbyliśmy się Windows, co powinno go uczynić przenośnym.

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne
//
// P023 - obsługa zdarzeń
//-----------------------

#include <SDL/SDL_gfx.h>

const int SCRX = 320;      // stałe określające szerokość i wysokość
const int SCRY = 240;      // ekranu w pikselach

int main(int argc, char *argv[])
{

  if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

  atexit(SDL_Quit);

  SDL_Surface * screen;
  
  if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1);

// Tworzymy prostokąt

  SDL_Rect * r = new SDL_Rect;
  
  r->x = screen->w >> 2;
  r->y = screen->h >> 2;
  r->w = screen->w >> 1;
  r->h = screen->h >> 1;
  
  Uint32 color = 0;        // kolor prostokąta  
  SDL_Event event;         // Unia zawierająca struktury zdarzeń
  bool running = true;     // zmienna decyzyjna
  
// pętla obsługi zdarzeń

  while(running)
  {
    SDL_WaitEvent(&event); // czekamy na zdarzenie
    switch(event.type)     // sprawdzamy, co się wydarzyło
    {
      case SDL_KEYDOWN:         if(event.key.keysym.sym != SDLK_ESCAPE) break;
      case SDL_QUIT:            running = false; break;
      case SDL_MOUSEBUTTONDOWN: if(event.button.button == SDL_BUTTON_LEFT)
                                  color = 0x00ff00;
                                else if(event.button.button == SDL_BUTTON_RIGHT)
                                  color = 0xff0000;
                                break;
      case SDL_MOUSEBUTTONUP:   color = 0;
                                break;
    }

// rysujemy prostokąt

    if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) exit(-1);

    SDL_FillRect(screen, r, color);
     
    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
    
// uaktualniamy ekran

    SDL_UpdateRect(screen, 0, 0, 0, 0); 
  }
  return 0;
}

   

Następny program jest prostym edytorem graficznym. Wykorzystuje on zdarzeni związane z ruchem myszki do rysowania punktów po obszarze graficznym. Rysujemy przytrzymując wciśnięty lewy przycisk myszki. Prawym przyciskiem kasujemy treść obszaru graficznego.

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne
//
// P024 - obsługa zdarzeń
//-----------------------

#include <SDL/SDL_gfx.h>

const int SCRX = 320;      // stałe określające szerokość i wysokość
const int SCRY = 240;      // ekranu w pikselach

int main(int argc, char * argv[])
{

  if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

  atexit(SDL_Quit);

  SDL_Surface * screen;
  
  if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1);

  SDL_Event event;      // Unia zawierająca struktury zdarzeń
  bool running = true;  // zmienna decyzyjna
  bool drawing = false; // zmienna decyzyjna

  while(running)
  {
    SDL_WaitEvent(&event); // czekamy na zdarzenie

    if(SDL_MUSTLOCK(screen)) if(SDL_LockSurface(screen) < 0) exit(-1);

    switch(event.type)     // sprawdzamy, co się wydarzyło
    {
      case SDL_KEYDOWN:         if(event.key.keysym.sym != SDLK_ESCAPE) break;
      case SDL_QUIT:            running = false; break;
      case SDL_MOUSEBUTTONDOWN: if(event.button.button == SDL_BUTTON_LEFT)
                                {
                                   gfxMoveTo(event.button.x, event.button.y);
                                   gfxPlot(screen, event.button.x, event.button.y, 0xffffff);
                                   drawing = true;
                                }
                                else if(event.button.button == SDL_BUTTON_RIGHT)
                                {
                                  SDL_FillRect(screen, NULL, 0);
                                  drawing = false;
                                }
                                break;
      case SDL_MOUSEBUTTONUP:   drawing = false;
                                break;
      case SDL_MOUSEMOTION:     if(drawing) gfxLineTo(screen, event.motion.x, event.motion.y, 0xffffff);
                                break;
    }

    if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
    
// uaktualniamy ekran

    SDL_UpdateRect(screen, 0, 0, 0, 0); 
  }
  return 0;
}


 

Ostatni program to prosta gra polegająca na odbijaniu paletką piłeczki. Przy każdym odbiciu piłeczka porusza się nieco szybciej a gracz staje się trochę krótszy. Gra kończy się w chwili opuszczenia przez piłeczkę planszy gry. Ruch palety sterowany jest ruchami myszki. Grę można w każdej chwili zakończyć naciskając klawisz ESC lub klikając ikonę zamknięcia aplikacji na pasku tytułowym.

Ponieważ piłeczka musi się ciągle poruszać, zdarzenia będziemy odczytywać przy pomocy funkcji SDL_PollEvent() a nie SDL_WaitEvent(). Różnica jest taka, iż ta pierwsza nie czeka, aż w kolejce pojawi się zdarzenie do obsługi, lecz w przypadku pustej kolejki zdarzeń po prostu zwraca 0.

Opóźnienie ruchu piłeczki uzyskamy przy pomocy funkcji:

void SDL_Delay(Uint32 ms);

Funkcja SDL_Delay(ms) wstrzymuje wykonanie programu na okres ms milisekund (tysięcznych części sekundy). Jednakże dokładność odmierzania czasu nie jest zbyt wysoka z uwagi na nieuchronne opóźnienia wprowadzane przez system operacyjny. Podczas wstrzymania programu zasoby procesora są dostępne dla innych procesów uruchomionych równolegle w systemie.

Ustalamy następujące zasady gry:

  1. Pole gry jest prostokątem o wymiarach ekranu, w którym prawa ściana została usunięta:
     

     

  2. Gracz jest umieszczany po prawej stronie w odległości 1/16 szerokości ekranu od prawego brzegu ekranu. Wysokość gracza wynosi 1/8 wysokości ekranu. W programie zapamiętujemy współrzędne końców kreski gracza.
     

     

  3. Piłeczka jest kołem o promieniu r = 1/64 szerokości ekranu i zbudowanym z 19 punktów leżących na obwodzie. Kolor konturu ustawiamy na biały, kolor wypełnienia ustawiamy na niebieski. W celu zaoszczędzenia obliczeń, punkty tworzące koło wyznaczamy jeden raz przed rozpoczęciem gry i zapamiętujemy w odpowiedniej tablicy.
     


     

  4. Reguły ruchu piłeczki są następujące:
    1. Piłeczka porusza się w osi x z niezerową prędkością bvx z przedziałów <-3,-1> i <1,3> (prędkość nigdy nie jest równa 0).
    2. Piłeczka porusza się w osi y z niezerową prędkością bvy z przedziałów <-3,-1> i <1,3> (prędkość nigdy nie jest równa 0). Jednostka oznacza 1/256 część szerokości ekranu.  Dzięki temu rozwiązaniu uniezależniamy się od aktualnych wymiarów powierzchni graficznej,
    3. Przy uderzeniu w ściany górne lub dolne prędkość bvy zmienia się na przeciwną - następuje odbicie.
    4. Przy uderzeniu w ścianę lewą prędkość bvx zmienia się na przeciwną - następuje odbicie.
    5. Przy uderzeniu w gracza zmienia się na przeciwną prędkość bvx. Dodatkowo zmieniają się wartości obu prędkości - zmiana kąta odbicia. Opóźnienie ruchu piłeczki maleje o 1/3 ms (jak to zrealizować, skoro rozdzielczość SDL_Delay() wynosi 1ms?).
  5. Jeśli piłeczka wyjdzie środkiem poza prawą krawędź, gra rozpoczyna się od nowa.
  6. Gracz steruje ruchem paletki za pomocą kursora myszki.
  7. Grę można w każdej chwili zakończyć naciśnięciem klawisza ESC.

Na podstawie powyższych reguł napiszemy na kole program gry

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne
//
// P025 - prosta gra zręcznościowa
//--------------------------------

#include <SDL/SDL_gfx.h>
#include <math.h>
#include <time.h>

const int SCRX = 1024;     // stałe określające szerokość i wysokość
const int SCRY = 768;      // ekranu w pikselach

SDL_Surface * screen;
SDL_Event event;

Sint32 py1, py2;           // końce kreski gracza
Sint32 px1;                // współrzędna x gracza

Sint32 bx[19],by[19];      // współrzędne punktów piłki
Sint32 br;                 // promień piłki
Sint32 bx1,by1;            // współrzędne środka piłki
Sint32 obx,oby;            // stare współrzędne środka piłki
Sint32 bvx,bvy;            // prędkość piłki wzdłuż osi x i y
Sint32 bunit;              // 1/256 szerokości ekranu.

// Rysuje planszę gry
//----------------------------------------------------------------
void GameBoard()
{
  if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

  SDL_FillRect(screen, NULL, 0);
  gfxHLine(screen, 0, 0, 0xff0000, screen->w);
  gfxHLine(screen, 0, screen->h - 1, 0xff0000, screen->w);
  gfxVLine(screen, 0, 1, 0xff0000, screen->h - 2);
 
  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

  SDL_UpdateRect(screen, 0, 0, 0, 0);
}

// Inicjuje dane gracza
//----------------------------------------------------------------
void InitPlayer()
{
  px1 = screen->w - (screen->w >> 4);
  py1 = screen->h >> 1;
  py2 = py1 + (screen->h >> 4);
  py1 -= (screen->h >> 4);
}

// Rysuje gracza
//----------------------------------------------------------------
void DrawPlayer(int mode)
{
  Uint32 color = 0x00ff00;
  SDL_Rect r;
  
  if(!mode) color = 0;

  if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

  r.x = px1;
  r.y = py1;
  r.w = (screen->w >> 8) + 1;
  r.h = py2 - py1 + 1;
  SDL_FillRect(screen, &r, color);

  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

  if(mode) SDL_UpdateRect(screen, px1, 1, r.w, screen->h - 2);
}

// Inicjuje dane piłki
//----------------------------------------------------------------
void InitBall()
{

// obliczamy promień piłki - 1/64 szerokości ekranu, ale nie mniej niż 3

  br = screen->w >> 6;
  if(br < 3) br = 3;

// wyliczamy 19 punktów na obwodzie koła o r = br i zapamiętujemy je
// w tablicach bx[] i by[]

  for(int i = 0; i < 19; i++)
  {
    bx[i] = lround(br * cos(6.2831 * i / 18));
    by[i] = lround(br * sin(6.2831 * i / 18));    
  }

// ustawiamy piłkę na środku ekranu

  obx = bx1 = screen->w >> 1;
  oby = by1 = screen->h >> 1;

// obliczamy prędkości w osi x i y - obie niezerowe

  do
  {
    bvx = 3 - (rand() % 7);
    bvy = 3 - (rand() % 7);
  }while((!bvx) || (!bvy));

// wyznaczamy jednostkę ruchu - 1/256 szerokości ekranu, lecz nie mniej niż 1

  bunit = screen->w >> 8;
  if(bunit == 0) bunit = 1;

// wyznaczamy rzeczywiste prędkości

  bvx *= bunit;
  bvy *= bunit;
}

// Rysuje piłeczkę
//----------------------------------------------------------------
void DrawBall(int mode)
{

// obliczamy szerokość i wysokość kwadratu obejmującego piłeczkę

  Sint32 len = (br << 1) + 1;

  if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

// w zależności od trybu rysujemy piłeczkę lub obszar piłki zamazujemy
// czarnym kwadratem

  if(mode)
  {
    gfxMoveTo(bx1 + bx[0], by1 + by[0]);

    for(int i = 1; i < 19; i++)
      gfxClipLineTo(screen, bx1 + bx[i], by1 + by[i], 0xffffff);

    gfxFloodFill(screen, bx1, by1, 0x0000ff);
  }
  else
  {
      SDL_Rect r;

      r.x = bx1 - br;
      r.y = by1 - br;
      r.w = r.h = len;

      SDL_FillRect(screen, &r, 0);

// Zapamiętujemy stare współrzędne do odświeżenia ekranu

      obx = bx1; oby = by1;
  }
  
  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

  if(mode)
  {
    SDL_UpdateRect(screen, obx - br, oby - br, len, len);
    SDL_UpdateRect(screen, bx1 - br, by1 - br, len, len);
  }  
}

//------------------------
// Główna funkcja programu
//------------------------

int main(int argc, char *argv[])
{

  if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

  atexit(SDL_Quit);
  
  if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE|SDL_FULLSCREEN))) exit(-1);

  srand((unsigned)time(NULL));     // inicjujemy generator liczb pseudolosowych
  
  InitPlayer();                    // inicjujemy gracza


  do                               // tutaj rozpoczyna się runda gry
  {
    InitBall();                    // inicjujemy piłkę
    Sint32 ms  = 25;               // opóźnienie
    Sint32 cms = 3;                // odlicza 1/3 milisekundy opóźnienia

    GameBoard();                   // rysujemy planszę
    
    while(true)                    // tutaj obsługujemy rozgrywkę
    {

      while(SDL_PollEvent(&event)) // odczytujemy zdarzenia dopóki są w kolejce
      {

         switch(event.type)
         {
           case SDL_KEYDOWN:       // zdarzenie naciśnięcia klawisza
             if(event.key.keysym.sym == SDLK_ESCAPE) exit(0);
             break;

           case SDL_QUIT: exit(0);

           case SDL_MOUSEMOTION:   // ruch myszki
             DrawPlayer(0);        // kasujemy gracza na starej pozycji
                                   // wyliczamy nową pozycję gracza
             Sint32 pdy = event.motion.y - ((py1 + py2) >> 1);
             py1 += pdy;         
             py2 += pdy;
             break;
         }
      }
      
// tutaj obsługujemy ruch piłeczki, Sprawdzamy gdzie znajdzie się piłka
// po dodaniu do jej współrzędnych przesunięć bvx i bvy

      if(bx1 + bvx - br < 1) bvx = -bvx;               // piłka poza lewą krawędzią planszy
      else if((bx1 + bvx + br >= px1) && (bx1 + bvx + br <= px1 + 3 * bunit) && 
              (by1 + br >= py1) && (by1 - br <= py2))  // piłka uderza w gracza
      {

        do // zmieniamy prędkości
        {
          bvx = 3 - (rand() % 7);
          bvy = 3 - (rand() % 7);
        }while((!bvx) || (!bvy));

        if(bvx > 0) bvx = - bvx; // w poziomie prędkość zawsze w lewo !!!

        bvx *= bunit;
        bvy *= bunit;
        
        cms--;    // wyliczamy nowe opóźnienie ruchów

        if(!cms)  // co trzy odbicia maleje o 1 ms
        {
          cms = 3;
          if(ms > 1) ms--;
        }
      }
      else if(bx1 + bvx + br >= screen->w - 1) break; // piłka opuszcza pole gry
      if((by1 - br + bvy <= 1) || (by1 + br + bvy >= screen->h - 1)) bvy = -bvy;

      DrawBall(0);   // usuwamy piłkę z pola

      bx1 += bvx;    // modyfikujemy jej współrzędne
      by1 += bvy;

      DrawBall(1);   // rysujemy piłkę na nowej pozycji i odświeżamy ekran
      
      DrawPlayer(1); // rysujemy gracza na nowej pozycji i odświeżamy ekran

      SDL_Delay(ms); // czekamy ms milisekund 
    }
  }while(true);
  return 0;
}

Podsumowanie

W tym rozdziale nie dodawaliśmy nowych funkcji do naszej biblioteki graficznej, zatem pliki SDL_gfx.cpp i SDL_gfx.h pozostają bez zmian. Poznane funkcje biblioteki SDL, to:

SDL_GetAppState()  - zwraca bieżący stan aplikacji SDL
SDL_EnableUNICODE(1)  - włącza kodowanie UNICODE naciśniętych klawiszy klawiatury
SDL_PollEvent(event)  - pobiera dostępne zdarzenie z kolejki zdarzeń.
SDL_WaitEvent(event)  - czeka, aż w kolejce zdarzeń pojawi się jakieś zdarzenie i pobiera je
SDL_Delay(ms)  - wstrzymuje wykonywanie programu na ms milisekund



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.