Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Piotr Szawdyński
Biblioteki C++

Wprawianie obiektów gry w ruch

[lekcja] Rozdział 3. Wprawianie obiektów gry w ruch - prezentacja złego i dobrego podejścia.
Mając do dyspozycji bibliotekę graficzną - wyświetlenie czegoś na ekranie nie jest specjalną filozofią. Inaczej ma się natomiast sprawa z wprawieniem obiektów w ruch i tej tematyce zostanie poświęcony niniejszy rozdział.

Obiekty sceny

Zanim przejdziemy do właściwej części rozdziału, koniecznym jest przygotowanie struktury, która będzie reprezentowała obiekt na scenie. Struktura dla obiektu w niniejszym rozdziale będzie skromna, ponieważ jedyne co będziemy w niej przechowywali to pozycję w osiach X i Y. Na potrzeby niniejszego rozdziału struktura obiektu będzie więc wyglądała następująco:
C/C++
struct RObiekt
{
    double x;
    double y;
   
    RObiekt( double f_x = 0.0, double f_y = 0.0 )
        : x( f_x )
        , y( f_y )
    { }
}; //struct RObiekt
Do tego wszystkiego dorzucimy jeszcze wektor przechowujący obiekty:
C/C++
typedef std::vector < RObiekt > VObiektyT;
VObiektyT vObiekty;
Przechowywanie wszystkich obiektów sceny w jednym kontenerze ma dla nas istotne znaczenie oraz niesie ze sobą wiele konsekwencji. Tymi konsekwencjami są:
  • możliwość dynamicznego dodawania obiektów na scenę w trakcie działania gry;
  • możliwość obsługiwania wszystkich obiektów sceny przy pomocy tego samego kodu (np. pętlą for);
  • kod jest krótszy, a przez to czytelniejszy;
  • ewentualne błędy łatwiej wykryć, ponieważ albo dotyczą wszystkich obiektów albo żadnego;
  • architektura wymusza aby obiekty posiadały wspólne cechy;
  • personalizacja własności obiektu wymaga znajomości dziedziczenia.
Na chwilę obecną nie będzie nas interesowało personalizowanie własności obiektów, więc kwestie związane z dziedziczeniem nie będą na razie omawiane. W każdym razie praca na zbiorach danych jest dużo wygodniejsza i praktyczniejsza aniżeli indywidualne podejście do każdego obiektu sceny dlatego też użycie kontenera do przechowywania obiektów jest po prostu zalecane i tym samym wskazane.
 
Wektor, który został już przez nas stworzony wypełnimy danymi. Tymi danymi są oczywiście obiekty sceny z którymi będziemy pracować:
C/C++
vObiekty.push_back( RObiekt( 50, 50 ) );
vObiekty.push_back( RObiekt( 100, 250 ) );
vObiekty.push_back( RObiekt( 300, 100 ) );
vObiekty.push_back( RObiekt( 500, 500 ) );
Obiekty jak już wspominałem w niniejszym rozdziale są skromne i posiadają jedynie swoje położenie - na nasze obecne potrzeby jest to w zupełności wystarczające.

Rysowanie obiektów

Istotną zaletą umieszczenia wszystkich obiektów w jednym kontenerze jest możliwość łatwego implementowania wszelkiego rodzaju algorytmów. W tym wypadku będzie to algorytm rysujący obiekty na scenie:
C/C++
oknoAplikacji.Clear();
for( VObiektyT::const_iterator i = vObiekty.begin(); i != vObiekty.end(); ++i )
     oknoAplikacji.Draw( sf::Shape::Circle( i->x, i->y, 10, sf::Color::Red ) );

oknoAplikacji.Display();
W powyższym algorytmie przyjęto, że każdy obiekt będzie reprezentowany przez czerwone kółko - nic nie stoi oczywiście na przeszkodzie by to były sprajty, jednak im kod krótszy tym prostszy do zrozumienia, więc taka też implementacja zostanie zastosowana w niniejszym rozdziale.

Przemieszczanie obiektów

Przemieszczanie obiektów - podejście złe

