Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Rares'
http://rares.tk
Biblioteki C++

Obsługa klawiatury

[kurs] Rozdział 7. Omówienie obsługi klawiatury i przykład sterowania obiektem.

Obsługa klawiatury

Na dzisiejszej lekcji stworzymy coś, co w 0.1 % przypomina grę. Napiszemy program wyświetlający ludka, którym będziemy mogli poruszać. Nie jest to szczyt możliwości SDL, ale poznasz bardzo ważną w programowaniu obsługę klawiatury. W tej lekcji napiszesz 2 programy. Każdy z nich będzie inaczej korzystał z klawiatury. Zdecydowałem, że nie podam Ci tylko jednej wersji, takiej która jest najczęściej używana w grach. Chcę abyś poznał także inną metodę obsługi klawiatury. Przed napisaniem czegokolwiek przygotuj sobie bitmapę ludka w formacie bmp, o wymiarach nie większych niż ma tworzone przez Ciebie okno. Obrazek ma nazwę ludek.bmp.

6.1 Program pierwszy

C/C++
#include <SDL.h>
SDL_Surface * ekran = NULL;
SDL_Surface * ludek = NULL;
SDL_Event zdarzenie;
SDL_Rect LudekDestination;
int x;
bool wyjscie = false;
int main( int argc, char * args[] )
{
    ekran = SDL_SetVideoMode( 640, 480, 32, SDL_SWSURFACE );
    ludek = SDL_LoadBMP( "ludek.bmp" );
    while( !wyjscie )
    {
        while( SDL_PollEvent( & zdarzenie ) )
        {
            if( zdarzenie.type == SDL_QUIT )
            {
                wyjscie = true;
            }
            if( zdarzenie.type == SDL_KEYDOWN )
            {
                switch( zdarzenie.key.keysym.sym )
                {
                case SDLK_RIGHT:
                    x++;
                    break;
                }
            }
        }
        LudekDestination.x = x;
        SDL_BlitSurface( ludek, NULL, ekran, & LudekDestination );
        SDL_Flip( ekran );
    }
    SDL_FreeSurface( ludek );
    SDL_Quit();
    return 0;
}

Po zdefiniowaniu powierzchni, zmiennej obsługującej zdarzenia i prostokątu, definiujemy dwie zmienne. Jedna jest typu bool i znasz ją z poprzedniej lekcji. Jej zadaniem jest sprawdzanie, czy użytkownik nie zamknął programu. Druga zmienna typu int nosi nazwę x i możesz się domyślać, że służy do kontrolowania położenia obrazka w poziomie. Następuje stworzenie okna oraz wczytanie bitmapy do powierzchni ludek. Potem rozpoczyna się pętla znana z poprzedniej lekcji, która obsługuje komunikaty i jest przerywana w momencie zamknięcia programu przez użytkownika. W pętli pojawia się kolejna, zagnieżdżona pętla, która wykonuje się dopóki program otrzymuje komunikaty. W środku pętli pojawiają się instrukcja warunkowa, którą również już znasz. Sprawdza ona czy użytkownik zamknął program, jeśli tak następuje zmiana wartości zmiennej wyjscie na prawdziwą. Niżej pojawia się kolejna instrukcja warunkowa, która sprawdza czy rodzajem zdarzenia nie był wciśnięty klawisz. Sprawdzanie odbywa się tak samo jak w górnej instrukcji. Najpierw nazwa zmiennej event, czyli zdarzenie, po kropce typ, a po znaku równości rodzaj zdarzenia. Typ SDL_KEYDOWN to połączenie dwu wyrazów - key i down, co oznacza po polsku wciśnięty klawisz. Jeśli powyższa instrukcja warunkowa zwróci wartość prawdziwą następuje instrukcja switch, która sprawdza jaki klawisz został wciśnięty, aby wykonać dla niego odpowiednie akcje. Warunkiem instrukcji switch jest struktura zdarzenie, którą zadeklarowaliśmy na początku programu. zdarzenie.key.keysym.sym - po nazwie zmiennej podajemy kategorię zdarzenia. Tutaj jest to key, co w angielskim oznacza klawisz. Potem podajemy rodzaj zdarzenia. Jest to keysym. Ta część struktury odpowiada za raportowanie zdarzeń klawiatury.  Ostatnia część struktury keysym to sym, której zadaniem jest odbieranie wciśnięć klawiatury. Funkcja ta zwraca flagę wciśniętego klawisza. Poszczególne akcje dla określonych klawiszy wykonujemy po case:

C/C++
case SDL_RIGHT:
x++;
break;

