Serwis Edukacyjny
Nauczycieli
w I-LO w Tarnowie

obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Uaktualniono: 31.07.2022

©2024 mgr Jerzy Wałaszek
I LO w Tarnowie

Zdarzenia

SPIS TREŚCI
Podrozdziały

Co to jest zdarzenie?

Przez zdarzenie (ang. event) rozumiemy wystąpienie pewnej sytuacji, na którą twoja aplikacja powinna w określony sposób zareagować. Takimi zdarzeniami w SDL są m.in.:

Współczesne systemy operacyjne są wielowątkowe i wykonują różne działania w tle. SDL w tle aplikacji użytkownika zbiera informacje o zaistniałych zdarzeniach i umieszcza je w tzw. kolejce zdarzeń (ang. event queue), z której aplikacja może je sobie pobierać w dowolnej chwili. Zdarzenia są tworzone przez różne urządzenia, z którymi współpracuje aplikacja (klawiatura, myszka, joystick, sterownik gier, itp.), przez system operacyjny (zdarzenia związane z obsługą okien) lub przez samą aplikację (tzw. zdarzenia użytkownika).

Zbieranie zdarzeń i umieszczanie ich w kolejce zdarzeń jest w SDL niezależne od platformy (odbywa się poza aplikacją użytkownika) i jest wykonywane przez podsystem obsługi zdarzeń (ang. event subsystem), który jest  inicjalizowany przy inicjalizacji podsystemu wideo przez wywołanie funkcji:

SDL_Init(SDL_INIT_VIDEO);

O podsystemach SDL i ich inicjalizacji pomówimy w osobnym rozdziale.

Dużo zdarzeń jest wspólnych dla różnych platform i są one w SDL obsługiwane w bardzo podobny sposób. Jednak niektóre zdarzenia są specyficzne dla systemu operacyjnego, w którym pracuje aplikacja, np. zdarzenia związane z Windows.

Obsługa zdarzeń pozwala twojej aplikacji na współpracę z użytkownikiem. W dotychczasowych programach korzystaliśmy z tej funkcji w ograniczonym zakresie przy oczekiwaniu na zamknięcie okienka aplikacji. Oczywiście możliwości są dużo większe, co zaraz zobaczysz.


Na początek:  podrozdziału   strony 

Typy zdarzeń

Zdarzenia w SDL są umieszczane w specjalnej strukturze SDL_Event, która jest unią struktur zdarzeń. Czym jest unia powinieneś wiedzieć z kursu programowania w języku C++. Jeśli nie pamiętasz, to wróć tutaj.

Wszystkie struktury zdarzeń posiadają jedno pole wspólne, które określa rodzaj struktury i jednocześnie rodzaj opisanego przez daną strukturę zdarzenia. Pole ma nazwę type. W zależności od zawartości tego pola unia SDL_Event zawiera odpowiednią strukturę. Poniżej w tabelce zebraliśmy wszystkie struktury zdarzeń, które mogą pojawić się w unii SDL_Event:

Typ struktury Nazwa pola Co zawiera
SDL_CommonEvent common dane wspólne.
SDL_WindowEvent window dane zdarzenia okna.
SDL_KeyboardEvent key dane zdarzenia klawiatury.
SDL_TextEditingEvent edit dane zdarzenia edycji.
SDL_TextInputEvent text dane zdarzenia wprowadzania tekstu.
SDL_MouseMotionEvent motion dane zdarzenia ruchu myszki.
SDL_MouseButtonEvent button dane zdarzenia przycisku myszki.
SDL_MouseWheelEvent wheel dane zdarzenia kółka myszki.
SDL_JoyAxisEvent jaxis dane zdarzenia ruchu joysticka.
SDL_JoyBallEvent jball dane zdarzenia ruchu kulką joysticka.
SDL_JoyHatEvent jhat dane zdarzenia ruchu kursorem joysticka.
SDL_JoyButtonEvent jbutton dane zdarzenia przycisku joysticka.
SDL_JoyDeviceEvent jdevice dane zdarzenia urządzenia joysticka.
SDL_ControllerAxisEvent caxis dane zdarzenia ruchu manetką kontrolera gier.
SDL_ControllerButtonEvent cbutton dane zdarzenia przycisku kontrolera gier.
SDL_ControllerDeviceEvent cdevice dane zdarzenia urządzenia kontrolera gier.
SDL_AudioDeviceEvent adevice dane zdarzenia urządzenia audio (>= SDL 2.0.4).
SDL_QuitEvent quit dane zdarzenia zakończenia aplikacji.
SDL_UserEvent user dane zdarzenia zdefiniowanego przez użytkownika.
SDL_SysWMEvent syswm dane zdarzenia systemowego okna.
SDL_TouchFingerEvent tfinger dane zdarzenia dotknięcia palcem.
SDL_MultiGestureEvent mgesture dane gestu wielopalcowego.
SDL_DollarGestureEvent dgesture dane gestu wielopalcowego.
SDL_DropEvent drop dane zdarzenia przeciągania i upuszczania.

Struktury te zajmują w unii SDL_Event tę samą przestrzeń, jednakże w danej chwili unia przechowuje tylko jedną z nich. Dostęp do pól struktur masz poprzez nazwę pola unii, które zawiera daną strukturę. Zobaczmy jak to działa na prostym przykładzie.

Załóżmy, że zdefiniowałeś sobie unię SDL_Event i nadałeś jej nazwę e:

SDL_Event e;

Następnie SDL wypełniło tę unię zdarzeniem ruchu myszki, czyli unia e przechowuje teraz strukturę typu SDL_MouseMotionEvent, która posiada następujące pola danych:

Uint32 type rodzaj zdarzenia; SDL_MOUSEMOTION.
Uint32 timestamp czas zdarzenia.
Uint32 windowID okno ze skupieniem myszki, jeśli istnieje.
Uint32 which identyfikator myszki lub SDL_TOUCH_MOUSEID
Uint32 state stan przycisku
Sint32 x współrzędna X względem okna.
Sint32 y współrzędna Y względem okna.
Sint32 xrel względny ruch w osi X.
Sint32 yrel względny ruch w osi Y.

Pole type jest wspólne dla wszystkich struktur i zawiera stałą określającą rodzaj zdarzenia. Dostęp do tego pola masz bezpośrednio poprzez unię: e.type. Załóżmy, że chcemy odczytać pozycję kursora myszki, którą SDL umieszcza w polach x i y tej struktury. Do danych tych musisz odwołać się kolejno poprzez nazwę unii (e), pole unii zawierające strukturę typu SDL_MouseMotionEvent (motion) i pola danych struktury zdarzenia (x i y):

