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

25.08.2010 - Marcin Milewski
TrudnośćTrudność

Czym jest Fluent Interface

Zacznimy od przykładu.

1
2
3
4
5
6
7
8
9
10
AppStatePtr hof(new HallOfFame());
AppStatePtr menu(new MainMenu());
TransitionEffectPtr effect = TransitionEffect::PreparePinWheelOut()
                                     .duration(2)
                                     .states(menu, hof)
                                     .blades(7)
                                     .fade_alpha(.5, 1)
                                     .rotation(45*2.5)
                                     .Build();
  

Czy powyższy kod zawiera błąd? Nawet gdyby, to znajdziemy go bez najmniejszego problemu. Kolejne parametry podajemy posługując się nazwą, a ich kolejność jest absolutnie dowolna. Ważne, aby całość zakończyła się wywołaniem Build(). Tworzenie instancji efektów w ten sposób jest zdecydowanie czytelniejsze, gdyż nie ma konstruktorów z kilkunastoma parametrami.

W powyższym przykładzie wykorzystaliśmy statyczną metodę PreparePinWheelOut klasy TransitionEffect do utworzenia pomocniczego obiektu, który ma takie metody jak duration, states, blades, itd. Na końcu następuje wywołanie metody Build, która zwraca wskaźnik typu TransitionEffectPtr. Koncepcja jest bardzo prosta, a efekty zdecydowania zadowalające.

Zobaczmy jak prosta jest ta pomocnicza klasa:

Pokaż/ukryj kod
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class tefFluent {
    tefPtr m_instance;
public:
    tefFluent(TransitionEffectType::Type effect_type)
        : m_instance(new TransitionEffect(effect_type)) {
    }
 
    tefFluent(AppStatePtr from_state, AppStatePtr to_state, double duration)
        : m_instance(new TransitionEffect(from_state, to_state, duration)) {
    }
 
    tefFluent& from(AppStatePtr from_state) {
        m_instance->SetFromState(from_state);
        return *this;
    }
 
    tefFluent& to(AppStatePtr to_state) {
        m_instance->SetToState(to_state);
        return *this;
    }
 
    tefFluent& states(AppStatePtr from, AppStatePtr to) {
        m_instance->SetFromState(from);
        m_instance->SetToState(to);
        return *this;
    }
 
    tefFluent& duration(double duration_in_seconds) {
        m_instance->SetDuration(duration_in_seconds);
        return *this;
    }
 
    tefFluent& delay(double before, double after) {
        m_instance->SetDelay(before, after);
        return *this;
    }
 
    tefFluent& type(TransitionEffectType::Type effect_type) {
        m_instance->SetEffectType(effect_type);
        return *this;
    }
 
    tefFluent& fade_alpha(double start, double end) {
        m_instance->SetFadeAlpha(start, end);
        return *this;
    }
 
    tefFluent& blades(int count) {
        m_instance->SetBladesCount(count);
        return *this;
    }
 
    tefFluent& rotation(double angle) {
        m_instance->SetRotation(angle);
        return *this;
    }
 
    tefPtr Build() {
        return m_instance;
    }
};
  

Pozostaje nam już tylko przedstawić implementację klasy TransitionEffect. Zacznijmy od pomocniczych metod związanych z techniką fluent interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
tefFluent TransitionEffect::Prepare(TransitionEffectType::Type effect_type) {
    if (effect_type==TransitionEffectType::FadeIn) {
        return tefFluent(effect_type).fade_alpha(1,0);
    } else if (effect_type==TransitionEffectType::FadeOut) {
        return tefFluent(effect_type).fade_alpha(0,1);
    } else if (effect_type==TransitionEffectType::PinWheelOut) {
        return tefFluent(effect_type).fade_alpha(0,1).blades(1);
    } else {
        return tefFluent(effect_type);
    }
}
 