Jeżeli do tej pory programowałeś tylko i wyłącznie w konsoli bądź tworzyłeś aplikacje okienkowe to z pewnością przemieszczanie obiektu z pozycji obecnej w osi x do pozycji x=100 napisałbyś tak:
C/C++
RObiekt & obiekt = vObiekty.at( 0 );
for(; obiekt.x <= 100; ++obiekt.x )
     oknoAplikacji.Draw( sf::Shape::Circle( obiekt.x, obiekt.y, 10, sf::Color::Red ) );

Jeżeli miałbyś następnie przemieścić obiekt w osi y do pozycji y=300 to zapewne napisałbyś kod następujący:
C/C++
for(; obiekt.y <= 300; ++obiekt.y )
     oknoAplikacji.Draw( sf::Shape::Circle( obiekt.x, obiekt.y, 10, sf::Color::Red ) );

Takie podejście tworzenia ruchu w grze jest złe z kilku powodów:
  • nie masz jak obsłużyć kilku animacji jednocześnie;
  • nie możesz zmienić kierunku ruchu obiektu np. w wyniku wystąpienia zderzenia obiektów (czyli wystąpienia kolizji);
  • animacja nie będzie widoczna w przypadku gdy biblioteka graficzna używa podwójnego buforowania - zobaczysz tylko i wyłącznie jak obiekt zmienił położenie z punktu początkowego do punktu końcowego;
  • wytwarzasz kod, którego nie będziesz w stanie dalej rozwijać.

Przemieszczanie obiektów - podejście poprawne

Skoro wiesz już jak nie należy robić przemieszczania obiektów to czas najwyższy zapoznać się z dobrymi i sprawdzonymi praktykami. Przyjrzyjmy się zatem uważnie naszej głównej pętli gry:
C/C++
while( oknoAplikacji.IsOpened() ) //główna pętla gry
{
    //Obsługa zdarzeń SFML (np. wyjście z aplikacji itp)
    // ... jakiś kod ... //
   
    //obsługa gry (TO NAS INTERESUJE):
    // ... jakiś kod ... //
   
    //Wyświetlanie obiektów (to nas w sumie teraz nie interesuje):
    oknoAplikacji.Clear();
    for( VObiektyT::const_iterator i = vObiekty.begin(); i != vObiekty.end(); ++i )
         oknoAplikacji.Draw( sf::Shape::Circle( i->x, i->y, 10, sf::Color::Red ) );
   
    oknoAplikacji.Display();
}
Istotą poprawnego pisania gier u podstaw jest zrozumienie jaka idea przyświeca głównej pętli gry - tą ideą oczywiście jest renderowanie jednej klatki w każdym jednym przebiegu pętli. Oznacza to, że w jednym przebiegu pętli możemy zmodyfikować pozycje wszystkich obiektów pamiętając jednocześnie, że myślimy o kolejnej klatce, która ma się pokazać użytkownikowi gry, a nie o efekcie docelowym jaki użytkownik chce osiągnąć (np. przemieścić postać z punktu A do punktu B). Przemieszczenie wspomnianej postaci z punktu A do punktu B musi nastąpić w czasie, a zatem klatka po klatce będziemy przesuwali obiekt tak aby znalazł się bliżej punktu B. Przemieszczanie obiektu możemy zrealizować np. tak:
C/C++
void PrzesunObiekt( RObiekt & obiekt, double idzDoX, double idzDoY, double fPredkosc = 1.0 )
{
    if( obiekt.x < idzDoX )
    {
        obiekt.x += fPredkosc; //idź w kierunku celu z podaną prędkością
        if( obiekt.x > idzDoX )
             obiekt.x = idzDoX; //jeżeli przeszliśmy cel to wróć do celu
       
    } else
    if( obiekt.x > idzDoX )
    {
        obiekt.x -= fPredkosc; //idź w kierunku celu z podaną prędkością
        if( obiekt.x < idzDoX )
             obiekt.x = idzDoX; //jeżeli przeszliśmy cel to wróć do celu
       
    }
   
    //Analogicznie oś Y:
    if( obiekt.y < idzDoY )
    {
        obiekt.y += fPredkosc;
        if( obiekt.y > idzDoY )
             obiekt.y = idzDoY;
       
    } else
    if( obiekt.y > idzDoY )
    {
        obiekt.y -= fPredkosc;
        if( obiekt.y < idzDoY )
             obiekt.y = idzDoY;
       
    }
}
Jeszcze lepszym rozwiązaniem będzie, jeżeli umieścimy powyższą funkcję jako metodę struktury RObject i pozbędziemy się argumentów fPredkosc oraz obiekt. Pole fPredkosc przeniesiemy oczywiście do struktury RObiekt tak abyśmy mogli każdemu obiektowi nadawać różne prędkości. Po wspomnianych przeróbkach i złożeniu kodu w całość, aplikacja będzie wyglądała następująco:
C/C++
#include <SFML/Graphics.hpp>

