Gra 2D, część 9: Efektowne przejścia między stanami

25.08.2010 - Marcin Milewski
TrudnośćTrudność

Dlaczego ekran miga?

Po zastosowaniu konstrukcji przedstawionej powyżej wprost do kodu naszej gry okaże się, że ekran miga przy rysowaniu każdej klatki. Dlaczego tak się dzieje? Zauważmy, że w metodzie Effect::Draw zmiana buforów następuje dwukrotnie. Raz przez wywołanie SwapBuffers obecne w pseudokodzie, a drugi raz przez wywołanie tej samej metody wewnątrz from_state->Draw(). Oto sposób, jak poradzić sobie z tą niedogodnością.

Wprowadzimy modyfikację do klasy bazowej dla każdego stanu - czyli klasy AppState. Każdy stan będzie przechowywał informację o tym, czy powinien wyczyścić ekran przed rozpoczęciem rysowania, a także czy po jego zakończeniu powinien wywołać funkcję glSwapBuffers(). Zmieniona klasa prezentuje się następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// AppState.h
class AppState {
public:
    explicit AppState() 
        : m_is_done(false),
          m_clear_before_draw(true),
          m_swap_after_draw(true) {
    }
    
    virtual ~AppState() {}
    
    virtual void Init() = 0;
    virtual void Start() = 0;
    virtual void Draw() = 0;
    virtual bool Update(double dt) = 0;
    virtual void ProcessEvents(const SDL_Event& event) = 0;
 
    virtual boost::shared_ptr<AppState> NextAppState() const = 0;
 
    bool IsDone() const           { return m_is_done; }
    void SetDone(bool value=true) { m_is_done = value; }
 
    bool IsClearBeforeDraw() const { return m_clear_before_draw; }
    bool IsSwapAfterDraw() const { return m_swap_after_draw; }
 
    AppState* SetClearBeforeDraw(bool clear) {
        m_clear_before_draw = clear;
        return this;
    }
 
    AppState* SetSwapAfterDraw(bool swap) {
        m_swap_after_draw = swap;
        return this;
    }
 
private:
    bool m_is_done;            // czy stan się zakończył (i należy przejść do następnego)
    bool m_clear_before_draw;  // czy przed rysowaniem stanu będzie czyszczenie ekranu
    bool m_swap_after_draw;    // czy rysowanie zakończy się podmianą buforów
};
  

Zwróćmy uwagę na metody modyfikujące tę klasę - SetClearBeforeDraw oraz SetSwapAfterDraw. Zamiast void zwracają one wskaźnik na AppState, pod którym jest zmodyfikowana instancja. Czemu to posłuży? Otóż teraz poza zwykłym wywołaniem tych metod:

1
2
3
state->SetClearBeforeDraw(true);
state->SetSwapAfterDraw(true);
  

możemy także użyć wywołania kaskadowego (zobacz method chaining):

1
2
state->SetClearBeforeDraw(true)->SetSwapAfterDraw(true);
  

Wykorzystanie tych metod zobaczymy przy okazji implementacji metody TransitionEffect::Draw. Teraz zobaczmy, jak będą wyglądały metody rysujące w klasach dziedziczących po AppState. Ta modyfikacja oczywiście nie jest obowiązkowa. Można ją pominąć w przypadku klas, które nie będą brały udziału w efektach przejścia. Przypuszczenie jest jednak takie, że będziemy chcieli mieć takie zachowanie dla wszystkich klas. A zatem każdą z nich należy zmienić według następującego szablonu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// plik MainMenu.h
void MainMenu::Draw() {
    if (IsClearBeforeDraw()) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glLoadIdentity();
    }
 
    (...)
 
    if (IsSwapAfterDraw()) {
        SDL_GL_SwapBuffers();
    }
}
  

Ciekawszy efekt - PinWheel

Zanim przejdziemy do przedstawienia szczegółów klasy TransitionEffect powiedzmy słowo o efekcie wiatraczka. Jest on zdecydowanie ciekawszy niż fade-in czy fade-out. Podstawą w tym efekcie są płaty wiatraka, których liczbę będzie można zmieniać. Dla uproszczenia będą one równomiernie rozłożone na okręgu oraz będą zakrywały cały ekran.

Skoro jesteśmy już przy parametrach, to dopuścimy także możliwość zdefiniowania początkowej oraz końcowej wartości dla przezroczystości. Dzięki temu otrzymamy możliwość częściowego lub pełnego wyłączenia tego parametru, czyli w rzeczywistości otrzymanie innych efektów.

Nasz efekt otrzymał wdzięczną nazwę "wiatraczek". Czy zatem nie powinien się kręcić? Oczywiście! Tej możliwości również nie zabraknie w naszej implementacji. Damy programistom możliwość zdefiniowania łącznego kąta o jaki obróci się wiatraczek. Będzie on mógł się kręcić zarówno w lewą jak i prawą stronę - wystarczy przy określaniu kąta dodać (lub nie) znak minus.

Aby dodać programistom jeszcze trochę swobody, udostępnimy możliwość określenie czasu, który efekt powinien odczekać przed rozpoczęciem oraz po jego zakończeniu. W niektórych sytuacjach ten parametr może być bardzo pomocny. Jego głównym podstawowym zastosowaniem ma być możliwość "przytrzymania" efektu po jego zakończeniu. Jednak każdy może wykorzystać według własnej wyobraźni. Domyślnie oba opóźnienia są ustawiane na 0 sekund.

0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com