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

18.01.2010 - Łukasz Milewski
TrudnośćTrudność

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);
}
5
Twoja ocena: Brak Ocena: 5 (4 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com