W naszym przykładzie obsługujemy tylko jeden klawisz, żeby nie komplikować. Jest to prawa strzałka. Akcja jaka jest wykonywana dla tego klawisza to dodanie do zmiennej x jeden. Zmienna ta kontroluje położenie sprite'a w poziomie. Potem następuje przerwanie instrukcji case. Poza pętlą widzimy takie wyrażenie: LudekDestination.x = x;. Na początku zdefiniowaliśmy to jako prostokąt. Pamiętasz z poprzednich lekcji, że jest to struktura, której możemy ustalić: położenie w poziomie (x), położenie w pionie (y), szerokość (w) i wysokość (h). W powyższym programie ustalamy tylko położenie w poziomie. Jest tu pewna różnica w porównaniu z poprzednimi programami wyświetlającymi sprite'y. Wtedy ustalaliśmy stałą liczbę, np. 100 lub 200. Tutaj podstawiamy zmienną x. Na początku zmienna x = 0 i właśnie w tym miejscu będzie leżeć sprite w poziomie. Będzie leżał w lewym górnym rogu okna. Wcześniej podczas wciśnięcia klawisza strzałki w prawo program dodawał do zmiennej x jeden. Stąd można łatwo wywnioskować, że sprite się poruszy. Kiedy zmienna x będzie miała wartość 100 to sprite będzie oddalony do lewej części okna o 100 pikseli. Będzie to oczywiście widać. Co do położenia sprite'a i kontrolowania tego są do wyjaśnienia jeszcze dwie rzeczy. Po pierwsze moglibyśmy zamiast napisać tak:

C/C++
case SDLK_RIGHT:
x++;
break;
Zrobi ć to tak:
case SDLK_RIGHT:
LudekDestination.x++;
break;

Dzięki temu po pętli zamiast pisać tak: LudekDestination.x = x; - nie pisać nic. Może to się wydawać prostsze i wygodniejsze, ale moim zdaniem lepiej utworzyć sobie zmienną x, kontrolować ją i jej wartość podstawiać do położenia x prostokąta. Jest to o wiele wygodniejsze, gdyż np. gdy mamy kilka plików w projekcie i w kilku korzystamy z położenia x prostokąta lepiej jest operować na zwykłej zmiennej niż na prostokącie. Jak Ty będziesz pisał, zależy tylko od Ciebie, ale radzę jednak by używać zmiennej. Co do typu tej zmiennej. Na razie jest to int, ale w przyszłości będziesz musiał dokładniej kontrolować położenie sprite'a i będziesz musiał użyć ułamków. W takim przypadku najlepiej użyć typu zmiennoprzecinkowego float. Można użyć i double, jednak wątpię czy będzie Ci potrzebna aż taka precyzja w SDL.

Po drugie, zaraz po pętli mamy podstawienie wartości x do położenia x prostokąta. Być może zastanawiasz się czemu nie możemy napisać tego przed pętlą. Przecież tak robiliśmy we wcześniejszych programach. Niestety ważne jest tu położenie tej instrukcji. Kiedy instrukcja leżałaby przed pętlą, program zaktualizowałby położenie tylko raz - przed wykonaniem pętli. Zmienna x ma na początku wartość 0, więc tyle zostałoby podstawione pod położenie w poziomie prostokąta. Potem pętla wykonywała by się i owszem zmienna x zmieniałaby wartość, ale sprite nie przesuwałby się, gdyż program nie wracałby do tamtej instrukcji żeby zaktualizować położenie. Wiesz przecież, że w C++ jest tak, że gdy program wykonuje działanie, a potem pętlę nie wróci do tamtej instrukcji podczas obiegów pętli ani po przerwaniu jej, chyba że wywołalibyśmy funkcję wewnątrz pętli zmieniającej wartość zmiennej. Jednak po co robić funkcję w celu zmiany wartości jednej zmiennej?

