Gra 2D, część 8: Dobrze mieć wybór... poziomu

25.08.2010 - Marcin Milewski
TrudnośćTrudność

Powtarzanie sprite'ów

Moment momoment, ale klasa sprite nie posiada metody SetReapeat(), więc wywoładnie m_horizontal_road_sprite->SetRepeat(...) spowoduje błąd kompilacji! Tak, zgadza się. Aż do teraz nie potrzebowaliśmy powtarzania sprite'ów ani w pionie ani w poziomie. Teraz jest potrzebna, aby drogi były rysowane prawidłowo (czyli sprite był powtarzany, a nie rozciągany).

Rysowania sprite'a polegało na odpowiednim wywołaniu funkcji DrawSprite() klasy Renderer. Samo wywołanie się nie zmienia, ale dodajemy warunek, że należy z niego korzystać tylko jeżeli mechanizm powtarzania jest wyłączony. Oto odpowiedni kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Plik Sprite.cpp
 
void Sprite::DrawCurrentFrame(double x, double y, double width, double height) {
    // jeżeli powtarzanie jest nieaktywne, to rysujemy normalnie
    if (m_width_repeat < 0 && m_height_repeat < 0) {
        Engine::Get().GetRenderer()->DrawSprite(
            m_data.left + m_data.width * m_current_frame, m_data.bottom,
            m_data.width, m_data.height, x, y, width, height, m_data.layer);
        return;
    }
 
    // (...)
  

W powyższym kodzie pojawiły się dwie zmienne określające rozmiary, w jakich powinien być rysowany sprite podczas powtarzania. Zwykle będziemy chcieli ustawiać obok siebie sprite w oryginalnych rozmiarach (najczęściej jest to rozmiar jednego kafla). Zmienne te należy dodać do deklaracji klasy Sprite. Potrzebne będą też odpowiednie metody do manipulowania nimi:

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
    // Plik Sprite.h
 
    /* Ustawia rozmiary sprite'a powyżej których będzie on powtarzany -- rysowany ponownie.
       Wywołanie:
         sprite->SetRepeat(.5, 1.0);
         sprite->DrawCurrentFrame(x, y, 2.0, 3.5);
       Oznacza, że w poziomie sprite zostanie powtórzony czterokrotnie, 
       a w pionie 3,5-krotnie. Każdy z narysowanych sprite'ów (być może poza ostatnim) 
       będzie miał wymiary 0.5 X 1.0
 
       Przypuszczalnie najczęściej będzie wykorzystywane następujące wywołanie:
         sprite->SetRepeat(tile_width, tile_height);
         sprite->DrawCurrentFrame(x, y, any_width, any_height);
       Zostanie wówczas narysowany prostokąt o wymiarach any_width X any_height wypełniony 
       sprite'mi standartowej wielkości jednego kafla.
 
       Argumenty width oraz height powinny być ściśle większe od 0. 
       Zachowanie dla pozostałych wartości jest nieokreślone.
     */
    void SetRepeat(double width, double height);
 
    /* Po wywołaniu tej metody, sprite będzie rozciągany, a nie powtarzany. */
    void ResetRepeat();
 
private:
    // (...)
    double m_width_repeat;     // szerokość, powyżej której sprite będzie ponownie rysowany
    double m_height_repeat;    // wysokość, powyżej której sprite będzie ponownie rysowany
};
  

Należy oczywiście pamiętać o zainicjalizowaniu zmiennej w konstruktorze. Domyślnie ustawiamy brak powtarzania. Implementacja wspomnianych funkcji także jest prosta:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Plik Sprite.cpp
Sprite::Sprite(const SpriteConfigData& data) :
    m_data(data), m_current_frame(0), m_current_frame_duration(0.0),
    m_width_repeat(-1), m_height_repeat(-1) {
 
}
 
// (...)
 
void Sprite::SetRepeat(double width, double height) {
    m_width_repeat = width;
    m_height_repeat = height;
}
 
void Sprite::ResetRepeat() {
    m_height_repeat = m_width_repeat = -1;
}
  

