Gra 2D, część 3 Wyświetlanie przewijanej mapy


Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
Opublikowane na Wrocławski Portal Informatyczny (http://informatyka.wroc.pl)
Strona główna > Gra 2D, część 3: Wyświetlanie przewijanej mapy
Gra 2D, część 3: Wyświetlanie przewijanej
mapy
18.01.2010 - Aukasz Milewski
TrudnośćTrudność
Poprzedni artykuł - poruszanie postacią [1] Następny artykuł - Hall of Fame [2]
Plan działania
Witaj! Tym razem zajmiemy się mapą gry. Na początku wczytamy z pliku opis poziomu. W
drugim kroku stworzymy jego reprezentację graficzną. Następnie nauczymy gracza poruszać
się po mapie i jednocześnie zaprogramujemy kamerę tak, aby był on na środku ekranu. Na
koniec dodamy kilka funkcji. Gracz nie będzie mógł cofnąć się do miejsca, które stracił z
widoku. Dodatkowo, gdy gracz zbliży się do lewej lub prawej krawędzi, mapa przestanie się
przesuwać i jego postać nie będzie musiała być już w środku ekranu.
Jak będziemy wyświetlali mapę?
Czym jest mapa (siatka) kaflowa? Wyobrazmy sobie, że dzielimy ekran na prostokąty, jak
tabliczkę czekolady. Każdy z tych prostokątów to jedno pole. Każde pole może mieć
określony typ, co oznacza, że może być np. puste lub zajęte. My wprowadzimy następne
rodzaje: pole zajęte, lewa krawędz platformy, środek platformy, prawa krawędz platformy i pole
puste. W ten sposób będziemy rysowali mapę.
Zaczynamy
Na początek pobieramy kod zródłowy, który jest wynikiem ukończenia poprzedniej lekcji
(znajdziesz go tutaj [3]). Do katalogu data wrzucamy plik poziomu pobrany z 1.lvl [4].
Powinniśmy również obok pliku poziomu umieścić nową teksturę z kaflami - tex.bmp [5].
Reprezentacja poziomu w grze
1 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
Plik poziomu jest bardzo prostym plikiem tekstowym. W pierwszej linii zapisujemy szerokość
(W) i wysokość (H). W naszym przypadku mamy mapę o wymiarach 50x20. Kolejne H (20)
linii zawiera po W (50) liczb ze zbioru {0, 1, 2, 3}. Przyjmijmy, że:
0 oznacza pole puste
1 oznacza lewą krawędz platformy
2 oznacza środek platformy
3 oznacza prawą krawędz platformy
Chcemy móc wczytać poziom, następnie pobierać jego wysokość lub szerokość, a także
sprawdzać typ pola w określonych miejscach. Dlatego deklaracja odpowiedniej klasy może
wyglądać np. tak (plik Level.h):
Pokaż/ukryj kod
1 #ifndef __LEVEL_H__
2 #define __LEVEL_H__
3
4 #include
5 #include
6 #include
7 #include "Types.h"
8
9 class Level {
10 public:
11 explicit Level();
12
13 void LoadFromFile(const std::string& filename);
14
15 FT::FieldType Field(size_t x, size_t y) const;
16 size_t GetWidth() const { return m_width; }
17 size_t GetHeight() const { return m_height; }
18
19 private:
20
2 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
21 size_t m_width;
22 size_t m_height;
23 std::vector > m_data;
24 };
25 typedef boost::shared_ptr LevelPtr;
26
27 #endif
Jednocześnie w pliku Types.h definiujemy nowy typ wyliczeniowy FieldType, który określa,
jakim typem jest dane pole (puste, lewa/prawa krawędz, środek).
1 namespace FT {
2 enum FieldType {
3 None = 0,
4 PlatformLeftEnd = 1,
5 PlatformMidPart = 2,
6 PlatformRightEnd = 3,
7
8 COUNT
9 };
10 }
Przyjrzymy się teraz przykładowej implementacji takiej klasy (plik Level.cpp):
1 #include
2 #include
3 #include "Engine.h"
4 #include "Level.h"
5
6
7 Level::Level()
8 : m_width(0),
9 m_height(0) {
10 }
Aadowanie poziomu z pliku jest proste. Wystarczy otworzyć odpowiedni plik (jego nazwę
dostajemy w parametrze). Następnie wczytujemy szerokość i wysokość do pól m_width oraz
m_height.
1 void Level::LoadFromFile(const std::string& filename) {
2 std::ifstream lvl(filename.c_str());
3 if (!lvl) {
4 std::cerr << "Nie udało się załadować pliku " << filename << "\n";
5 return;
6 }
7
8 lvl >> m_width >> m_height;
Tworzymy tablicę dwuwymiarową, która będzie odpowiadała naszej mapie. W tym celu
ustalamy wymiary wektora na wymiary wczytywanego poziomu.
1 m_data.resize(m_height);
2 for (size_t y = 0; y < m_height; ++y) {
3 m_data.at(y).resize(m_width);
3 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
4 }
Ostatecznie wczytujemy m_width * m_height (W*H) liczb. Każda z nich określa typ pola w
miejscu o danych współrzędnych.
1 int tmp;
2 for (size_t y = 0; y < m_height; ++y) {
3 for (size_t x = 0; x < m_width; ++x) {
4 lvl >> tmp;
5 m_data.at(y).at(x) = FT::FieldType(tmp);
6 }
7 }
8 }
Zdefiniujmy metodę Field. Ważne: gdy odwołujemy się do pola o zbyt dużych współrzędnych,
leżącego poza mapą, zwracamy brak pola (FT::None).
1 FT::FieldType Level::Field(size_t x, size_t y) const {
2 if (x >= m_width || y >= m_height) {
3 return FT::None;
4 }
5 return m_data.at(y).at(x);
6 }
Wyświetlanie mapy
To nie koniec pracy. Zapewne chciałbyś już, Drogi Czytelniku, zobaczyć mapę (nie oszukujmy
się - reprezentacja mapy w pamięci jest mało interesująca). W tym celu zaprogramujemy
klasę - siatkę sprite'ów. (plik SpriteGrid.h):
Pokaż/ukryj kod
1 #ifndef __SPRITE_GRID_H__
2 #define __SPRITE_GRID_H__
3
4 #include
5 #include "Sprite.h"
6 #include "Level.h"
7
8 class SpriteGrid {
9 public:
10 explicit SpriteGrid();
11
12 void SetLevel(const LevelPtr lvl, double dx);
13 void Draw(double dx) const;
14
15 void StoreSprite(FT::FieldType ft, SpritePtr p);
16
17 private:
18 void SetSprite(size_t x, size_t y, SpritePtr sprite) {
19 m_grid.at(y).at(x) = sprite;
20 }
21
22 private:
23 std::vector< std::vector< SpritePtr > > m_grid;
24
4 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
25 std::vector m_sprites;
26 };
27
28 #endif
Jak widać, mamy metody do ustawiania pola siatki na określony sprite (SetSprite), do
ustawiania poziomu wraz z przesunięciem (SetLevel), do rysowania mapy (Draw) oraz do
zapamiętywania sprite'ów (czyli kojarzenia sprite'a z typem pola) (StoreSprite).
Pole m_grid reprezentuje naszą wirtualną siatkę (tę z pierwszego rysunku). W tablicy
m_sprites w polu m_sprites[t] jest sprite reprezentujący typ pola t.
Jako uważny Czytelnik, na pewno zastanawiasz się właśnie, czym jest przesunięcie. Gracz
podczas gry będzie się poruszał po mapie, zatem będzie widział zawsze pewien fragment
poziomu. Klasa SpriteGrid musi wiedzieć, który fragment poziomu narysować. Możemy
przekazać tę informację klasie np. poprzez parametr określający, jak bardzo gracz jest
przesunięty względem poziomu (albo poziom względem gracza).
Już za chwilę będziemy mogli ujrzeć mapę na ekranie monitora. Najpierw jednak musimy
dodać do klasy Renderer dwie metody, które pomogą nam w implementacji metod klasy
SpriteGrid (plik Renderer.h):
1 size_t GetHorizontalTilesOnScreenCount() const {
2 return 1.0 / m_tile_width + 0.5;
3 }
4 size_t GetVerticalTilesOnScreenCount() const {
5 return 1.0 / m_tile_height + 0.5;
6 }
Mamy już wszystkie potrzebne informacje, aby zdefiniować SpriteGrid (plik SpriteGrid.cpp):
1 #include "SpriteGrid.h"
2 #include "Engine.h"
Konstruktor musi jedynie pamiętać, aby stworzyć siatkę o szerokości o jedno pole większej niż
liczba pełnych widocznych pól na ekranie. Zauważmy, że gdy gracz poruszy się o połowę
kafla w prawą stronę, widać połowę następnego kafla z prawej strony. Ze względu na właśnie
ten przypadek musimy znać następny kafel, zanim będzie on całkowicie widoczny.
5 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
1 SpriteGrid::SpriteGrid() {
2 psize_t height = Engine::Get().Renderer()->GetVerticalTilesOnScreenCount();
3 size_t width = Engine::Get().Renderer()->GetHorizontalTilesOnScreenCount();
4 width++; /
5 m_grid.resize(height);
6 for (size_t i = 0; i < height; ++i) {
7 m_grid.at(i).resize(width);
8 }
9 }
SetLevel zapisuje w komórkach siatki sprite'y. Są one dobrane na podstawie odpowiednio
przesuniętego poziomu. Ta metoda iteruje po wszystkich polach siatki.
1 void SpriteGrid::SetLevel(const LevelPtr lvl, double dx) {
2 int half_grid_width = (m_grid.at(0).size()-1) / 2;
3
4 for (size_t y = 0; y < m_grid.size(); ++y) {
5 std::vector& row = m_grid.at(y);
6 for (size_t x = 0; x < row.size(); ++x) {
Współrzędnej y w siatce odpowiada współrzędna y poziomu gry (mapa nigdy nie przesuwa
się w pionie). Współrzędnej x odpowiada x przesunięte o długość połowy ekranu (pozycja
gracza na ekranie) do tyłu i o pozycję gracza do przodu (gracz przesuwa się o jeden kafel,
gdy poziom na ekranie przesuwa się o jeden kafel).
1 int draw_x = x + static_cast(dx) - half_grid_width + 1;
2 int draw_y = y;
Teraz możemy pobrać typ pola z wcześniej napisanej klasy Level. Na tej podstawie wystarczy
wybrać odpowiedni sprite.
6 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
1 const FT::FieldType& ft = lvl->Field(draw_x, draw_y);
2 if (ft != FT::None) {
3 SetSprite(x, y, m_sprites.at(ft));
4 }
5 else {
6 SetSprite(x, y, SpritePtr());
7 }
8 }
9 }
10 }
Znamy przesunięcie, czyli pozycję kafli w przestrzeni świata (dx). Jednak wyświetlając siatkę,
potrzebujemy jej pozycji w przestrzeni oka (ang. eye space - to, co widzisz na ekranie). Gdyby
kafle miały wymiary 1x1, to obie przestrzenie byłyby identyczne. Tak nie jest, dlatego
potrzebujemy przekształcić pozycję kafli w świecie na pozycję kafli w przestrzeni oka. Kafel
ma szerokość tile_width (a nie 1), czyli jest przeskalowany. Zatem naszym przekształceniem
będzie po prostu tile_width*pozycja. Dodatkowo, przesuniemy kafle o 0.45, gdyż chcemy, aby
gracz był w środku ekranu.
Gdybyśmy w tym momencie zakończyli obsługę mapy, przesuwałaby się ona skokowo - tzn.
zawsze podczas przejścia na następny kafel, posuwałaby się o 1. Nas oczywiście interesuje
płynne przejście. Aby uzyskać taki efekt, wystarczy policzyć część ułamkową dx (bo przy
powyższej procedurze jest ona ignorowana) i przesunąć mapę na ekranie w lewo (przy
pomocy glTranslatef) o tę część ułamkową. Dzięki temu gracz będzie miał wrażenie, że mapa
przesuwa się w sposób ciągły.
7 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
Pozostała część kodu nie powinna sprawiać nam problemu - po prostu iterujemy po
wszystkich kaflach i rysujemy je (wywołujemy metodę DrawCurrentFrame, omówiliśmy ją w
poprzednim artykule).
Pokaż/ukryj kod
1 void SpriteGrid::Draw(double dx) const {
2 const double tile_width = Engine::Get().Renderer()->GetTileWidth();
3 const double tile_height = Engine::Get().Renderer()->GetTileHeight();
4
5 glPushMatrix();
6 {
7 glTranslatef(dx*tile_width-0.45, 0, 0);
8
9 double offset = dx - static_cast(dx);
10 glTranslatef(-offset * tile_width, 0, 0);
11 for (size_t y = 0; y < m_grid.size(); ++y) {
12 const std::vector& row = m_grid.at(y);
13 for (size_t x = 0; x < row.size(); ++x) {
14 const SpritePtr& sprite = row.at(x);
15 if (sprite) {
16 sprite->DrawCurrentFrame(x * tile_width,
17 1.0 - (y+1) * tile_height,
18 tile_width, tile_height);
19 }
20 }
21 }
22 }
23 glPopMatrix();
24 }
25
Zrozumienie działania metody StoreSprite także nie powinno być dla nas kłopotliwe.
1 void SpriteGrid::StoreSprite(FT::FieldType ft, SpritePtr sp) {
2 if (m_sprites.size() <= static_cast(ft))
3 m_sprites.resize(ft + 1);
4 m_sprites.at(ft) = sp;
5 }
Wspaniale!!! Napisaliśmy już cały potrzebny kod, aby wyświetlić mapę.
Integrujemy SpriteGrid z grą
8 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
Do klasy App dodajemy pola (plik App.h):
1 LevelPtr m_level;
2 SpriteGrid m_level_view;
Następnie w metodzie App::Draw, tuż przed rysowaniem gracza, dodajemy te dwie linijki,
które wyświetlą siatkę na podstawie poziomu i pozycji gracza:
1 m_level_view.SetLevel(m_level, m_player->GetX());
2 m_level_view.Draw(m_player->GetX());
Nie zapominamy także o nowych plikach nagłówkowych w App.h:
1 #include "Level.h"
2 #include "SpriteGrid.h"
Powinniśmy również zmienić konstruktor App::App, aby wyglądał następująco (plik App.h):
Pokaż/ukryj kod
1 explicit App(size_t win_width, size_t win_height, bool fullscreen_mode) :
2 m_window_width(win_width),
3 m_window_height(win_height),
4 m_fullscreen(fullscreen_mode) {
5
6 m_level_view.StoreSprite(FT::PlatformLeftEnd,
7 SpritePtr(new Sprite(
8 Engine::Get().SpriteConfig()->Get("platform_left"))));
9 m_level_view.StoreSprite(FT::PlatformMidPart,
10 SpritePtr(new Sprite(
11 Engine::Get().SpriteConfig()->Get("platform_mid"))));
9 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
12 m_level_view.StoreSprite(FT::PlatformRightEnd,
13 SpritePtr(new Sprite(
14 Engine::Get().SpriteConfig()->Get("platform_right"))));
15
16 m_level.reset(new Level());
17 m_level->LoadFromFile("data/1.lvl");
18 m_player.reset(new Player(9, 5, m_level->GetWidth()));
19 }
20
Jak widzisz, wykorzystujemy tu metodę StoreSprite, aby powiązać sprite'y z odpowiednimi
typami platformy. Następnie wczytujemy poziom z pliku. Zwróćmy uwagę, że dane są
wczytywane na podstawie konfiguracji sprite'ów. Dlatego musimy ją jeszcze odpowiednio
uzupełnić. Nowa metoda SpriteConfig::SpriteConfig wygląda teraz tak:
1 SpriteConfig::SpriteConfig() {
2 Insert("player_right",
3 SpriteConfigData(DL::Player, 5, 0.2, 0, 4 * 32, 32, 32, true));
4 Insert("player_left",
5 SpriteConfigData(DL::Player, 5, 0.2, 0, 5 * 32, 32, 32, true));
6 Insert("player_stop",
7 SpriteConfigData(DL::Player, 1, 0.2, 0, 6 * 32, 32, 32, true));
8
9 Insert("platform_left",
10 SpriteConfigData(DL::Foreground, 1, 1, 0, 1*32, 32, 32, true));
11 Insert("platform_mid",
12 SpriteConfigData(DL::Foreground, 1, 1, 0, 2*32, 32, 32, true));
13 Insert("platform_right",
14 SpriteConfigData(DL::Foreground, 1, 1, 0, 3*32, 32, 32, true));
15 }
Ustawiliśmy wielkość widocznej mapy na 20x20. Zatem szerokość i wysokość kafla to 0.05
(wówczas na mapie mieści się 20x20 kafli). Dlatego w pliku Renderer.h zmieniamy konstruktor
na taki:
1 Renderer() :
2 m_tile_width(0.05), m_tile_height(0.05) {
3 }
Zwróćmy jeszcze uwagę, że SpriteGrid jest tworzony w konstruktorze App::App. SpriteGrid
wymaga wówczas, aby renderer był w pełni zainicjalizowany. Dlatego do main.cpp musimy
dodać ładowanie silnika. Obecnie main.cpp powinien wyglądać tak:
Pokaż/ukryj kod
1 #include
2 #include "App.h"
3 #include "Engine.h"
4
5 int main(int argc, char *argv[]) {
6 std::cout << " strzałki lewo/prawo - poruszanie się postacią\n"
7 << " strzałka do góry - skok\n"
8 << " przytrzymanie klawisza d - bieganie"
9 << std::endl;
10
11
10 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
12 Engine::Get().Load(); /// *NOWE*
13
14 App app(600, 400, false);
15 app.Run();
16 return 0;
17 }
Teraz możemy już skompilować i uruchomić program. Zobaczysz poziom. Możesz się po nim
poruszać przy pomocy strzałek. Gracz zawsze jest w środku. Nie ma jeszcze kolizji (zajmiemy
się tym pózniej), dlatego niemożliwe jest wskakiwanie na platformy.
Pierwsza obiecana modyfikacja
OK. Zostały nam jeszcze dwie kwestie do rozwiązania na dzisiaj. Najpierw sprawmy, aby
gracz nie mógł cofnąć się do miejsca, które stracił z widoku. Spróbuj sam zaimplementować
taką funkcjonalność!
Na początek dodamy nowe pole do klasy gracza. Jako ostatnie pole dopisz:
1 double m_max_x_pos;
Będzie ono pamiętało maksymalną pozycję, na jakiej był gracz. Gracz nigdy nie może cofnąć
się dalej niż na pozycję m_max_x_pos - połowa szerokości ekranu w kaflach. Wynika to z
faktu, że gracz widzi połowę ekranu w kaflach wstecz, zatem będąc na pozycji m_max_x_pos
nie mógł widzieć tego, co jest przed pozycją m_max_x_pos - połowa szerokości ekranu w
kaflach. Oznacza to, że nie może już tam wrócić. Po dodaniu pola pozostaje nam jego
ustawienie w konstruktorze Player::Player (plik Player.cpp). Linijki:
11 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
1 m_can_go_left(true),
2 m_can_go_right(true) {
3 SetDefaultMoving();
4 }
zamieniamy na:
1 m_can_go_right(true),
2 m_max_x_pos(x) { // *NOWE*
3 SetDefaultMoving();
4 }
Musimy jeszcze zmienić metodę Player::Update (plik Player.cpp). Odpowiedni kod dodamy za
linijkami:
1 // nie można wyjść poza mapę
2 if (m_x < 0) {
3 m_x = 0; // nie można wyjść za początek mapy
4 } else if (m_x > m_level_width - 1) {
5 m_x = m_level_width - 1; // nie można wyjść za ostatni kafel mapy
6 }
tak, aby ostatecznie wyglądało to w ten sposób:
1 // nie możemy cofnąć się do miejsc, których już nie widzimy
2 if (m_x > m_max_x_pos) {
3 m_max_x_pos = m_x;
4 }
5 const size_t half_screen_tiles_count =
6 (Engine::Get().Renderer()->GetHorizontalTilesOnScreenCount()-1)/2;
7 if (m_x < m_max_x_pos - half_screen_tiles_count) {
8 m_x = m_max_x_pos - half_screen_tiles_count;
9 }
Działanie jest bardzo proste. Na początek uaktualniamy m_max_x_pos (pierwszy if).
Następnie sprawdzamy, czy aktualna pozycja nie jest wcześniejsza niż pierwsza dozwolona.
Jeżeli tak, to nie pozwalamy na ruch.
Selektywne przewijanie mapy
12 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
13 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
Zapewne widzisz już, że przy podchodzeniu do krawędzi gra wygląda nienaturalnie
(szczególnie przy cofaniu się poza m_max_x_pos). Spróbujmy to poprawić.
Na początek powinniśmy zdefiniować, kiedy mapa powinna się przewijać, a kiedy nie. W
klasie Player (plik Player.h) definiujemy taką metodę:
Pokaż/ukryj kod
1 bool MoveMap() {
2 const size_t screen_tiles_count =
3 Engine::Get().Renderer()->GetHorizontalTilesOnScreenCount();
4 const size_t half_screen_tiles_count = screen_tiles_count/2;
5
6 return
7 m_x >= m_max_x_pos
8 &&
9 m_x < (m_level_width - 1) - half_screen_tiles_count;
10 }
11
Mapa porusza się, gdy gracz nie próbuje się cofnąć (m_x >= m_max_x_pos) oraz gdy
jesteśmy oddaleni od końca poziomu o więcej niż połowę ekranu (m_x < (m_level_width - 1) -
half_screen_tiles_count). Pamiętajmy o dodaniu nagłówka #include "Engine.h", skoro teraz
używamy Engine w klasie Player.
Sam warunek to nie wszystko. Trzeba go jeszcze gdzieś wykorzystać :-) Najprostszy sposób
to drobna modyfikacja metody App::Draw (plik App.cpp). Zrobimy to tak, że w klasie App
zapamiętasz pole, które będzie oznaczało ostatnią pozycję, przy której mapa powinna się
przesunąć. Tę wartość będziemy uaktualniali w metodzie App::Draw. Użyjemy jej do
rysowania mapy zamiast pozycji gracza. Modyfikujemy kod:
14 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
1 void App::Draw() {
2 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3 glLoadIdentity();
aby wyglądał tak:
1 void App::Draw() {
2 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3 glLoadIdentity();
4
5 if (m_player->MoveMap()) {
6 m_stored_player_pos_x = m_player->GetX();
7 }
Pole m_stored_player_pos_x typu double należy dodać do klasy App i ustawić na 1.0 w
konstruktorze.
Wracając do metody Draw, zamień teraz:
1 glTranslatef(-(m_player->GetX()
2 *
3 Engine::Get().Renderer()->GetTileWidth() - 0.45),
4 0, 0);
5 glMatrixMode(GL_MODELVIEW);
6
7 m_level_view.SetLevel(m_level, m_player->GetX());
8 m_level_view.Draw(m_player->GetX());
na:
1 glTranslatef(-(m_stored_player_pos_x
2 *
3 Engine::Get().Renderer()->GetTileWidth() - 0.45),
4 0, 0);
5 glMatrixMode(GL_MODELVIEW);
6
7 m_level_view.SetLevel(m_level, m_stored_player_pos_x);
8 m_level_view.Draw(m_stored_player_pos_x);
Poprawiamy błędy
Moglibyśmy w tym momencie skończyć, ale przez powyższą zmianę powstał drobny problem.
Spróbujmy przejść postacią gracza do prawej krawędzi mapy i cofnąć się. Okazuje się, że
możesz to zrobić tylko o połowę ekranu (tak, jak chcieliśmy). Niestety, widzimy cały ekran,
więc wydaje się, że gracz utknął na środku. Musimy lekko poprawić metodę Player::Update.
Zmiana powinna sprawdzać ten przypadek (gracz jest dalej niż długość poziomu - połowa
długości ekranu) i wówczas nie modyfikować m_max_x_pos.
Dopisz kawałek za linijkami:
1 const size_t half_screen_tiles_count =
2 (Engine::Get().Renderer()->GetHorizontalTilesOnScreenCount()-1)/2;
3 if (m_x < m_max_x_pos - half_screen_tiles_count) {
15 z 16 2012-12-21 17:02
Gra 2D, część 3: Wyświetlanie przewijanej mapy http://informatyka.wroc.pl/print/474
4 m_x = m_max_x_pos - half_screen_tiles_count;
5 }
aby całość wyglądała tak:
1 const size_t half_screen_tiles_count =
2 (Engine::Get().Renderer()->GetHorizontalTilesOnScreenCount()-1)/2;
3 if (m_x < m_max_x_pos - half_screen_tiles_count) {
4 m_x = m_max_x_pos - half_screen_tiles_count;
5 }
6 if (m_x > m_level_width - half_screen_tiles_count - 2) {
7 m_max_x_pos = m_level_width - half_screen_tiles_count - 2;
8 }
Gratulacje
Świetnie! Właśnie udało nam się zaimplementować nietrywialną obsługę scrollowanej mapy.
Aby ugruntować tę wiedzę wykonajmy poniższe zadania.
Kod końcowy - znajdziesz go tutaj [6]
Masz pytanie, uwagę? Zauważyłeś błąd? Powiedz o tym na forum [7].
Zadania
1. Dodaj kolejny typ kafla - np. różowy kwadrat. Zmień plik poziomu tak, aby było kilka
takich kafli.
2. Dodaj przesuwane tło. Stwórz nową klasę Background. Ta klasa powinna rysować coś w
tle (np. duży obrazek). Zadbaj o to, aby tło przesuwało się wraz z mapą.
3. Dodaj do tła paralaksę. Tło powinno składać się z kilku warstw (myślimy o nich, jakby
były poukładane od najbliższych do najdalszych). Im warstwa jest bliżej, tym wolniej się
przesuwa i tym większe elementy zawiera.
Poprzedni artykuł - poruszanie postacią [1] Następny artykuł - Hall of Fame [2]
Adres zródła: http://informatyka.wroc.pl/node/474
Odnośniki:
[1] http://informatyka.wroc.pl/node/422
[2] http://informatyka.wroc.pl/node/475
[3] http://informatyka.wroc.pl/upload/mmi/platf/03_kod_poczatkowy.zip
[4] http://informatyka.wroc.pl/upload/mmi/platf/03_1.lvl
[5] http://informatyka.wroc.pl/upload/mmi/platf/03_tex.bmp
[6] http://informatyka.wroc.pl/upload/mmi/platf/03_kod_wynikowy.zip
[7] http://informatyka.wroc.pl/forum/viewtopic.php?f=55&t=358
16 z 16 2012-12-21 17:02


Wyszukiwarka

Podobne podstrony:
Gra 2D, część 4 Piszemy Hall of Fame
Gra 2D, część 4 Piszemy Hall of Fame
Gra 2D, część 2 Wyżej, dalej, szybciej poruszanie postacią
Gra Memory Owoce (Fruits), częśc 1
Alfanumeryczne wyświetlacze LCD Cześć 2

więcej podobnych podstron