e.motion.x; // dostęp do pozycji x
e.motion.y; // dostęp do pozycji y

Zasada ta obowiązuje dla wszystkich pól struktur zdarzeń w SDL. Jedynym wyjątkiem jest pole type, które również jest samodzielnym polem unii SDL_Event, zatem można się do niego odwoływać jako e.type, ale możesz również odwołać się jako e.motion.type, ponieważ pole to znajduje się w tym samym miejscu we wszystkich strukturach zdarzeń. Musisz to dobrze zrozumieć.

Gdy poprosisz SDL o pobranie zdarzenia z kolejki, przekazujesz unię SDL_Event. W odpowiedzi SDL wypełnia tę unię odpowiednią strukturą zdarzenia, która jest pobierana z wewnętrznej kolejki zdarzeń. W polu type SDL umieszcza stałą identyfikującą pobrane zdarzenie oraz określa rodzaj struktury zdarzenia umieszczonej w unii SDL_Event. Stałe zdarzeń są następujące:

Rodzaj zdarzenia Struktura zdarzenia Pole unii SDL_Event
SDL_AUDIODEVICEADDED
SDL_AUDIODEVICEREMOVED
SDL_AudioDeviceEvent adevice
SDL_CONTROLLERAXISMOTION SDL_ControllerAxisEvent caxis
SDL_CONTROLLERBUTTONDOWN
SDL_CONTROLLERBUTTONUP
SDL_ControllerButtonEvent cbutton
SDL_CONTROLLERDEVICEADDED
SDL_CONTROLLERDEVICEREMOVED
SDL_CONTROLLERDEVICEREMAPPED
SDL_ControllerDeviceEvent cdevice
SDL_DOLLARGESTURE
SDL_DOLLARRECORD
SDL_DollarGestureEvent dgesture
SDL_DROPFILE
SDL_DROPTEXT
SDL_DROPBEGIN
SDL_DROPCOMPLETE
SDL_DropEvent drop
SDL_FINGERMOTION
SDL_FINGERDOWN
SDL_FINGERUP
SDL_TouchFingerEvent tfinger
SDL_KEYDOWN
SDL_KEYUP
SDL_KeyboardEvent key
SDL_JOYAXISMOTION SDL_JoyAxisEvent jaxis
SDL_JOYBALLMOTION SDL_JoyBallEvent jball
SDL_JOYHATMOTION SDL_JoyHatEvent jhat
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
SDL_JoyButtonEvent jbutton
SDL_JOYDEVICEADDED
SDL_JOYDEVICEREMOVED
SDL_JoyDeviceEvent jdevice
SDL_MOUSEMOTION SDL_MouseMotionEvent motion
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
SDL_MouseButtonEvent button
SDL_MOUSEWHEEL SDL_MouseWheelEvent wheel
SDL_MULTIGESTURE SDL_MultiGestureEvent mgesture
SDL_QUIT SDL_QuitEvent quit
SDL_SYSWMEVENT SDL_SysWMEvent syswm
SDL_TEXTEDITING SDL_TextEditingEvent edit
SDL_TEXTINPUT SDL_TextInputEvent text
SDL_USEREVENT SDL_UserEvent user
SDL_WINDOWEVENT SDL_WindowEvent window

Niektóre struktury są wykorzystywane przez kilka podobnych zdarzeń. Na przykład zdarzenia SDL_MOUSEBUTTONDOWN i SDL_MOUSEBUTTONUP. Pierwsze powstaje, gdy użytkownik nacisnął jeden z przycisków myszki (przycisk w dół, ang. down). Drugie powstaje, gdy użytkownik zwolnił naciśnięty przycisk myszki (przycisk w górę, ang. up). W obu przypadkach wykorzystywana jest ta sama struktura typu SDL_MouseButtonEvent, a dostęp do jej pól w unii SDL_Event następuje poprzez pole button, w którym ta struktura zostaje umieszczona.

Wynika z tego następujący sposób obsługi zdarzeń:

// Definiujemy unię SDL_Event
SDL_Event e;
...
// Każemy SDL pobrać zdarzenie z kolejki do unii e
...
// Przetwarzamy pobrane zdarzenie
switch(e.type)
{
   ZDARZENIE1: ...; // obsługujemy zdarzenia
               break;
   ZDARZENIE2: ...;
               break
   ...
}

Wszystko stanie się jasne dalej w przykładach konkretnych programów


Na początek:  podrozdziału   strony 

Pobieranie zdarzeń

SDL automatycznie umieszcza zdarzenia w swojej wewnętrznej kolejce zdarzeń. Jednak aplikacja użytkownika musi pobrać zdarzenie z tej kolejki, aby móc je obsłużyć w swoim kodzie. Wiemy już, że zdarzenia będą umieszczane w unii SDL_Event. Aby pobrać zdarzenie z kolejki SDL, używamy jednej z kilku dostępnych funkcji:

SDL_WaitEvent(e) – czeka na zdarzenie i pobiera je do unii e.
e – wskaźnik unii SDL_Event, w której będzie umieszczone pobrane zdarzenie

W trakcie czekania aplikacja użytkownika zostaje wstrzymana.

SDL_WaitEventTimeout(e,t) – czeka zadany czas na dostępne zdarzenie, po czym pobiera je do unii e.
e – wskaźnik unii SDL_Event, w której będzie umieszczone pobrane zdarzenie
t – czas oczekiwania w milisekundach

Funkcja zwraca 1, jeśli w czasie t pojawiło się zdarzenie w kolejce. W tym przypadku zdarzenie zostaje pobrane z kolejki i umieszczone w unii e. Jeśli czas oczekiwania upłynął bez zdarzenia, funkcja zwraca 0, a w unii e nic nie jest umieszczane. W trakcie czekania aplikacja użytkownika zostaje wstrzymana.

SDL_PollEvent(e) – Jeśli w kolejce jest zdarzenie, to je pobiera do unii e, jeśli nie ma, to natychmiast wraca.
e – wskaźnik unii SDL_Event, w której będzie umieszczone pobrane zdarzenie

Funkcja zwraca 1, jeśli pobrała zdarzenie z kolejki, lub 0, jeśli kolejka zdarzeń była pusta – w takim przypadku unia e nie jest zmieniana.

Funkcji takich jest więcej w SDL, lecz na razie wystarczą nam te trzy najważniejsze. Dwie pierwsze funkcje czekają aż w kolejce pojawi się jakieś zdarzenie, po czym pobierają je i umieszczają w unii e odpowiednią strukturę zdarzenia wraz z informacjami dodatkowymi o zdarzeniu (np. kod wciśniętego klawisza, współrzędne myszki, wartość wychylenia drążka joysticka, itp.). Pierwsza funkcja czeka nieokreślony czas, a druga czeka przez zadaną liczbę milisekund.