Omówiliśmy już przypadek kiedy powtarzanie sprite'a jest wyłączone. Napisaliśmy także interfejs, z którego mogą korzystać inne klasy. Nadszedł wiec czas, aby przedstawić implementację rysowania powtórzonego sprite'a w pionie lub poziomie (jest to kontynuacja kodu metody Sprite::DrawCurrentFrame()). Na początek zdefiniujmy kilka pomocniczych wartości:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    // Plik Sprite.cpp, kontynuacja implementacji metody Sprite::DrawCurrentFrame()
 
    // (...)
    // poniżej znajduje się kod dla rysowania z włączonym powtarzaniem
 
    // rozmiary powtarzanego sprite'a
    const double rep_width = m_width_repeat;                       
    const double rep_height = m_height_repeat;
 
    // liczba całych sprite'ów na każdej z osi
    const int whole_in_x = static_cast<int>(width / rep_width);    
    const int whole_in_y = static_cast<int>(height / rep_height);
 
    // rozmiar skrawków (części brakujących do pełnych klocków)
    const double scrap_width = width - whole_in_x * rep_width;     
    const double scrap_height = height - whole_in_y * rep_height;
 
    // położenie sprite'a w teksturze (zawsze takie samo)
    const double tex_x = m_data.left + m_data.width * m_current_frame;
    const double tex_y = m_data.bottom;
  

Tak przygotowani przechodzimy do kodu rysującego. Jest on podzielony na 4 fragmenty:

  1. Rysowanie całych sprite'ów.
  2. Rysowanie kawałków sprite'ów po prawej stronie.
  3. Rysowanie kawałków sprite'ów o góry.
  4. Rysowanie części zawartej w narożniku.

Kolejność w jakiej będą rysowane odpowiednie fragmenty powtórzonego sprite'a.

Wszystkie fragmenty rysowane są w podobny sposób. Kod może wyglądać na skomplikowany, ale po chwili analizy dostrzeżemy, że jest to żonglerka zdefiniowanymi wcześniej wartościami.

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
// Plik Sprite.cpp
    // rysuj całe sprite'y
    for (int ix=0; ix < whole_in_x; ++ix) {
        for (int iy=0; iy < whole_in_y; ++iy) {
            Engine::Get().GetRenderer()->DrawSprite(
                tex_x,  tex_y,                                           // tex pos
                m_data.width,  m_data.height,                            // tex size
                x + rep_width * ix,  y + rep_height * iy,                // screen pos
                rep_width,  rep_height,                                  // screen size
                m_data.layer);
        }
    }
 
    // dla każdego y'ka narysuj skrawki x'ów
    for (int iy=0; iy < whole_in_y && scrap_width > 0; ++iy) {
        Engine::Get().GetRenderer()->DrawSprite(
            tex_x,  tex_y,                                               // tex pos
            m_data.width * (scrap_width/rep_width),  m_data.height,      // tex size
            x + rep_width * whole_in_x,   y + rep_height * iy,           // screen pos
            scrap_width,  std::max(scrap_height,  rep_height),           // screen size
            m_data.layer);
    }
 
    // dla każdego x'a narysuj skrawki y'ów
    for (int ix=0; ix < whole_in_x && scrap_height > 0; ++ix) {
        Engine::Get().GetRenderer()->DrawSprite(
            tex_x,  tex_y,                                               // tex pos
            m_data.width,  m_data.height * (scrap_height/rep_height),    // tex size
            x + rep_width * ix,  y + rep_height * whole_in_y,            // screen pos
            std::max(scrap_width,  rep_width),  scrap_height,            // screen size
            m_data.layer);
    }
 
    // skrawek z x'a i y'ka -- narożnik
    if (scrap_height > 0 && scrap_width > 0) {
      Engine::Get().GetRenderer()->DrawSprite(
        // kolejne linie to tex pos, tex size,screen pos, screen size, layer
        tex_x,  tex_y,                                               
        m_data.width*(scrap_width/rep_width), m_data.height*(scrap_height/rep_height),
        x + rep_width * whole_in_x,  y + rep_height * whole_in_y,   
        scrap_width,  scrap_height,                                 
        m_data.layer);
    }
}
  
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com