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

25.08.2010 - Marcin Milewski
TrudnośćTrudność

Wszystko o TransitionEffect

Nadszedł moment aby poznać szczegóły implementacji klasy TransitionEffect. Poznamy wszystkie jej pola, metody oraz sposób tworzenia instancji efektu. Na koniec zobaczymy, jak zapanować nad mnogością dostępnych parametrów.

Pola w klasie TransitionEffect zostały podzielone na dwie grupy. Pierwsza z nich jest związana z parametrami, które można ustawiać. To właśnie dzięki nim pisząc jeden efekt otrzymujemy tak naprawdę całą rodzinę efektów. Druga grupa pól jest związana z aktualnym stanem efektu - znajdziemy tutaj m.in. aktualną wartość przezroczystości, czy zegar odmierzający czas trwania stanu. Oto pola, które będą wykorzystywane w zaimplementowanych efektach:

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
// plik TransitionEffect.h
class TransitionEffect : public AppState {
    TransitionEffectType::Type m_effect_type;     // typ efektu
 
    // parametry efektów
    AppStatePtr m_from_state;           // stan, z którego robimy przejście
    AppStatePtr m_to_state;             // stan, do którego robimy przejście; 
                                        // zostanie zwrócony po zakończeniu efektu
    double m_duration;                  // czas trwania efektu (nie wliczając 
                                        // delay_before oraz delay_after)
    double m_delay_before;              // opóźnienie od rozpoczęcia efektu
    double m_delay_after;               // opóźnienie po zakończeniu efektu
    double m_start_fade_alpha;          // przezroczystość na początku efektu
    double m_end_fade_alpha;            // przezroczystość na końcu efektu
    int m_blades_count;                 // liczba ramion wiatraczka; 
                                        // powinna być większa od 0
    double m_rot_angle;                 // łączny kąt, o jaki będzie obrócony wiatraczek
 
    // pola dotyczące aktualnego stanu
    double m_current_fade_alpha;        // aktualna przezroczystość rysowanego czworokąta
    GLUquadricObj* m_quadric;           // obiekt do wyświetlania dysków/sfer/...
    double m_sweep_angle;               // kąt wypełnienia dysku:
                                        // 360 oznacza pełen dysk, a 0 - pusty
    double m_current_rot_angle;         // aktualny kąt obrotu wiatraczka
    double m_timer;                     // czas trwania stanu (pozwala określić, 
                                        // czy już minał czas delay, czy nie)
  
Przedstawienie poszczególnych faz efektu na osi czasu.

Trochę się tych pól nazbierało :) Aby móc definiować różne efekty, każdemu z nich nadajemy w konstruktorze wartość domyślną. Oczywiście każdy efekt może określić z których zmiennych będzie korzystał oraz w jaki sposób. Np. efekt fade-in nigdy nie skorzysta z m_blades_count. Aby umożliwić zmianę tych parametrów dla każdego z nich definiujemy metodę do jego ustawiania. Oto one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// część deklaracji klasy TransitionEffect w pliku TransitionEffect.h
    void SetStates(AppStatePtr from, AppStatePtr to) { 
        SetFromState(from); 
        SetToState(to); 
    }
    void SetFromState(AppStatePtr from) { m_from_state = from; }
    void SetToState(AppStatePtr to) { m_to_state = to; }
    void SetDuration(double duration) { m_duration = duration; }
    void SetDelay(double before, double after) {
        m_delay_before = before;
        m_delay_after = after;
    }
    void SetEffectType(TransitionEffectType::Type type) { m_effect_type = type; }
    void SetFadeAlpha(double start, double end) { 
        m_start_fade_alpha = start; 
        m_end_fade_alpha = end; 
    }
    void SetBladesCount(int count) { m_blades_count = count; }
    void SetRotation(double angle_in_degrees) { m_rot_angle = angle_in_degrees; }
  

Przyjrzyjmy się bliżej sposobowi tworzenia konkretnej instancji obiektu.

1
2
3
4
5
6
7
TransitionEffect effect(TransitionEffectType::Type);
effect.SetFadeAlpha(.5, 1);
effect.SetBladesCount(12);
effect.SetToState(AppStatePtr(new HallOfFame()));
effect.SetFromState(AppStatePtr(new MainMenu()));
effect.SetDuration(2.3);
  

Nie ukrywajmy, że nie wygląda to zbyt zachęcająco. Możemy dodać więcej konstruktorów, które będą niektóre wartości ustawiały na wartości domyślne. Tylko... w jakiej kolejności powinny być przekazywane argumenty do konstruktora? Przypomnijmy, że domyślny argumentu muszą być wymienione jako ostatnie. Nie chcielibyśmy przecież zmuszać programistę do podawania wartości domyślnych dla pięciu argumentów tylko po to, aby mógł zmienić wartość szóstego! To jednak nie koniec kłopotów. Mając około 10 argumentów jak pamiętać ich kolejność? Jeżeli niektóre z nich będą tego samego typu, to o błąd wcale nie trudno. Zobaczmy co autor mógł chcieć powiedzieć:

1
2
3
4
AppStatePtr hof(new HallOfFame());
AppStatePtr menu(new MainMenu());
TransitionEffect effect(TransitionEffectType::Type, hof, menu, .5, 1, 2, 7, 4.5);
  

Czy to wywołanie jest na pewno poprawne? Nikomu nie życzmy szukać błędów w takim kodzie. Trzeba znaleźć inne, lepsze rozwiązanie. Z pomocą przychodzą nam tzw. fluent interfaces.

0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com