Trzecia funkcja nie czeka na zdarzenie. Jeśli jest w kolejce, to go pobiera i umieszcza w unii e, zwracając wynik 1. Jeśli kolejka jest pusta, to zwraca wynik 0 bez zmiany zawartości unii e. Pozwala to aplikacji użytkownika wykonywać różne działania w trakcie oczekiwania na zdarzenie. Przykładów można tutaj podać mnóstwo: samochód jedzie bez zmiany kierunku aż użytkownik wychyli drążek joysticka, a wtedy aplikacja zmienia odpowiednio kierunek jazdy.

Zdarzenia są pobierane i obsługiwane w pętli, którą nazywamy pętlą zdarzeń (ang. event loop). Na początku pętli wywołujemy funkcję pobierającą zdarzenie, następnie zdarzenie przetwarzamy w poleceniu switch (obsługujemy tylko te zdarzenia, które są nam potrzebne, inne ignorujemy), po czym pętla wykonuje kolejny obieg.

Poniższy program pokazuje sposób reagowania na kliknięcie dowolnym przyciskiem myszki. Przeanalizuj go dokładnie.

C++
// Zdarzenia 1
//------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: KLIKNIJ W OKNO PRZYCISKIEM MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Kasujemy  treść okna i ustawiamy kolor czerwony
  SDL_SetRenderDrawColor(r,255,0,0,255);
  SDL_RenderClear(r);

  // Uaktualniamy okno
  SDL_RenderPresent(r);

  SDL_Event e;
  bool running = true;
  while(running)
  {
    SDL_WaitEvent(&e); // Czekamy na zdarzenie, pobieramy je do unii e
    switch(e.type)     // Sprawdzamy rodzaj odebranego zdarzenia
    {
      case SDL_QUIT:;         // Zamknięcie okna?
      case SDL_MOUSEBUTTONUP: // Zwolnienie przycisku myszki?
                      running = false; // To przerwie pętlę
                      break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Kolejny program demonstruje użycie funkcji SDL_WaitEventTimeout(). Aplikacja czeka przez 2 sekundy na kliknięcie myszką w okno. Jeśli takie zdarzenie się pojawi, okienko pozostanie aktywne. Jeśli w ciągu 2 sekund nie będzie kliknięcia, okno zostanie zamknięte.

C++
// Zdarzenia 2
//------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: KLIKNIJ W OKNO PRZYCISKIEM MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Kasujemy  treść okna i ustawiamy kolor zielony
  SDL_SetRenderDrawColor(r,0,255,0,255);
  SDL_RenderClear(r);

  // Uaktualniamy okno
  SDL_RenderPresent(r);

  SDL_Event e;
  int counter = 20;
  while(counter) // Dopóki licznik > 0
  {
    if(counter == 10) // Gdy czas się kończy, okno na czerwono
    {
      SDL_SetRenderDrawColor(r,255,0,0,255);
      SDL_RenderClear(r);
      SDL_RenderPresent(r);
    }
    if(SDL_WaitEventTimeout(&e,100)) // Czekamy na zdarzenie max 0,1 sekundy, pobieramy je do unii e
      switch(e.type)                 // Badamy rodzaj zdarzenia
      {
        case SDL_QUIT: counter = 0;  // To przerwie pętlę i zakończy aplikację
                       break;
        case SDL_MOUSEBUTTONDOWN:    // Zdarzenia przycisków myszki
        case SDL_MOUSEBUTTONUP:
                       SDL_SetRenderDrawColor(r,255,255,0,255); // Okno na żółto
                       SDL_RenderClear(r);
                       SDL_RenderPresent(r);
                       counter = 20; // Odtwarzamy licznik
                       break;
      }
    else counter--; // Upłynął czas, zmniejszamy licznik
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Następny przykład wykorzystuje funkcję SDL_PollEvent() do realizacji prostej gry w odbijanie piłeczki.

C++
// Zdarzenia 3
//------------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: KLIKNIJ W OKNO PRZYCISKIEM MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Inicjalizujemy generator pseudolosowy
  srand(time(NULL));

  // Prostokąty "piłki" i pola
  SDL_Rect ball  = {0,0,5,5};
  SDL_Rect field = {W_W/2-W_W/4,W_H/2-W_H/4,W_W/2,W_H/2};
  SDL_Rect dummy;

  int dx,dy; // kierunki ruchu piłki

  int fr,fg,fb; // kolor pola

  fr = 0;
  fg = 255;
  fb = 0;

  dx = 1 + rand() % 3;
  do dy = 1 + rand() % 3; while(dy == dx);

  SDL_Event e;
  bool running = true;
  int ddx,ddy;
  while(running)
  {
    // Rysujemy pole gry i piłkę
    SDL_SetRenderDrawColor(r,0,0,0,255);
    SDL_RenderClear(r);

    SDL_SetRenderDrawColor(r,fr,fg,fb,255);
    SDL_RenderFillRect(r,&field);

    SDL_SetRenderDrawColor(r,255,255,255,255);
    SDL_RenderFillRect(r,&ball);

    // Modyfikujemy położenie piłki
    if((ball.x+dx < 0) || (ball.x+ball.w+dx >= W_W)) dx = -dx;
    if((ball.y+dy < 0) || (ball.y+ball.h+dy >= W_H)) dy = -dy;
    ball.x += dx;
    ball.y += dy;

    // Obsługujemy zdarzenia
    if(SDL_PollEvent(&e)) // Jeśli jest zdarzenie w kolejce, pobieramy go
      switch(e.type)      // Sprawdzamy rodzaj zdarzenia
      {
        case SDL_QUIT: running = false;  // To przerwie pętlę
                       break;
        case SDL_MOUSEBUTTONDOWN:        // Naciśnięty przycisk myszki
                       // Zapamiętujemy kierunki ruchu piłki, zmieniając je na przeciwne
                       ddx = 1; if(dx > 0) ddx = -1;
                       ddy = 1; if(dy > 0) ddy = -1;
                       // Wyliczamy nowe przyrosty ruchu piłki w obu osiach
                       dx = 1 + rand() % 3;
                       do dy = 1 + rand() % 3; while(dy == dx);
                       dx *= ddx; // Ustawiamy odpowiednie kierunki ruchu
                       dy *= ddy;
                       field.x--; // Zwiększamy pole
                       field.y--;
                       field.w += 2;
                       field.h += 2;
      }

    // Sprawdzamy, czy piłka dotknęła pola
    if(SDL_IntersectRect(&ball, &field, &dummy))
    {
      // Zerujemy pozycję piłki
      ball.x = ball.y = 0;
      dx = 1 + rand() % 3;
      do dy = 1 + rand() % 3; while(dy == dx);

      // Zwiększamy kolor pola, który również służy nam jako licznik trafień
      fr += 64;
      fb += 64;

      // Sprawdzamy, czy koniec?

      if(fr > 255)
      {
          // Efekt końcowy - migotanie pola
          for(int i = 0; i < 16; i++)
          {
              SDL_SetRenderDrawColor(r,255,0,0,255);
              SDL_RenderFillRect(r,&field);
              SDL_RenderPresent(r);
              SDL_Delay(50);
              SDL_SetRenderDrawColor(r,255,255,255,255);
              SDL_RenderFillRect(r,&field);
              SDL_RenderPresent(r);
              SDL_Delay(50);
          }
          running = false; // To zakończy program
      };
    }

    // Odświeżamy okno
    SDL_RenderPresent(r);
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Sama gra jest nieco głupia, ale celem programu jest obsługa zdarzeń. Zasady gry są następujące:

W oknie rysowany jest zielony prostokąt oraz piłeczka. Twoim zadaniem jest odbijanie piłeczki (dowolnym przyciskiem myszki), tak aby nie uderzyła w pole. Jeśli uderzy 4 razy, gra się kończy. Po każdym odbiciu pole rośnie, również zmienia się kierunek ruchu piłki. Przeanalizuj program, nie powinieneś mieć z nim problemów.

W programie użyta została nowa funkcja:

SDL_IntersectRect(r1,r2,r3) – wylicza część wspólną prostokątów r1 i r2 umieszczając ją w r3.
r1,r2, r3 – wskaźniki struktur SDL_Rect

Funkcja zwraca wartość logiczną true, jeśli prostokąty r1 i r2 przecinają się. W przeciwnym razie zwraca false.

W naszej "grze" funkcję tę wykorzystujemy do sprawdzenia, czy piłka weszła na pole.


Na początek:  podrozdziału   strony 

Zdarzenia myszki

Zdarzenia myszki odwzorowują ruch kursora myszki oraz stan jej przycisków. Poniżej opiszemy podstawowe zdarzenia związane z myszką:

SDL_MOUSEMOTION

Zdarzenie SDL_MOUSEMOTION pojawia się, gdy użytkownik poruszy myszką. W takim przypadku unia SDL_Event zostaje wypełniona strukturą SDL_MouseMotionEvent:

Uint32 type rodzaj zdarzenia; SDL_MOUSEMOTION.
Uint32 timestamp czas zdarzenia.
Uint32 windowID okno ze skupieniem myszki, jeśli istnieje.
Uint32 which identyfikator myszki.
Uint32 state stan przycisków myszki.
Sint32 x współrzędna X względem okna.
Sint32 y współrzędna Y względem okna.
Sint32 xrel względny ruch w osi X.
Sint32 yrel względny ruch w osi Y.

Dostęp do pól tej struktury następuje poprzez pole motion.

Poniższy program reaguje na ruch myszki rysując punkty na ekranie w miejscu otrzymania zdarzenia SDL_MOUSEMOTION. Wykorzystuje pola x i y, które zawierają współrzędne kursora myszki w momencie zdarzenia.

C++
// Zdarzenia 4
// Ruch myszki
//------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: PRZESUWAJ KURSOR MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  bool running = true;
  // Tło okna białe
  SDL_SetRenderDrawColor(r,255,255,255,255);
  SDL_RenderClear(r);
  // Kolor rysowania czarny
  SDL_SetRenderDrawColor(r,0,0,0,255);
  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  SDL_Event e;

  while(running)
  {
    // Obsługujemy zdarzenia
    SDL_WaitEvent(&e); // Czekamy na zdarzenie
    switch(e.type)     // Sprawdzamy rodzaj zdarzenia
    {
      case SDL_QUIT: running = false;  // To przerwie pętlę
                     break;
      case SDL_MOUSEMOTION:            // Ruch myszki
                     // Na współrzędnych kursora myszki rysujemy punkt
                     SDL_RenderDrawPoint(r,e.motion.x,e.motion.y);
                     // Uaktualniamy treść okna
                     SDL_RenderPresent(r);
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Zwróć uwagę, iż szybki ruch myszką tworzy w okienku osobne punkty. Poniższy program eliminuje tę niedogodność przez rysowanie odcinków od poprzednich współrzędnych do aktualnych.

C++
// Zdarzenia 5
// Ruch myszki
//------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: PRZESUWAJ KURSOR MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  bool running = true;
  Sint32 old_x = 0, old_y = 0; // poprzednie współrzędne kursora myszki

  // Tło okna białe
  SDL_SetRenderDrawColor(r,255,255,255,255);
  SDL_RenderClear(r);
  // Kolor rysowania czarny
  SDL_SetRenderDrawColor(r,0,0,0,255);
  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  SDL_Event e;

  while(running)
  {
    // Obsługujemy zdarzenia
    SDL_WaitEvent(&e); // Czekamy na zdarzenie
    switch(e.type)     // Sprawdzamy rodzaj zdarzenia
    {
      case SDL_QUIT: running = false;  // To przerwie pętlę
                     break;
      case SDL_MOUSEMOTION:            // Ruch myszki
                     // Rysujemy linię od poprzednich współrzędnych do obecnych
                     SDL_RenderDrawLine(r,old_x,old_y,e.motion.x,e.motion.y);
                     // Obecne współrzędne zapamiętujemy
                     old_x = e.motion.x;
                     old_y = e.motion.y;
                     // Uaktualniamy treść okna
                     SDL_RenderPresent(r);
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Zdarzenie ruchu myszki udostępnia również informację o stanie przycisków myszki. Informacja ta znajduje się w polu state struktury SDL_MouseMotionEvent. Przyciski badamy w tym polu za pomocą masek:

SDL_BUTTON_LMASK – lewy przycisk myszki

SDL_BUTTON_RMASK – prawy przycisk myszki

Poniższy program sprawdza stan przycisków myszki. Jeśli jest wciśnięty przycisk prawy, to treść okna zostaje wymazana. Jeśli jest wciśnięty przycisk lewy, to zostaje wybrany losowy kolor rysowania.

C++
// Zdarzenia 6
// Ruch myszki
//------------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: PRZESUWAJ KURSOR MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  bool running = true;
  Sint32 old_x = 0, old_y = 0; // poprzednie współrzędne kursora myszki

  // Składowe koloru rysowania
  int rc = 0, gc = 0, bc = 0;

  // Inicjujemy generator pseudolosowy
  srand(time(NULL));

  // Tło okna białe
  SDL_SetRenderDrawColor(r,255,255,255,255);
  SDL_RenderClear(r);
  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  SDL_Event e;

  while(running)
  {
    // Obsługujemy zdarzenia
    SDL_WaitEvent(&e); // Czekamy na zdarzenie
    switch(e.type)     // Sprawdzamy rodzaj zdarzenia
    {
      case SDL_QUIT: running = false;  // To przerwie pętlę
                     break;
      case SDL_MOUSEMOTION:            // Ruch myszki
                     // Rysujemy linię od poprzednich współrzędnych do obecnych
                     SDL_SetRenderDrawColor(r,rc,gc,bc,255);
                     SDL_RenderDrawLine(r,old_x,old_y,e.motion.x,e.motion.y);
                     // Obecne współrzędne zapamietujemy
                     old_x = e.motion.x;
                     old_y = e.motion.y;
                     // Sprawdzamy stan przycisków myszki
                     if(e.motion.state & SDL_BUTTON_LMASK) // Lewy przycisk?
                     {
                         rc = rand() %256;
                         gc = rand() %256;
                         bc = rand() %256;
                     }
                     if(e.motion.state & SDL_BUTTON_RMASK) // Prawy przycisk?
                     {
                          SDL_SetRenderDrawColor(r,255,255,255,255);
                          SDL_RenderClear(r);
                     }

                     // Uaktualniamy treść okna
                     SDL_RenderPresent(r);
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Zwróć uwagę, iż przyciski myszki są wykrywane tylko wraz z jej ruchem. Jeśli zatrzymasz kursor myszki w jednym miejscu okna, to naciskanie przycisków nic nie daje, ponieważ nie występuje zdarzenie ruchu myszki. Dlatego mamy osobne zdarzania związane ze zmianą stanu przycisków myszki:

SDL_MOUSEBUTTONDOWN

To zdarzenie pojawia się, gdy użytkownik naciśnie przycisk myszki. Zawsze występuje jeden raz po naciśnięciu przycisku. Poprzednie zdarzenie ruchu myszki zgłaszało stan przycisków, jednakże w prosty sposób nie informowało nas o samym momencie naciśnięcia przycisku.

SDL_MOUSEBUTTONUP

To zdarzenie pojawia się, gdy użytkownik zwolni naciśnięty poprzednio przycisk myszki.

W obu powyższych zdarzeniach unia SDL_Event zostaje wypełniona strukturą SDL_MouseButtonEvent. Dostęp do pól tej struktury masz poprzez pole button:

Uint32 type rodzaj zdarzenia; SDL_MOUSEBUTTONDOWN lub SDL_MOUSEBUTTONUP.
Uint32 timestamp czas zdarzenia.
Uint32 windowID okno ze skupieniem myszki, jeśli takie jest.
Uint32 which identyfikator myszki.
Uint8 button przycisk, którego stan się zmienił.
Uint8 state stan tego przycisku; SDL_PRESSED lub SDL_RELEASED.
Uint8 clicks 1 dla pojedynczego kliknięcia, 2 dla podwójnego kliknięcia, itd. (>= SDL 2.0.2).
Sint32 x współrzędna X względem okna.
Sint32 y współrzędna Y względem okna.

Kliknięcie przycisku myszki udostępnia informację o położeniu kursora myszki w momencie kliknięcia: pola x i y.

W polu button znajduje się informacja, który z przycisków myszki zmienił swój stan:

SDL_BUTTON_LEFT – lewy przycisk

SDL_BUTTON_RIGHT – prawy przycisk

W polu state masz informację o stanie przycisku, którego stan uległ zmianie:

SDL_PRESSED – przycisk jest wciśnięty

SDL_RELEASED – przycisk jest zwolniony

Nie musisz z tego pola korzystać, ponieważ rodzaj zdarzenia informuje nas o stanie przycisku (SDL_MOUSEBUTTONDOWN – przycisk wciśnięty, SDL_MOUSEBUTTONUP – przycisk zwolniony).

Pole clicks może być przydatne, jeśli chcesz, aby twój program inaczej reagował na pojedyncze kliknięcie, a inaczej na podwójne. Opcja ta działa tylko z SDL2 o wersji co najmniej 2.0.2.

Poprzedni program zmodyfikujemy, tak aby teraz wykorzystywał zdarzenie kliknięcia przyciskiem myszki:

C++
// Zdarzenia 7
// Przyciski myszki
//-----------------

#include <SDL.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: PRZESUWAJ KURSOR MYSZKI",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  bool running = true;
  Sint32 old_x = 0, old_y = 0; // poprzednie współrzędne kursora myszki

  // Składowe koloru rysowania
  int rc = 0, gc = 0, bc = 0;

  // Inicjujemy generator pseudolosowy
  srand(time(NULL));

  // Tło okna białe
  SDL_SetRenderDrawColor(r,255,255,255,255);
  SDL_RenderClear(r);
  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  SDL_Event e;

  while(running)
  {
    // Obsługujemy zdarzenia
    SDL_WaitEvent(&e); // Czekamy na zdarzenie
    switch(e.type)     // Sprawdzamy rodzaj zdarzenia
    {
      case SDL_QUIT: running = false;  // To przerwie pętlę
                     break;
      case SDL_MOUSEMOTION:            // Ruch myszki
                     // Rysujemy linię od poprzednich współrzędnych do obecnych
                     SDL_SetRenderDrawColor(r,rc,gc,bc,255);
                     SDL_RenderDrawLine(r,old_x,old_y,e.motion.x,e.motion.y);
                     // Obecne współrzędne zapamiętujemy
                     old_x = e.motion.x;
                     old_y = e.motion.y;
                     // Uaktualniamy treść okna
                     SDL_RenderPresent(r);
                     break;
      case SDL_MOUSEBUTTONDOWN:         // Przycisk myszki
                     if(e.button.button == SDL_BUTTON_LEFT) // Lewy przycisk?
                     {
                         rc = rand() %256;
                         gc = rand() %256;
                         bc = rand() %256;
                     }
                     else if(e.button.button == SDL_BUTTON_RIGHT) // Prawy przycisk?
                     {
                          SDL_SetRenderDrawColor(r,255,255,255,255);
                          SDL_RenderClear(r);
                          // Uaktualniamy treść okna
                          SDL_RenderPresent(r);
                     }
                     break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Sprawdź, co się dzieje, gdy wychodzisz kursorem myszki poza obszar okna, a później wracasz nim do okienka w innym miejscu. Czy potrafisz wytłumaczyć to zachowanie programu?

Kolejny program jest prostym edytorem graficznym, który umożliwia rysowanie myszką po powierzchni okna. Na spodzie okna jest paleta dostępnych kolorów. Kolor wybierasz z palety przez wskazanie go myszką i kliknięcie lewym przyciskiem. Rysujesz naciskając lewy przycisk na powierzchni okna poza paletą. Prawy przycisk myszki czyści zawartość okna.

C++
// Zdarzenia 8
// Przyciski myszki
//-----------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

// Element palety kolorów
struct PElement
{
    SDL_Rect r;
    int cr,cg,cb;
};
int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: EDYTOR GRAFICZNY",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }
  // Zmienne

  bool running = true;

  // Definiujemy paletę kolorów
  PElement P[16];

  const int PEH = W_H / 20; // Wysokość palety
  const int PEW = W_W / 16; // Szerokość elementu palety

  // Definiujemy kolejne elementy palety
  for(int i = 0; i < 16; i++)
  {
      // Najpierw pozycja zależna od numeru
      P[i].r.x = PEW * i;
      P[i].r.y = W_H - PEH;
      // Teraz wymiary
      P[i].r.w = PEW;
      P[i].r.h = PEH;
      // Teraz kolor zależny od numeru, z którego bierzemy kolejne bity
      // Trzy najmłodsze bity numeru to kolejno kolory b,g,r
      // Czwarty bit określa jasność kolorów, 127 - ciemny, 255 - jasny.
      P[i].cr = (i & 0x01);
      P[i].cg = (i & 0x02) >> 1;
      P[i].cb = (i & 0x04) >> 2;
      if(i & 0x08)
      {
          P[i].cr *= 255;
          P[i].cg *= 255;
          P[i].cb *= 255;
      }
      else
      {
          P[i].cr *= 127;
          P[i].cg *= 127;
          P[i].cb *= 127;
      }
  }

  // Kolor rysowania, początkowo czarny
  int dcr,dcg,dcb;
  dcr = dcg = dcb = 0;

  // Tryb rysowania lub nierysowania
  bool drawing = false;

  // Pozycja początku linii
  int xp,yp;

  // Tło okna białe
  SDL_SetRenderDrawColor(r,255,255,255,255);
  SDL_RenderClear(r);
  // Rysujemy paletę
  for(int i = 0; i < 16; i++)
  {
      // Ustawiamy kolor rysowania na kolor elementu palety
      SDL_SetRenderDrawColor(r,P[i].cr,P[i].cg,P[i].cb,255);
      // Rysujemy prostokąt
      SDL_RenderFillRect(r,&P[i].r);
  }
  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  SDL_Event e;

  while(running)
  {
    // Obsługujemy zdarzenia
    SDL_WaitEvent(&e); // Czekamy na zdarzenie
    switch(e.type)     // Sprawdzamy rodzaj zdarzenia
    {
      case SDL_QUIT: running = false;  // To przerwie pętlę
                     break;
      case SDL_MOUSEBUTTONDOWN:
                     if(e.button.button == SDL_BUTTON_RIGHT) // Czyścimy okienko
                     {
                        SDL_Rect rr;
                        SDL_SetRenderDrawColor(r,255,255,255,255);
                        rr.x = 0;
                        rr.y = 0;
                        rr.w = W_W;
                        rr.h = W_H - PEH;
                        SDL_RenderFillRect(r,&rr);
                        drawing = false;
                        SDL_RenderPresent(r);
                     }
                     else if(e.button.button == SDL_BUTTON_LEFT)
                     {
                         // Jeśli kursor myszki jest nad obszarem rysunku, to włączamy rysowanie
                         // i stawiamy w tym miejscu punkt
                         if(e.button.y < W_H - PEH)
                         {
                             drawing = true;
                             SDL_SetRenderDrawColor(r,dcr,dcg,dcb,255);
                             xp = e.button.x;
                             yp = e.button.y;
                             SDL_RenderDrawPoint(r,xp,yp);
                             SDL_RenderPresent(r);
                         }
                         // W przeciwnym razie odczytujemy kolor kliknietego elementu palety
                         else
                         {
                             int i = e.button.x / PEW;
                             dcr = P[i].cr;
                             dcg = P[i].cg;
                             dcb = P[i].cb;
                         }
                     }
                     break;
        case SDL_MOUSEBUTTONUP:
                     drawing = false;
                     break;
        case SDL_MOUSEMOTION:
                     // Jeśli kursor jest w obszarze rysunkowym i lewy przycisk
                     // myszki jest wciąż wciśniety, rysujemy linię od ostatniego
                     // punktu
                     if(!drawing) break;
                     if(e.motion.state & SDL_BUTTON_LMASK)
                     {
                         if(e.motion.y < W_H - PEH)
                         {
                             SDL_SetRenderDrawColor(r,dcr,dcg,dcb,255);
                             int xk = e.motion.x;
                             int yk = e.motion.y;
                             SDL_RenderDrawLine(r,xp,yp,xk,yk);
                             // Uaktualniamy początek linii
                             xp = xk;
                             yp = yk;
                             SDL_RenderPresent(r);
                         }
                     }
                     else drawing = false;
                     break;
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Na początek:  podrozdziału   strony 

Zdarzenia klawiatury

Zdarzenia klawiatury pozwalają programowi reagować na naciśnięcia lub zwolnienia klawiszy przez użytkownika. Informacje o zdarzeniu klawiaturowym zwraca nam zdarzenia SDL_KEYDOWN (naciśnięcie nowego klawisza) i SDL_KEYUP (zwolnienie klawisza, który został poprzednio naciśnięty). Zdarzenia te wypełnia unię SDL_Event strukturą SDL_KeyboardEvent, która zawiera następujące pola danych:
Uint32 type rodzaj zdarzenia; SDL_KEYDOWN lub SDL_KEYUP.
Uint32 timestamp czas zdarzenia.
Uint32 windowID okno ze skupieniem klawiatury, jeśli takie jest.
Uint8 state stan klawisza; SDL_PRESSED lub SDL_RELEASED.
Uint8 repeat wartość różna od 0, jeśli jest to powtórzenie klawisza.
SDL_Keysym keysym struktura SDL_Keysym reprezentująca naciśnięty lub zwolniony klawisz.

Dostęp do pól tej struktury uzyskujesz poprzez pole unii o nazwie key.

Aby oswoić się z obsługą klawiatury w SDL2, napiszemy prosty program, który mruga okienkiem na czerwono, gdy klawisz jest naciskany, a na zielono, gdy jest zwalniany.

C++
// Zdarzenia 9
// Naciskanie i zwalnianie klawiszy na klawiaturze
//------------------------------------------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: KLAWIATURA",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Tło okna białe
  SDL_SetRenderDrawColor(r,255,255,255,255);
  SDL_RenderClear(r);
  // Uaktualniamy treść okna
  SDL_RenderPresent(r);

  SDL_Event e;
  bool running = true;
  bool key = false;
  while(running)
  {
    // Obsługujemy zdarzenia
    SDL_WaitEvent(&e); // Czekamy na zdarzenie
    switch(e.type)     // Sprawdzamy rodzaj zdarzenia
    {
      case SDL_QUIT:    running = false;  // To przerwie pętlę
                        break;
      case SDL_KEYDOWN: if(!e.key.repeat) // Jeśli to powtórka, nie mrugamy
                        {
                            SDL_SetRenderDrawColor(r,255,0,0,255);
                            key = true;
                        }
                        break;
      case SDL_KEYUP:   SDL_SetRenderDrawColor(r,0,255,0,255);
                        key = true;
                        break;
    }
    if(key)
    {
        key = false;
        SDL_RenderClear(r);
        SDL_RenderPresent(r);
        SDL_Delay(50);
        SDL_SetRenderDrawColor(r,255,255,255,255);
        SDL_RenderClear(r);
        SDL_RenderPresent(r);
    }
  }

  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Zwróć uwagę, że zdarzenia SDL_KEYDOWN i SDL_KEYUP dotyczą każdego klawisza klawiatury, również klawiszy funkcyjnych i SHIFT/CTRL/ALT.

W kolejnym kroku nauczymy się odczytywać kody naciśniętych klawiszy. W tym celu struktura SDL_KeyboardEvent zawiera pole o nazwie keysym, które samo jest strukturą typu SDL_Keysym o następujących polach:

SDL_Scancode scancode fizyczny kod klawisza w SDL; zobacz na SDL_Scancode.
SDL_Keycode sym wirtualny kod klawisza w SDL2; zobacz na SDL_Keycode.
Uint16 mod bieżące modyfikatory klawisza; zobacz na SDL_Keymod.
Uint32 unused nieużywane.

Pierwsze pole o nazwie scancode zawiera tzw. kod matrycowy klawisza, który określa jego położenie na klawiaturze. Ponieważ klawiatury mogą być definiowane w różny sposób w systemie operacyjnym, kod matrycowy nie zawsze odzwierciedla znaczenie naciśniętego klawisza w obowiązującym układzie klawiatury. Kody matrycowe masz zdefiniowane w podanym linku.

Drugie pole o nazwie sym zawiera tzw. kod wirtualny klawisza, który powstaje po przetworzeniu kody matrycowego w obowiązującym układzie klawiatury. Używanie kodów wirtualnych gwarantuje automatyczne dostosowanie się programu do ustawień systemowych dotyczących klawiatury. Kody wirtualne masz dostępne w podanym linku.

Teraz trochę praktyki. Napiszemy prosty program, który rysuje w oknie mały kwadrat i reaguje na klawisze kursora do przesuwania tego kwadratu oraz na klawisz ESC, który kończy działanie programu.

C++
// Zdarzenia 10
// Klawisze kursora i klawisz ESC
//-------------------------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: KLAWISZE KURSORA",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Prostokąt wyświetlany w oknie
  SDL_Rect rr;

  rr.w = rr.h = W_H / 8;
  rr.x = W_W / 2 - rr.w / 2; // Początkowe współrzędne prostokąta;
  rr.y = W_H / 2 - rr.h / 2;

  SDL_Event e;
  bool running = true;
  bool key = true;
  while(running)
  {
    // Obsługujemy zdarzenia
    if(SDL_PollEvent(&e))       // Jeśli jest zdarzenie w kolejce, pobieramy go
    {
        switch(e.type)          // Sprawdzamy rodzaj zdarzenia
        {
            case SDL_QUIT:      running = false;  // To przerwie pętlę
                                break;
            case SDL_KEYDOWN:   switch(e.key.keysym.sym)
                                {
                                    case SDLK_ESCAPE:   running = false;
                                                        break;
                                    case SDLK_LEFT:     if(rr.x > 0)
                                                        {
                                                            key = true;
                                                            rr.x --;
                                                        }
                                                        break;
                                    case SDLK_RIGHT:    if(rr.x + rr.w < W_W)
                                                        {
                                                            key = true;
                                                            rr.x ++;
                                                        }
                                                        break;
                                    case SDLK_UP:       if(rr.y > 0)
                                                        {
                                                            key = true;
                                                            rr.y --;
                                                        }
                                                        break;
                                    case SDLK_DOWN:     if(rr.y + rr.h < W_H)
                                                        {
                                                            key = true;
                                                            rr.y ++;
                                                        }
                                                        break;
                                    default:            break;
                        }
                        break;
        }
        if(key)
        {
            key = false;
            // Tło okna niebieskie
            SDL_SetRenderDrawColor(r,0,0,255,255);
            SDL_RenderClear(r);

            // Żółty prostokąt
            SDL_SetRenderDrawColor(r,255,255,0,255);
            SDL_RenderFillRect(r,&rr);

            // Uaktualniamy treść okna
            SDL_RenderPresent(r);
        }
    }
  }
  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

W podobny sposób piszesz aplikacje reagujące na inne klawisze.


Na początek:  podrozdziału   strony 

Zdarzenia okna

Zdarzenia okna powstają, gdy z oknem użytkownika coś się dzieje. W takim przypadku unia SDL_Event zostaje wypełniona strukturą SDL_WindowEvent. Pola tej struktury są następujące:
Uint32 type rodzaj zdarzenia; SDL_WINDOWEVENT.
Uint32 timestamp czas zdarzenia.
Uint32 windowID okno, którego dotyczy dane zdarzenie.
Uint8 event rodzaj zdarzenia dla okna: SDL_WindowEventID.
Sint32 data1 dane zależne od rodzaju zdarzenia w polu event.
Sint32 data2 dane zależne od rodzaju zdarzenia w polu event.

Zasada obsługi tych zdarzeń jest prosta. Po wykryciu w polu type zdarzenia okna SDL_WINDOWEVENT sprawdzamy zawartość pola event (wcześniej możemy sprawdzić pole windowID, jeśli aplikacja ma kilka okien), w którym znajdziemy numer określający, jakie zdarzenie okna wystąpiło. Numery zdarzeń okna są enumeracją SDL_WindowEventID, które przyjmuje następujące wartości:
SDL_WINDOWEVENT_NONE (nigdy nieużywane).
SDL_WINDOWEVENT_SHOWN okno zostało wyświetlone.
SDL_WINDOWEVENT_HIDDEN okno zostało ukryte.
SDL_WINDOWEVENT_EXPOSED okno zmieniło treść i powinno zostać przerysowane.
SDL_WINDOWEVENT_MOVED okno zostało przesunięte.
SDL_WINDOWEVENT_RESIZED okno zmieniło rozmiar; to zdarzenie zawsze jest poprzedzone przez SDL_WINDOWEVENT_SIZE_CHANGED
SDL_WINDOWEVENT_SIZE_CHANGED okno zmieniło się w wyniku wywołania funkcji API, poprzez system lub w wyniku działań użytkownika; po tym zdarzeniu następuje SDL_WINDOWEVENT_RESIZED, jeśli zmianie uległ rozmiar w wyniku zdarzenia zewnętrznego, np.  działanie użytkownika lub menadżera okien.
SDL_WINDOWEVENT_MINIMIZED okno zostało zminimalizowane.
SDL_WINDOWEVENT_MAXIMIZED okno zostało zmaksymalizowane.
SDL_WINDOWEVENT_RESTORED okno zostało przywrócone do standardowego rozmiaru i położenia.
SDL_WINDOWEVENT_ENTER okno otrzymało skupienie myszki.
SDL_WINDOWEVENT_LEAVE okno utraciło skupienie myszki.
SDL_WINDOWEVENT_FOCUS_GAINED okno otrzymało skupienie klawiatury.
SDL_WINDOWEVENT_FOCUS_LOST okno utraciło skupienie klawiatury.
SDL_WINDOWEVENT_CLOSE menadżer okien żąda jego zamknięcia.
SDL_WINDOWEVENT_TAKE_FOCUS okno otrzymuje propozycję przejęcia skupienia (powinno wywołać SDL_SetWindowInputFocus() na sobie, na podoknie lub zignorować zdarzenie) (>= SDL 2.0.5)
SDL_WINDOWEVENT_HIT_TEST okno miało hittest, który nie był SDL_HITTEST_NORMAL (>= SDL 2.0.5)

W zależności od rodzaju zdarzenia okna pola data1 i data2 zawierają różne dane, związane z tym zdarzeniem.

Poniższy program demonstruje obsługę zdarzeń SDL_WINDOWEVENT_ENTER i SDL_WINDOWEVENT_LEAVE. Pierwsze zdarzenie powstaje, gdy kursor myszki wchodzi w obszar okna. Drugie zdarzenie powstaje, gdy kursor myszki opuszcza obszar okna. W programie będzie rysowany czerwony prostokąt. Gdy myszka znajdzie się nad obszarem okna, kolor prostokąta zmieni się na zielony. Gdy myszka opuści obszar okna, kolor prostokąta zmieni się na niebieski.

C++
// Zdarzenia 11
// Zdarzenia okna
//-------------------------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
const int W_W = 640;
const int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: OKNO",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0);

  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Prostokąt wyświetlany w oknie
  SDL_Rect rr;


  rr.w = W_W / 2;
  rr.h = W_H / 2;
  rr.x = W_W / 4;
  rr.y = W_H / 4;

  SDL_Event e;
  bool running = true;
  bool draw    = true;
  SDL_SetRenderDrawColor(r,255,0,0,0); // wstępny kolor prostokąta
  while(running)
  {
    // Obsługujemy zdarzenia
    if(SDL_PollEvent(&e))       // Jeśli jest zdarzenie w kolejce, pobieramy go
    {
        switch(e.type)          // Sprawdzamy rodzaj zdarzenia
        {
            case SDL_QUIT:      running = false;  // To przerwie pętlę
                                break;
            case SDL_WINDOWEVENT:
                if(e.window.event == SDL_WINDOWEVENT_ENTER) SDL_SetRenderDrawColor(r,0,255,0,255);
                if(e.window.event == SDL_WINDOWEVENT_LEAVE) SDL_SetRenderDrawColor(r,0,0,255,255);
                draw = true;
                break;
            default:            break;
        }
        if(draw)
        {
            draw = false;
            SDL_RenderFillRect(r,&rr);

            // Uaktualniamy treść okna
            SDL_RenderPresent(r);
        }
    }
  }
  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Poniższy program reaguje na zmianę rozmiaru okna i rysuje prostokąt proporcjonalny do nowych rozmiarów. Nowy rozmiar okna pobieramy z pól data1 (szerokość) i data2 (wysokość).

C++
// Zdarzenia 12
// Zdarzenia okna
//-------------------------------

#include <SDL.h>
#include <iostream>

using namespace std;

// Rozmiar okienka
int W_W = 640;
int W_H = 480;

int main(int argc, char* args[])
{
  // Inicjujemy SDL
  if(SDL_Init(SDL_INIT_VIDEO))
  {
    cout << "SDL_Init Error: " << SDL_GetError() << endl;
    return 1;
  }

  // Tworzymy okno
  SDL_Window * w = SDL_CreateWindow("Zdarzenia: OKNO - ROZMIAR",
                                    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                    W_W, W_H, SDL_WINDOW_RESIZABLE);


  if(!w)
  {
    cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl;
    SDL_Quit();
    return 2;
  }

  // Tworzymy kontekst graficzny
  SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC);
  if(!r)
  {
     cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl;
     SDL_DestroyWindow(w);
     SDL_Quit();
     return 3;
  }

  // Prostokąt wyświetlany w oknie
  SDL_Rect rr;

  SDL_Event e;
  bool running = true;
  bool draw    = true;
  while(running)
  {
    // Obsługujemy zdarzenia
    if(SDL_PollEvent(&e))       // Jeśli jest zdarzenie w kolejce, pobieramy go
    {
        switch(e.type)          // Sprawdzamy rodzaj zdarzenia
        {
            case SDL_QUIT:      running = false;  // To przerwie pętlę
                                break;
            case SDL_WINDOWEVENT:
                if(e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
                {
                    draw = true;
                    W_W = e.window.data1; // Pobieramy nowy rozmiar okna
                    W_H = e.window.data2;
                }
                break;
            default:            break;
        }
        if(draw)
        {
            draw = false;
            rr.w = W_W / 2;
            rr.h = W_H / 2;
            rr.x = W_W / 4;
            rr.y = W_H / 4;

            // Tło okna białe
            SDL_SetRenderDrawColor(r,255,255,255,255);
            SDL_RenderClear(r);

            // Prostokąt czerwony;
            SDL_SetRenderDrawColor(r,255,0,0,255);
            SDL_RenderFillRect(r,&rr);

            // Uaktualniamy treść okna
            SDL_RenderPresent(r);
        }
    }
  }
  // Usuwamy kontekst graficzny
  SDL_DestroyRenderer(r);

  // Usuwamy okno
  SDL_DestroyWindow(w);

  // Kończymy pracę z SDL2
  SDL_Quit();

  return 0;
}

Na początek:  podrozdziału   strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2024 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.