Gra 2D, część 4 Piszemy Hall of Fame


Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Opublikowane na Wrocławski Portal Informatyczny (http://informatyka.wroc.pl)
Strona główna > Gra 2D, część 4: Piszemy Hall of Fame
Gra 2D, część 4: Piszemy Hall of Fame
29.01.2010 - Aukasz Milewski
TrudnośćTrudność
Każdy z nas lubi wygrywać, być najlepszym ;-) Dlatego, aby uatrakcyjnić naszą grę
powinniśmy zaspokoić tę potrzebę gracza. W tej części kursu stworzymy ekran, na
którym gracz będzie mógł umieścić swoje imię gdy zdobędzie więcej punktów niż inni.
Poprzedni artykuł - mapa kaflowa [1] Następny artykuł - odtwarzamy dzwięk [2]
Plan działania
Skonstruujemy Hall of fame, czyli listę najlepszych graczy, posortowaną według ilości punktów.
Na początek nauczymy się korzystać z czcionek bitmapowych. Następnie na podstawie takiej
czcionki wyświetlimy prosty hall of fame. Ostatnim krokiem będzie zaprogramowanie ekranu, na
którym można wpisać swoje imię. Zaprogramujemy dwa sposoby wpisywania imienia: z klawiatury
lub klikając na odpowiednie litery. Zaczynamy!
Kroki początkowe
Wgraj sobie nową teksturę [3]. Umieść ją w katalogu data. Zawiera ona wszystko to, co poprzednio
oraz czcionkę. Zauważ, że napisy są na szachownicy. Dzięki temu łatwiej jest narysować litery.
Grafika jest robocza, co oznacza że zmienimy ją gdy gra będzie gotowa. Tutaj znajdziesz kod, od
którego zaczynamy. [4]
1 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Czcionka bitmapowa
Pierwszym efektem będzie czcionka rastrowa (znana również jako czcionka bitmapowa). Oznacza
to, że wyświetlane litery są zapisane w pliku z grafiką jako bitmapa. Naszym celem jest
wyświetlanie słów złożonych z liter i cyfr oraz znaków specjalnych.
Zacznijmy od zdefiniowania interfejsu klasy Text. Chcemy mieć możliwość ustawienia wielkości
tekstu oraz warstwy, na której będzie on widoczny. Chcielibyśmy także móc narysować literę,
cyfrę, znak specjalny (np. podkreślenie), cały napis oraz liczbę. Dodatkowo, potrzebujemy
specjalnej metody rysującej, aby nie powtarzać niepotrzebnie kodu. Kod może wyglądać np. tak
(plik Text.h):
Pokaż/ukryj kod
1 #ifndef __TEXT_H__
2 #define __TEXT_H__
3
4 class Text {
5 public:
6 explicit Text(double width = 0.025, double height = 0.025, size_t layer = 0) {
7 SetSize(width, height);
8 SetLayer(layer);
9 }
10
11 void SetSize(double width, double height) {
12 m_width = width;
13 m_height = height;
14 }
15
16 void SetLayer(size_t layer) {
17 m_layer = layer;
18 }
19
20 void DrawDigit(char ch, double pos_x, double pos_y);
21 void DrawLetter(char ch, double pos_x, double pos_y);
22 void DrawSpecial(char ch, double pos_x, double pos_y);
23 void DrawText(const std::string& text, double pos_x, double pos_y);
24 void DrawNumber(size_t number, double pos_x, double pos_y, size_t width = 0);
25
26 private:
27 void Draw(int tex_x, int tex_y, double pos_x, double pos_y);
28
29 private:
30 double m_width;
31 double m_height;
32 size_t m_layer;
33 };
34
35 #endif /* __TEXT_H__ */
36
Parameter width w DrawNumber określa, jaka ma być minimalna długość liczby (gdy jest zbyt
krótka, wyrównujemy ją odstępami z lewej strony). Przejdzmy do implementacji. Potrzebujemy
funkcji, która zamienia liczbę na string (można to zrobić łatwo przy pomocy boost::lexical_cast, ale
staramy się ograniczyć wykorzystanie boost do klasy shared_ptr). Do pliku Utils.h wpisujemy taką
treść:
1 #ifndef __UTILS_H_INCLUDED__
2 #define __UTILS_H_INCLUDED__
3 #include
2 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
4 #include
5
6 /**
7 * Zamienia liczbę number na odpowiadający jej ciąg znaków.
8 */
9
std::string IntToStr(int number);
10
11
#endif
A do pliku Utils.cpp:
1 #include "Utils.h"
2
3 std::string IntToStr(int number) {
4 std::stringstream ss;
5 ss << number;
6 return ss.str();
7 }
Następny krok to rysowanie. Zajdzie potrzeba manipulowania macierzą projekcji, dlatego z klasy
App przenosimy fragment metody App::Resize. Obecnie powinna ona wyglądać tak:
1 void App::Resize(size_t width, size_t height) {
2 m_screen = SDL_SetVideoMode(width, height, 32,
3 SDL_OPENGL
4 | SDL_RESIZABLE
5 | SDL_HWSURFACE);
6 assert(m_screen && "problem z ustawieniem wideo");
7 m_window_width = width;
8 m_window_height = height;
9
10 Engine::Get().Renderer()->SetProjection(width, height);
11 }
Ustawienie macierzy projekcji jest teraz w klasie Renderer. Do pliku Renderer.cpp na koniec
dopisujemy:
1 void Renderer::SetProjection(size_t width, size_t height) {
2 glViewport(0, 0,
3 static_cast (width),
4 static_cast (height));
5 ResetProjection();
6 glMatrixMode(GL_MODELVIEW);
7 glLoadIdentity();
8 }
9
10 void Renderer::ResetProjection() {
11 glMatrixMode(GL_PROJECTION);
12 glLoadIdentity();
13 glOrtho(0, 1, 0, 1, -1, 10);
14 }
a do pliku Renderer.h do klasy Renderer w zasięgu publicznym dorzucamy deklaracje:
1 void SetProjection(size_t width, size_t height);
2 void ResetProjection();
Znaczenie tych kodów zostało wytłumaczone w artykule Tworzenie okna i wyświetlanie
3 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
animowanego sprite'a [5]
Spójrzmy na metodę Text::Draw. Jej zadaniem jest wyświetlenie na ekranie sprite'a z atlasu
tekstur z pozycji (tex_x, tex_y) na pozycję (pos_x, pos_y). Metoda ta zakłada, że sprite jest
wielkości (32,32) oraz ma być wyświetlony na ekranie w wielkości ustawionej metodą SetSize (pola
m_width, m_height).
Wyświetlanie sprite'a jest w pewien sposób specjalne Jest on bowiem wyświetlany tak, jakby (0,0)
było w lewym dolnym rogu ekranu, a (1,1) w prawym górnym rogu. Inaczej jest wyświetlana mapa
i gracz. Dla gracza pozycja (0,0) oznacza, że jest on na początku mapy. Nie znaczy to jednak, że
jest w lewym dolnym rogu ekranu!
Aby uzyskać taki efekt, musimy ustawić macierz projekcji na identyczność oraz rzut ortogonalny
od początku (tak, jak w artykule pierwszym - metoda App::Resize). Zanim to jednak zrobimy,
powinniśmy odłożyć na stos stare macierze - MODELVIEW i PROJECTION. Nie chcemy przecież,
aby Text::Draw wpływało w jakikolwiek sposób na rysowanie innych elementów (np. przesuwało
je).
Przydatna jest struktura danych, która pozwala dodawać elementy i je z niej pobierać. Chcemy,
aby pobrany element był zawsze tym najpózniej włożonym (elementy wyjmujemy w kolejności
odwrotnej do wkładania). Można to przyrównać do stosu kartek. Zawsze na górze jest tylko jedna -
ta ostatnio położona. Aby zobaczyć kolejną kartkę trzeba podnieść pierwszą. Struktura, która
zachowuje się w ten sposób nazywana jest stosem. W naszym przypadku na stos odkładamy
macierze OpenGL.
Aby odłożyć macierz na stos, należy ustawić ją jako aktywną - przez glMatrixMode. Następnie
możemy posłużyć się funkcjami glPushMatrix i glPopMatrix do obsługi stosu macierzy.
Po ustawieniu macierzy projekcji od nowa, a macierzy MODELVIEW na identyczność, wystarczy
narysować sprite'a przy pomocy znanej nam już metody Renderer::DrawSprite.
Pokaż/ukryj kod
1 void Text::Draw(int tex_x, int tex_y, double pos_x, double pos_y) {
2
3 glPushMatrix(); // MODELVIEWn
4 {
5 glMatrixMode(GL_PROJECTION);
6 glPushMatrix();
7 {
8 Engine::Get().Renderer()->ResetProjection();
9 glMatrixMode(GL_MODELVIEW);
10 glLoadIdentity();
11 Engine::Get().Renderer()->DrawSprite(tex_x, tex_y, 32, 32,
12 pos_x, pos_y, m_width, m_height,
13 DL::DisplayLayer(m_layer));
14 }
15 glMatrixMode(GL_PROJECTION);
16 glPopMatrix();
17 }
18 glMatrixMode(GL_MODELVIEW);
19 glPopMatrix();
20 }
21
Posiadając metodę Draw, możemy zdefiniować kolejne proste metody: DrawDigit, DrawLetter,
DrawSpecial. Kolejno wypisują one cyfry, litery oraz znaki specjalne (np. podkreślenie). Zadanie
tych metod sprowadza się do obliczenia, gdzie w atlasie znajduje się odpowiednia grafika i
wywołania metody Draw.
4 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Poszczególne znaki są zapisane w pliku tak, jak widać na załączonym obrazku (kolejny raz
podkreślmy, że jest to grafika robocza - zostanie zmieniona po ukończeniu kodu gry).
Na podstawie powyższej ilustracji łatwo wyznaczyć pozycje kolejnych liter (w teksturze obrazek z
czcionką jest przesunięty w dół).
Pokaż/ukryj kod
1 void Text::DrawDigit(char ch, double pos_x, double pos_y) {
2 int digit = ch - '0';
3 int tex_x = digit * 32;
4 int tex_y = 7*32;
5 Draw(tex_x, tex_y, pos_x, pos_y);
6 }
7
8 void Text::DrawLetter(char ch, double pos_x, double pos_y) {
9 int letter = toupper(ch) - 'A';
10
11 int letter_row = letter / 10; // wiersz, w którym jest litera
12 int letter_col = letter % 10; // kolumna, w której jest litera
13
14 int tex_x = letter_col * 32;
15 int tex_y = (8+letter_row) * 32;
16
17 Draw(tex_x, tex_y, pos_x, pos_y);
18 }
19
20 void Text::DrawSpecial(char ch, double pos_x, double pos_y) {
21 double tex_x = 0;
22 double tex_y = 0;
23
24 if (ch == '_') {
25 tex_x = 192;
26 tex_y = 320;
27 }
28 else {
29 return; // pomijamy znaki, których nie znamy
30 }
31 Draw(tex_x, tex_y, pos_x, pos_y);
32 }
33
Na bazie tych metod możemy zbudować kolejne: DrawText oraz DrawNumber.
Text::DrawText iteruje po kolejnych znakach i przy pomocy funkcji z nagłówka cctype sprawdza,
czy znak jest literą, cyfrą czy podkreśleniem. Wszystkie inne znaki są pomijane. Gdy metoda
pozna typ znaku, od razu wywołuje odpowiednią metodę spośród tych, które przed chwilą
wymieniliśmy.
Pokaż/ukryj kod
1 void Text::DrawText(const std::string& text, double pos_x, double pos_y) {
2 double x = pos_x;
5 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
3 double y = pos_y;
4
5 for (size_t i = 0; i < text.size(); ++i) {
6 char ch = text.at(i);
7 if (isdigit(ch)) {
8 DrawDigit(ch, x, y);
9 }
10 else if (isalpha(ch)) {
11 DrawLetter(ch, x, y);
12 }
13 else if (ch == '_') {
14 DrawSpecial(ch, x, y);
15 }
16 else {
17 ; // inne znaki po prostu pomijamy
18 }
19 x += m_width;
20 }
21 }
22
Metoda Text::DrawNumber też jest bardzo leniwa. Najpierw przy pomocy fukncji IntToStr zamienia
liczbę na napis. Następnie sprawdza, czy liczba jest dobrze wyrównana (dodaje wiodące zera do
osiągnięcia długości width). Ostatecznie wywoływana jest metoda DrawText z odpowiednim
tekstem, reprezentującym daną liczbę.
1 void Text::DrawNumber(size_t number,
2 double pos_x,
3 double pos_y,
4 size_t width) {
5 std::string number_str = IntToStr(number);
6 size_t spaces_count = std::max(0,
7 static_cast (width)
8 -
9 static_cast (number_str.size()));
10 for (size_t i = 0; i < spaces_count; ++i)
11 number_str = " " + number_str;
12 DrawText(number_str, pos_x, pos_y);
13 }
Możemy spróbować wypisać coś na ekranie. Wystarczy do metody App::Draw dopisać takie linijki (i
dodać nagłówek Text.h). Powinny być dodane zaraz po narysowaniu gracza.
1 Text t;
2 t.DrawText("witaj swiecie", 0.3, 0.5);
6 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Wyświetlanie Hall of fame
Wyświetlenie hall of fame jest dosyć proste, gdy mamy już taką przyjemną klasę Text. Na
początek skopiujmy PLIK_Z_HALL_OF_FAME do katalogu data w katalogu z kodem gry. Format
pliku jest prosty. W każdym z dziesięciu wierszy jest najpierw nick danej osoby, odstęp, a
następnie jej punkty.
Nagłówek definiuje strukturę zagnieżdżoną Entry. Jest ona parą (nazwa,punkty). Będziemy
przechowywali wektor takich par, posortowany malejąco według punktów. Do interfejsu będą
należały metody: rysująca, uaktualniająca stan, przetwarzająca zdarzenia oraz sprawdzająca, czy
należy wyłączyć Hall Of Fame.
Pokaż/ukryj kod
1 #ifndef __HALL_OF_FAME_H__
2 #define __HALL_OF_FAME_H__
3
4 #include
5 #include
6
7 class HallOfFame {
8 private:
9 struct Entry {
10 std::string name;
11 size_t points;
12 };
13
14 public:
15 explicit HallOfFame();
16
17 void Draw();
18 void Update(double dt);
19 void ProcessEvents();
20
21 bool IsDone() const { return m_is_done; }
7 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
22
23 private:
24 void LoadFromFile();
25
26 private:
27 bool m_is_done;
28 std::vector m_entries;
29 };
30
31 #endif /* __HALL_OF_FAME_H__ */
32
Implementacja również jest prosta.
Pokaż/ukryj kod
1 #include
2 #include
3
4 #include
5 #include
6
7 #include "HallOfFame.h"
8 #include "Text.h"
9 #include "Engine.h"
10
11
12 HallOfFame::HallOfFame()
13 : m_is_done(false) {
14 LoadFromFile();
15 }
16
Rysowanie Hall of Fame polega na dobraniu wielkości i pozycji odpowiednich tekstów. Listę mamy
już wczytaną, wystarczy tylko wypisać każdy jej element, używając naszej bitmapowej czcionki.
Pokaż/ukryj kod
1 void HallOfFame::Draw() {
2 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3 glLoadIdentity();
4
5 Text t(0.08, 0.08);
6 t.DrawText("Hall Of Fame", 0.01, 0.9);
7
8 t.SetSize(0.035, 0.035);
9 double y = 0.7;
10 double x = 0.15;
11 for (size_t i = 0; i < m_entries.size(); ++i) {
12 t.DrawText(m_entries.at(i).name, x, y);
13 t.DrawNumber(m_entries.at(i).points, x+0.4, y, 8);
14 y -= 0.07;
15 }
16
17 SDL_GL_SwapBuffers();
18 }
19
Uaktualniać hall of fame nie trzeba
8 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
1 void HallOfFame::Update(double /* dt */) {
2
3 }
Przetwarzanie klawiszy ograniczymy na razie do reagowania na naciśnięcie dowolnego klawisza i
wyjście w takim przypadku z aplikacji.
1 void HallOfFame::ProcessEvents() {
2 SDL_Event event;
3 while (SDL_PollEvent(&event)) {
4 if (event.type == SDL_QUIT) {
5 m_is_done = true;
6 break;
7 } else if (event.type == SDL_KEYDOWN) {
8 m_is_done = true;
9 break;
10 }
11 }
12 }
Aby pobrać z pliku wektor wyników wczytujemy linijka po linijce plik i zapamiętujemy odpowiednie
wpisy w wektorze. Pamiętajmy, że wpisy są posortowane według ilości punktów (nierosnąco)
1 void HallOfFame::LoadFromFile() {
2 std::ifstream in("data/hof.txt");
3 if (!in) {
4 std::cerr << "Nie moge odczytac Hall of Fame\n";
5 return;
6 }
7
8 Entry entry;
9 while (in >> entry.name >> entry.points) {
10 m_entries.push_back(entry);
11 }
12 }
Zmienimy plik App.cpp, aby zobaczyć, czy wszystko działa. Na początku pliku dodajemy nagłówek
"HallOfFame.h". Następnie zmieniamy pętlę główną z:
Pokaż/ukryj kod
9 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
1 // pętla główna
2 is_done = false;
3 size_t last_ticks = SDL_GetTicks();
4 while (!is_done) {
5 ProcessEvents();
6
7 // time update
8 size_t ticks = SDL_GetTicks();
9 double delta_time = (ticks - last_ticks) / 1000.0;
10 last_ticks = ticks;
11
12 // update & render
13 if (delta_time > 0) {
14 Update(delta_time);
15 }
16 Draw();
17 }
18
na:
Pokaż/ukryj kod
1 // pętla główna
2 HallOfFame hof;
3 size_t last_ticks = SDL_GetTicks();
4 while (!hof.IsDone()) {
5 hof.ProcessEvents();
6
7 // time update
8 size_t ticks = SDL_GetTicks();
9 double delta_time = (ticks - last_ticks) / 1000.0;
10 last_ticks = ticks;
11
12 // update & render
13 if (delta_time > 0) {
14 hof.Update(delta_time);
15 }
16 hof.Draw();
17 }
18
Możemy włączyć program i zobaczyć, co się dzieje. Wciśnięcie dowolnego klawisza kończy
program.
10 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Wprowadzanie nowego wyniku
Poprzedni punkt był demonstracją, jak używać klasy Text. Teraz nauczymy się obsługiwać mysz
oraz przechwytywać to, co pisze użytkownik. W tym celu napiszemy kolejną klasę - ScoreSubmit.
Domyślasz się już, że interfejs klasy ScoreSubmit będzie bardzo zbliżony do interfejsu klasy
HallOfFame. Mamy zatem konstruktor i metody ProcessEvents, Draw, Update oraz IsDone.
Metoda StoreInFile jest odpowiedzialna za zapisywanie w pliku aktualnej wersji punktacj,
11 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
natomiast LetterPosition zwraca pozycję (x,y) na ekranie, gdzie będzie wyświetlona litera
przekazana w argumencie - wykorzystamy to do stworzenia klawiatury klikalnej myszką.
Pokaż/ukryj kod
1 #ifndef __SCORE_SUBMIT_H__
2 #define __SCORE_SUBMIT_H__
3
4 #include
5
6 class ScoreSubmit {
7 private:
8 struct Entry {
9 std::string name;
10 size_t points;
11 };
12
13 public:
14 explicit ScoreSubmit(size_t points);
15
16 void ProcessEvents();
17 void Draw();
18 void Update(double dt);
19
20 bool IsDone() const { return m_is_done; }
21
22 private:
23 std::pair LetterPosition(char ch);
24 void StoreInFile();
25
26 private:
27 bool m_is_done;
28 std::string m_player_nickname;
29 size_t m_next_letter;
30 size_t m_points;
31 char m_highlighted_char;
32
33 };
34
35 #endif /* __SCORE_SUBMIT_H__ */
36
Przejdzmy do implementacji. Tekst wpisany przez gracza będziemy pamiętali w polu
m_player_nickname, a miejsce, gdzie należy wpisać kolejną literę (pozycja w m_player_nickname)
zapamiętamy w m_next_letter. Dodatkowo, będziemy pamiętali, która litera jest podświetlona (nad
którą jest aktualnie mysz) - użyjemy do tego celu pola m_highlighted_char.
Początkowy stan nie powinien Cię zaskoczyć:
1 ScoreSubmit::ScoreSubmit(size_t points)
2 : m_is_done(false),
3 m_player_nickname("__________"),
4 m_next_letter(0),
5 m_points(points),
6 m_highlighted_char(' ') {
7 }
m_player_nickname ustawiamy na same znaki podkreślenia, ponieważ chcemy, aby pojawiły się w
miejsach, w których gracz jeszcze nie wpisał litery.
12 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Wyświetlanie wyniku
Teraz wyświetlamy napis "gratulacje" oraz "wpisz swoje imię".
1 void ScoreSubmit::Draw() {
2 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3 glLoadIdentity();
4
5 Text t(0.08, 0.08);
6 t.DrawText("gratulacje", 0.1, 0.9);
7 t.SetSize(0.05, 0.05);
8 t.DrawText("wpisz swoje imie", 0.1, 0.8);
9
10 t.DrawText(m_player_nickname, 0.25, 0.6);
11
Następnie rysujemy wszystkie litery od 'a' do 'z'. Ich pozycja jest określana przez metodę
LetterPosition. Naszym celem jest stworzenie klawiatury. W każdym jej wierszu będzie po 7 liter.
Wymiary liter ustawiamy na 0.05x0.05 (czyli na ekranie zmieści się ich 20x20). Zanim narysujemy
literę, sprawdzamy, czy jest ona aktualnie zaznaczona. Jeżeli tak, rysujemy kwadrat za nią, tak,
aby litera była w jego środku. Da nam to efekt podświetlenia zaznaczonej myszą litery.
Ostatecznie wykorzystujemy klasę Tekst do wyświetlenia znaku.
1 t.SetSize(0.05, 0.05);
2
3 for (char ch = 'a'; ch <= 'z'; ++ch) {
4 std::pair pos = LetterPosition(ch);
5 if (ch == m_highlighted_char) {
6 Engine::Get().Renderer()->DrawQuad(pos.first - 0.005,
7 pos.second - 0.005,
8 pos.first + 0.055,
9 pos.second + 0.055,
10 1,0,0,1);
11 }
12 t.DrawLetter(ch, pos.first, pos.second);
13 }
14
15 SDL_GL_SwapBuffers();
16 }
Ponownie potrzebujemy rozszerzyć klasę Renderer. Tym razem będziemy rysowali wypełnione
kwadraty. Do klasy Renderer dodajemy deklarację metody DrawQuad (plik Renderer.h):
1 void DrawQuad(double min_x, double min_y,
2 double max_x, double max_y,
3 double r, double g, double b, double a) const;
Teraz w pliku Renderer.cpp dodaj jej prostą implementację:
1 void Renderer::DrawQuad(double min_x, double min_y,
2 double max_x, double max_y,
3 double r, double g, double b, double a) const {
Na początek zapamiętujemy aktualny stan OpenGL. Pod koniec metody przywrócimy go metodą
glPopAttrib(). Robimy tak, ponieważ chcemy wyłączyć tekstury i mieszanie kolorów.
13 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
1 glPushAttrib(GL_ALL_ATTRIB_BITS);
2 glDisable(GL_TEXTURE_2D);
3 glEnable(GL_BLEND);
4 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
5 glColor4f(r, g, b, a);
Następnie rysujemy nasz czworokąt.
1 glBegin(GL_QUADS);
2 {
3 glVertex2f(min_x, min_y);
4 glVertex2f(max_x, min_y);
5 glVertex2f(max_x, max_y);
6 glVertex2f(min_x, max_y);
7 }
8 glEnd();
9
10 glPopAttrib();
11 }
Potrzebujemy jeszcze metody LetterPosition. Oblicza ona numer litery w alfabecie oraz kolumnę i
wiersz, w której litera powinna być wyświetlona (przy założeniu, że w wierszu jest 7 liter). W ten
sposób otrzymujemy pozycję w tablicy. Aby ją wyświetlić, na ekranie musimy przenieś
współrzędne litery do przestrzeni oka. Zakładamy, że litera ma na ekranie rozmiar 0.07x0.07.
Dodatkowo, przesuwamy tablicę w pionie i w poziomie - współczynniki są dobrane empirycznie
(przez uruchamianie programu i sprawdzanie, czy dobrze wygląda).
1 std::pair ScoreSubmit::LetterPosition(char ch) {
2 int index = ch - 'a';
3 int col = index % 7;
4 int row = index / 7;
5
6 return std::make_pair(0.25 + col * 0.07,
7 0.45 - row * 0.07);
8 }
Uaktualnianie i przetwarzanie zdarzeń
Jak poprzednio (przy HallOfFame), nie ma potrzeby uaktualniania stanu ScoreSubmit w każdym
przebiegu pętli. Musimy natomiast trochę zmodyfikować przetwarzanie zdarzeń
1 void ScoreSubmit::ProcessEvents() {
2 SDL_Event event;
3 while (SDL_PollEvent(&event)) {
4 if (event.type == SDL_QUIT) {
5 m_is_done = true;
6 break;
7 } else if (event.type == SDL_KEYDOWN) {
Najpierw sprawdzamy, czy gracz nacisnął Enter. Jeżeli tak, chcemy zapisać wynik w pliku, jednak
tylko w przypadku, gdy faktycznie coś wpisał (czyli gdy pierwszy znak jest różny od podkreślenia;
można wprowadzać tylko litery).
1 if (event.key.keysym.sym == SDLK_RETURN
2 && m_player_nickname.at(0) != '_') {
3 StoreInFile();
14 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
4 m_is_done = true;
5 break;
6 }
Następnie sprawdzamy, czy to, co nacisnął gracz jest literą. Jeżeli tak i dodatkowo nie
osiągnęliśmy maksymalnej długości wpisu, obliczamy teraz, jaką literę nacisnął gracz i dodajemy
ją do m_player_nickname.
1 else if (event.key.keysym.sym >= SDLK_a
2 && event.key.keysym.sym <= SDLK_z
3 && m_next_letter < m_player_nickname.size()) {
4 char key = event.key.keysym.sym - SDLK_a + 'a';
5 m_player_nickname.at(m_next_letter++) = key;
6 }
Ostatnim sprawdzamy klawiszem jest backspace. Gracz może użyć go do cofnięcia tego, co
wpisał. Ustawiamy ostatnio wpisany znak na '_' i zmniejszamy liczbę wpisanych liter o 1. Musimy
pamiętać, że z pustego napisu nie można usuwać liter.
1 else if (event.key.keysym.sym == SDLK_BACKSPACE) {
2 if (m_player_nickname.at(0) != '_') {
3 m_player_nickname.at(--m_next_letter) = '_';
4 }
5 }
Teraz czas na wskaznik myszy. Przy poruszaniu pobieramy pozycję wskaznika myszy (zmienne
x,y). Pamiętamy, że ma on współrzędne w przestrzeni ekranu (0..599 x 0..399, ponieważ mamy
ekran 600x400). Musimy też odbić współrzędne pionowo (odjąć od 1.0), gdyż wskaznik myszy ma
punkt (0,0) w lewym górnym rogu ekranu, a nasze (0,0) jest w lewym dolnym rogu. Znając
pozycję, po prostu iterujemy po wszystkich literach i sprawdzamy, czy pozycja myszy zawiera się
w kwadracie, w którym jest wyświetlana litera. Jeżeli tak, zapamiętujemy literę jako podświetloną
(wykorzystujemy ten fakt w metodzie Draw).
1 }
2 else if (event.type == SDL_MOUSEMOTION) {
3 double x = event.motion.x / 600.0;
4 double y = 1.0 - event.motion.y / 400.0;
5 m_highlighted_char = ' ';
6 for (char ch = 'a'; ch <= 'z'; ++ch) {
7 std::pair ch_pos = LetterPosition(ch);
8 if (x >= ch_pos.first && x <= ch_pos.first + 0.07
9 && y <= ch_pos.second + 0.07 && y >= ch_pos.second) {
10 m_highlighted_char = ch;
11 }
12 }
13 }
W przypadku kliknięcia myszą, sprawdzamy, czy jest zaznaczona jakaś litera i czy nie
przekroczyliśmy maksymalnej długości napisu. Jeżeli te warunki są spełnione, to podobnie jak w
obsłudze wpisywania liter z klawiatury, dodajemy literę do m_player_nickname.
1 else if (event.type == SDL_MOUSEBUTTONDOWN) {
2 if (m_highlighted_char != ' '
3 && m_next_letter < m_player_nickname.size()) {
4 m_player_nickname.at(m_next_letter++) = m_highlighted_char;
5 }
6 }
15 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
7 }
8 }
Zapis do pliku
Na koniec zostawiliśmy najprostszą metodę
void ScoreSubmit::StoreInFile() {
1
Otwieramy plik data/hof.txt, w którym pamiętamy wyniki. Wczytujemy je do pamięci (wektor
entries).
1 std::fstream hof("data/hof.txt");
2 if (!hof) {
3 std::cerr << "Nie moge odczytac Hall of Fame\n";
4 return;
5 }
6
7 std::vector entries;
8 Entry e;
9 while (hof >> e.name >> e.points) {
10 entries.push_back(e);
11 }
Następnie tworzymy nowy wpis. Zapamiętujemy w nim wpisaną przez gracza nazwę oraz liczbę
zdobytych punktów. Dodajemy go do wektora z wynikami.
1 Entry new_e;
2 for (size_t i = 0; i < m_player_nickname.size(); ++i) {
3 if (m_player_nickname.at(i) != '_') {
4 new_e.name += m_player_nickname.at(i);
5 }
6 }
7 new_e.points = m_points;
8 entries.push_back(new_e);
Zauważmy, że wszystkie wpisy, poza ostatnio dodanym, są uporządkowane względem ilości
punktów. Wystarczy zatem przesuwać dodany wpis w kierunku początku listy tak długo, jak
poprzedni wpis jest od niego gorszy. W ten sposób uzyskujemy posortowaną po ilości punktów
listę wpisów.
1 int j = entries.size() - 1;
2 while (j > 0) {
3 if (entries.at(j-1).points < entries.at(j).points) {
4 std::swap(entries.at(j-1), entries.at(j));
5 }
6 --j;
7 }
Ostatecznie nadpisujemy plik z wynikami. Wpisujemy do niego 10 najlepszych wpisów.
1 hof.close();
2 hof.open("data/hof.txt", std::ios::out);
3 for (size_t i = 0; i < 10 && i < entries.size(); ++i) {
4 hof << entries.at(i).name << " " << entries.at(i).points << "\n";
5
16 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
6 }
}
Uruchomienie
Właśnie ukończyliśmy kolejny moduł - formatkę do dodawania wpisów do hall of fame. Wpisy
możemy dodawać z klawiatury lub użyć stylowej klawiaturki obsługiwanej myszą. Zatem zmieńmy
nasz program tak, aby wyświetlał tę formatkę! W tym celu edytuj plik App.cpp. Dodajemy plik
nagłówkowy
1 #include "ScoreSubmit.h"
Następnie zmień\niamy pętlę główną w metodzie update. Poprzednia wersja wyglądała tak
(używała klasy HallOfFame):
Pokaż/ukryj kod
1 // pętla główna
2 HallOfFame hof;
3 size_t last_ticks = SDL_GetTicks();
4 while (!hof.IsDone()) {
5 hof.ProcessEvents();
6
7 // time update
8 size_t ticks = SDL_GetTicks();
9 double delta_time = (ticks - last_ticks) / 1000.0;
10 last_ticks = ticks;
11
12 // update & render
13 if (delta_time > 0) {
14 hof.Update(delta_time);
15 }
16 hof.Draw();
17
17 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
18 }
Po podmianie HallOfFame na ScoreSubmit otrzymujemy:
Pokaż/ukryj kod
1 // pętla główna
2 ScoreSubmit ss(222); // w argumencie podajesz liczbę zdobytych punktów
3 size_t last_ticks = SDL_GetTicks();
4 while (!ss.IsDone()) {
5 ss.ProcessEvents();
6
7 // time update
8 size_t ticks = SDL_GetTicks();
9 double delta_time = (ticks - last_ticks) / 1000.0;
10 last_ticks = ticks;
11
12 // update & render
13 if (delta_time > 0) {
14 ss.Update(delta_time);
15 }
16 ss.Draw();
17 }
18
Nie widać tego na screenie, ale oczywiście nad literą 'z' był kursor myszy.
Zaprogramowaliśmy nieodłączny element każdej gry komputerowej - Hall Of Fame. Zaczęliśmy od
zdefiniowania czcionki bitmapowej. Następnie dodaliśmy do gry formatkę pozwalającą wpisywać
imię. Ostatecznie utworzyliśmy małą klawiaturę obsługiwaną myszą. Dzięki temu umiemy
posługiwać się zdarzeniami myszy i klawiatury oraz wyświetlać tekst na ekranie. Kod zródłowy,
który jest wynikiem tych zmian można znalezć tutaj [6]. Zawiera on także zmiany, które są efektem
wykonania pierwszego ćwiczenia z poniższej listy. Będziemy to wykorzystywać w przyszłości,
dlatego ważne jest wykonanie tego zadania.
18 z 19 2012-12-21 17:03
Gra 2D, część 4: Piszemy Hall of Fame http://informatyka.wroc.pl/print/475
Masz pytanie, uwagę? Zauważyłeś błąd? Powiedz o tym na forum [7].
Zadania
1. Zauważ, że klasa ScoreSubmit wykorzystuje fakt, że wybrana rozdzielczość to 600x400. W
którym miejscu? Zmień klasę tak, aby była niezależna od rozdzielczości.
2. Dodaj tekst "kliknij w litere aby ja dodac" do ekranu ScoreSubmit. Musisz zrobić to w ten
sposób, aby klawiatura była nadal poprawnie obsługiwana.
3. Dodaj do klawiatury ekranowej klawisze cofania (ma działać tak, jak backspace) oraz
zatwierdzania (działa tak, jak enter)
Adres zródła: http://informatyka.wroc.pl/node/475
Odnośniki:
[1] http://informatyka.wroc.pl/node/474
[2] http://informatyka.wroc.pl/node/476
[3] http://informatyka.wroc.pl/upload/mmi/platf/04_tex.bmp
[4] http://informatyka.wroc.pl/upload/mmi/platf/04_kod_poczatkowy.zip
[5] http://informatyka.wroc.pl/node/387
[6] http://informatyka.wroc.pl/upload/mmi/platf/04_kod_wynikowy.zip
[7] http://informatyka.wroc.pl/forum/viewtopic.php?f=41&t=348
19 z 19 2012-12-21 17:03


Wyszukiwarka

Podobne podstrony:
Gra 2D, część 3 Wyświetlanie przewijanej mapy
Gra 2D, część 2 Wyżej, dalej, szybciej poruszanie postacią
Zelazny, Roger Amber Short Story 06 Hall of Mirrors
LotR The Hall of Fire Magazine 02
Fredric Brown Hall of Mirrors
LotR The Hall of Fire Magazine Index 01 48
Stuart Hall, Cultural Studies, and the Unresolved Problem of the Relation of Culture to Not Culture
49 Gra w przeciwieństwa Game of Opposites Sep 15 2013
Gra Memory Owoce (Fruits), częśc 1

więcej podobnych podstron