tefFluent TransitionEffect::PrepareFadeIn(AppStatePtr next_state) {
    return TransitionEffect::Prepare(TransitionEffectType::FadeIn).to(next_state);
}
 
tefFluent TransitionEffect::PrepareFadeOut(AppStatePtr from_state) {
    return TransitionEffect::Prepare(TransitionEffectType::FadeOut).from(from_state);
}
 
tefFluent TransitionEffect::PreparePinWheelOut() {
    return TransitionEffect::Prepare(TransitionEffectType::PinWheelOut);
}
  

Metody te są bardzo krótkie i pozwalając stworzyć część instancji w prostszy sposób. Wykorzystując te metody stworzymy kod łatwiejszy do przeczytania i zrozumienia przez innych programistów. Funkcje do inicjalizacji efektu oraz przetwarzania zdarzeń również są bardzo proste:

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
void TransitionEffect::Start() {
    m_timer = 0;
    m_current_fade_alpha = m_start_fade_alpha;
    if (m_effect_type==TransitionEffectType::FadeIn) {
        //m_current_fade_alpha = 1;   // dużo czarnego i będzie coraz mniej
    } else if (m_effect_type==TransitionEffectType::FadeOut) {
        //m_current_fade_alpha = 0;   // mało czarnego i będzie coraz więcej
    } else if (m_effect_type==TransitionEffectType::PinWheelOut) {
        //m_current_fade_alpha = 0;
        if (m_quadric) {
            gluDeleteQuadric(m_quadric);
        }
        m_quadric = gluNewQuadric();
        m_sweep_angle = 0.0;
    } else {
        assert(false && "Start: Nieznany typ efektu");
    }
}
 
void TransitionEffect::Init() {
    // nie potrzebujemy żadnych zasobów
}
 
void TransitionEffect::ProcessEvents(const SDL_Event& event) {
}
 
AppStatePtr TransitionEffect::NextAppState() const {
    if (IsDone()) {
        if (m_from_state) {
            m_from_state->SetClearBeforeDraw(true)->SetSwapAfterDraw(true);
        }
        return m_to_state;
    } else {
        return AppStatePtr();
    }
}
  

Zostały do ujawnienia najciekawsze z metod - aktualizacja oraz rysowanie. W pierwszej z nich sprawdzamy w którym momencie przetwarzania efektu się znajdujemy i czekamy (delay_before lub delay_after) bądź wyświetlamy efekt. Kod jest zwięzły i przejrzysty:

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
bool TransitionEffect::Update(double dt) {
    // jeżeli efekt się zakończył to nic nie robimy
    if (IsDone() || (m_timer >= m_delay_before + m_duration + m_delay_after)) {
        SetDone(true);
        return IsDone();
    }
    m_timer += dt;
 
    // jeżeli nie upłynął jeszcze m_delay_before
    if (m_timer <= m_delay_before) {
    }
 
    // jeżeli upłynął czas trwania efektu
    if (m_delay_before + m_duration < m_timer) {
        if (m_effect_type==TransitionEffectType::PinWheelOut) {
            m_sweep_angle = 360.0;
        }
    }
 
    // efekt jest aktywny - upłynął czas delay_before, 
    // ale jeszcze nie jest w fazie delay_after
    if (m_delay_before <= m_timer && m_timer <= m_delay_before + m_duration) {
        m_current_fade_alpha += (m_end_fade_alpha-m_start_fade_alpha) * dt/m_duration;
        if (m_effect_type==TransitionEffectType::FadeIn) {
        } else if (m_effect_type==TransitionEffectType::FadeOut) {
        } else if (m_effect_type==TransitionEffectType::PinWheelOut) {
            m_sweep_angle += (360.0/m_blades_count) * dt/m_duration;
            m_current_rot_angle += m_rot_angle * dt/m_duration;
        } else {
            assert(false && "Update: Nieznany typ efektu");
        }
    }
    return !IsDone();
}
  
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com