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 graficznejOL035 - 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ł nie jest już rozwijany
Współ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.
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.
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
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
numerycznejSDLK_KP1 SDLK_KP2 SDLK_KP3 SDLK_KP4 SDLK_KP5 SDLK_KP6 SDLK_KP7 SDLK_KP8 SDLK_KP9 SDLK_KP_PERIOD '.' Pozostałe
klawisze
tabliczki
numerycznejSDLK_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
funkcyjneSDLK_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.
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.
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
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
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
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); intSDL_Event
SDL_WaitEvent
(*
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:
- Pole gry jest prostokątem o wymiarach ekranu, w którym prawa ściana została usunięta:
- 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.
- 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.
- Reguły ruchu piłeczki są następujące:
- 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).
- 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,
- Przy uderzeniu w ściany górne lub dolne prędkość bvy zmienia się na przeciwną - następuje odbicie.
- Przy uderzeniu w ścianę lewą prędkość bvx zmienia się na przeciwną - następuje odbicie.
- 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?).
- Jeśli piłeczka wyjdzie środkiem poza prawą krawędź, gra rozpoczyna się od nowa.
- Gracz steruje ruchem paletki za pomocą kursora myszki.
- 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; }
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
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe