Poprzedni artykuł - poruszanie postacią
Następny artykuł - Hall of Fame
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? Wyobraźmy 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ędź platformy, środek platformy, prawa krawędź platformy i pole puste. W ten sposób będziemy rysowali mapę.
Zaczynamy
Na początek pobieramy kod źródłowy, który jest wynikiem ukończenia poprzedniej lekcji (znajdziesz go tutaj). Do katalogu data wrzucamy plik poziomu pobrany z 1.lvl. Powinniśmy również obok pliku poziomu umieścić nową teksturę z kaflami - tex.bmp.
Reprezentacja poziomu w grze
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ędź platformy
- 2 oznacza środek platformy
- 3 oznacza prawą krawędź 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
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
| #ifndef __LEVEL_H__
#define __LEVEL_H__
#include <boost/shared_ptr.hpp>
#include <string>
#include <vector>
#include "Types.h"
class Level {
public:
explicit Level();
void LoadFromFile(const std::string& filename);
FT::FieldType Field(size_t x, size_t y) const;
size_t GetWidth() const { return m_width; }
size_t GetHeight() const { return m_height; }
private:
size_t m_width;
size_t m_height;
std::vector<std::vector<FT::FieldType> > m_data;
};
typedef boost::shared_ptr<Level> LevelPtr;
#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ędź, środek).
1
2
3
4
5
6
7
8
9
10
| namespace FT {
enum FieldType {
None = 0,
PlatformLeftEnd = 1,
PlatformMidPart = 2,
PlatformRightEnd = 3,
COUNT
};
} |
Przyjrzymy się teraz przykładowej implementacji takiej klasy (plik Level.cpp):
1
2
3
4
5
6
7
8
9
10
| #include <fstream>
#include <iostream>
#include "Engine.h"
#include "Level.h"
Level::Level()
: m_width(0),
m_height(0) {
} |
Ładowanie 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
2
3
4
5
6
7
8
| void Level::LoadFromFile(const std::string& filename) {
std::ifstream lvl(filename.c_str());
if (!lvl) {
std::cerr << "Nie udało się załadować pliku " << filename << "\n";
return;
}
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
2
3
4
| m_data.resize(m_height);
for (size_t y = 0; y < m_height; ++y) {
m_data.at(y).resize(m_width);
} |
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
2
3
4
5
6
7
8
| int tmp;
for (size_t y = 0; y < m_height; ++y) {
for (size_t x = 0; x < m_width; ++x) {
lvl >> tmp;
m_data.at(y).at(x) = FT::FieldType(tmp);
}
}
} |
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
2
3
4
5
6
| FT::FieldType Level::Field(size_t x, size_t y) const {
if (x >= m_width || y >= m_height) {
return FT::None;
}
return m_data.at(y).at(x);
} |