struct RObiekt
{
    double x;
    double y;
    double fPredkosc;
   
    RObiekt( double f_x = 0.0, double f_y = 0.0, double f_fPredkosc = 1.0 )
        : x( f_x )
        , y( f_y )
        , fPredkosc( f_fPredkosc )
    { }
   
    void PrzesunObiekt( double idzDoX, double idzDoY )
    {
        if( x < idzDoX )
        {
            x += fPredkosc;
            if( x > idzDoX )
                 x = idzDoX;
           
        } else
        if( x > idzDoX )
        {
            x -= fPredkosc;
            if( x < idzDoX )
                 x = idzDoX;
           
        }
       
        if( y < idzDoY )
        {
            y += fPredkosc;
            if( y > idzDoY )
                 y = idzDoY;
           
        } else
        if( y > idzDoY )
        {
            y -= fPredkosc;
            if( y < idzDoY )
                 y = idzDoY;
           
        }
    }
}; //struct RObiekt

int main()
{
    sf::RenderWindow oknoAplikacji( sf::VideoMode( 800, 600, 32 ), "Wytwarzanie Gier 2D, C++ | http://cpp0x.pl" );
    oknoAplikacji.UseVerticalSync( true ); //Włączenie synchronizacji pionowej - stała liczba FPS (zazwyczaj 60) - zadziała pod warunkiem, że system nie wymusza na aplikacji wyłączenia tego trybu
   
    typedef std::vector < RObiekt > VObiektyT;
    VObiektyT vObiekty;
   
    vObiekty.push_back( RObiekt( 50, 50, 1.0 ) );
    vObiekty.push_back( RObiekt( 100, 250, 3.5 ) );
    vObiekty.push_back( RObiekt( 300, 100, 5.0 ) );
    vObiekty.push_back( RObiekt( 500, 500, 2.5 ) );
   
    while( oknoAplikacji.IsOpened() )
    {
        sf::Event zdarzenie;
        while( oknoAplikacji.GetEvent( zdarzenie ) )
        {
            if( zdarzenie.Type == sf::Event::Closed )
                 oknoAplikacji.Close();
           
            if( zdarzenie.Type == sf::Event::KeyPressed && zdarzenie.Key.Code == sf::Key::Escape )
                 oknoAplikacji.Close();
           
        } //while
       
        //Przemieszczanie obiektów:
        vObiekty[ 0 ].PrzesunObiekt( 300, 300 );
        vObiekty[ 1 ].PrzesunObiekt( 500, 500 );
        vObiekty[ 2 ].PrzesunObiekt( 100, 100 );
        vObiekty[ 3 ].PrzesunObiekt( 300, 40 );
       
       
        oknoAplikacji.Clear();
        for( VObiektyT::const_iterator i = vObiekty.begin(); i != vObiekty.end(); ++i )
             oknoAplikacji.Draw( sf::Shape::Circle( i->x, i->y, 10, sf::Color::Red ) );
       
        oknoAplikacji.Display();
    }
    return 0;
}

Podsumowanie

W tym rozdziale dowiedziałeś się jak powinien być zorganizowany kod w głównej pętli gry. Ponadto dowiedziałeś się w jaki sposób należy organizować kod gry od postaw tak, aby był on zarówno elastyczny jak i łatwy w utrzymaniu.

Zadanie domowe

Rozbuduj kod przedstawiony w niniejszym rozdziale tak aby dowolnie wybrany przez Ciebie obiekt przemieszczał się do punktu w którym kliknie się myszą na ekranie. Obsługa myszy jest omówiona w kursie SFML (» Kurs SFML 1.6, C++Obsługa zdarzeń - klawiatura, mysz i inne lekcja), natomiast cała reszta wymaga od Ciebie odrobiny kreatywności. Powodzenia! :)
Poprzedni dokument Następny dokument
Budowa szkieletu gry Sterowanie ruchem obiektów