Takie działanie jest bezsensowne (są wyjątki, ale tym się nie zajmujemy). Wiesz już, że pętla musi co obieg aktualizować położenie prostokąta, więc taka instrukcja musi leżeć wewnątrz pętli, a czy leży na początku czy jej końcu jest nieważne.
Potem następuje skopiowanie sprite'a na ekran, przełączenie bufora i wyświetlenie sprite'a. Nie ma już żadnych instrukcji do wykonania w pętli, więc następuje kolejny obieg. Skompiluj program i uruchom go. Naciśnij prawą strzałkę. Z pewnością zauważyłeś, że program nie działa tak jak powinien. Jeśli nie, to wciśnij jeszcze raz prawą strzałkę. Sprite minimalnie się przesunął. A teraz przytrzymaj klawisz. I co? Nie tego oczekiwałeś? Sprite nie przesuwa się kiedy przytrzymasz klawisz. Spróbuj teraz wciskać szybko klawisz. Sprite przesuwa się, ale z lekkim zacięciem. Im szybciej wciskasz przycisk tym szybciej przesuwa się sprite. Pewnie nadal nie o to Ci chodziło. Takie rozwiązanie nie sprawdza się w grach, kiedy np. jedziesz samochodem, albo idziesz kimś naprzód. Wyobraź sobie ciągłe wciskanie klawisza, aby tylko się przesunąć. To by nie przeszło. Takie coś sprawdza się, gdy musisz coś potwierdzić, zaakceptować lub wybrać w grze. Prawda? Tam tak to działa, gdyż drugi sposób się do tego nie nadaje. Przedstawię teraz kod drugiego programu, który teraz będzie działać tak jak chciałeś.

6.2 Drugi program

C/C++
#include <SDL.h>
SDL_Surface * ekran = NULL;
SDL_Surface * ludek = NULL;
SDL_Event zdarzenie;
SDL_Rect LudekDestination;
Uint8 * keystate = SDL_GetKeyState( NULL );
int x;
bool wyjscie = false;
int main( int argc, char * args[] )
{
    ekran = SDL_SetVideoMode( 640, 480, 32, SDL_SWSURFACE );
    ludek = SDL_LoadBMP( "ludek.bmp" );
    while( !wyjscie )
    {
        while( SDL_PollEvent( & zdarzenie ) )
        {
            if( zdarzenie.type == SDL_QUIT )
            {
                wyjscie = true;
            }
        }
        if( keystate[ SDLK_RIGHT ] )
        {
            x++;
        }
        LudekDestination.x = x;
        SDL_BlitSurface( ludek, NULL, ekran, & LudekDestination );
        SDL_Flip( ekran );
    }
    SDL_FreeSurface( ludek );
    SDL_Quit();
    return 0;
}

Pierwszą istotną różnicą jest ta linijka na początku programu: Uint8 *keystate = SDL_GetKeyState(NULL);. Tutaj deklarujemy wskaźnik typu unsigned int o nazwie keystate (key state - stan klawisza). Wskaźnik ten inicjujemy od razu funkcją SDL_GetKeyState, której zadaniem jest - jak możesz się domyślać - sprawdzanie stanu klawiszy. Jeśli jakiś klawisz został wciśnięty funkcja ta zwraca wskaźnik do tablicy przechowującej stan klawiszy. Dzięki temu możemy sprawdzić jaki został wciśnięty klawisz i wykonać dla niego odpowiednie akcje. Dalej jest tak samo, jak we wcześniejszym programie, lecz kiedy dochodzimy do pętli głównej można zauważyć, że uległa zmianie. Przede wszystkim fragment mający na celu sprawdzenie czy został wciśnięty klawisz prawej strzałki jest przeniesiony poza pętlę komunikatów. Znajduje się teraz bezpośrednio w pętli głównej, dzięki czemu akcje klawiatury są wykonywane cały czas i nie są przerywane przez pętlę komunikatów. Sama funkcja sprawdzająca czy nie wcisnęliśmy prawej strzałki uległ zmianie:

C/C++
if( keystate[ SDLK_RIGHT ] )
{
    x++;
}

Jak wiesz keystate to wskaźnik do funkcji zwracającej tablicę przechowującą stan klawiszy. Ponieważ jest to tablica piszemy tu nazwę wskaźnika oraz obok niego w nawiasach kwadratowych flagę klawisza, który chcemy sprawdzić. Nazwa flagi każdego klawisza w SDL zaczyna się od SDLK (K to key, czyli klawisz). RIGHT oznacza prawą strzałkę. Jeśli chciałbyś dowiedzieć się jak nazywają się flagi innych klawiszy możesz zawsze zerknąć do Google. Wystarczy wpisać SDLKey. Pierwszy link zawiera spis wszystkich klawiszy. Przykładowo podam niektóre klawisze:
  • SDLK_LEFT - lewa strzałka
  • SDLK_UP - strzałka w górę
  • SDLK_RETURN - enter
  • SDLK_SPACE - spacja
  • SDLK_TAB - tabulator
  • SDLK_ASTERISK - gwiazdka
  • SDLK_COLON - dwukropek
  • SDLK_A - a

I tak dalej. Jeśli funkcja stwierdzi, że prawa strzałka została wciśnięta - dodaje do zmiennej x 1.
Poprzedni dokument Następny dokument
Obsługa zdarzeń Obsługa myszy