|
Gra 2D, część 8: Dobrze mieć wybór... poziomu
25.08.2010 - Marcin Milewski
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:
- Rysowanie całych sprite'ów.
- Rysowanie kawałków sprite'ów po prawej stronie.
- Rysowanie kawałków sprite'ów o góry.
- 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);
}
}
|
|
|