Gra 2D, część 8: Dobrze mieć wybór... poziomu

25.08.2010 - Marcin Milewski
TrudnośćTrudność

Implementacja klasy LevelChoiceScreen

Plik nagłówkowy klasy LevelChoiceScreen

Skoro określiliśmy już elementy, które należy wykonać, to możemy przystąpić do implementacji. Podstawowym elementem odpowiedzialnym za działanie ekranu wyboru poziomów będzie klasa LevelChoiceScreen będąca nowym stanem naszej aplikacji, czyli podklasą AppState. Tworzymy zatem nowy plik LevelChoiceScreen.h. Jego pierwsza część to przede wszystkim deklaracje metod z klasy bazowej. Zwróćmy jednak uwagę na SetPlayer. Funkcja ta będzie nam potrzebna do przechowywania wskaźnika do naszej postaci. Tak samo będziemy chcieli przechowywać wskaźnik na LevelChoiceScreen w klasie Game, ale o tym za chwilę. Początek wspomnianego pliku jest dość typowy:

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
#ifndef __LEVELCHOICESCREEN_H_INCLUDED__
#define __LEVELCHOICESCREEN_H_INCLUDED__
 
#include <map>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
 
#include "AppState.h"
#include "Sprite.h"
 
class Player;
typedef boost::shared_ptr<Player> PlayerPtr;
 
 
class LevelChoiceScreen: public AppState, 
             public boost::enable_shared_from_this<LevelChoiceScreen> {
public:
    explicit LevelChoiceScreen(PlayerPtr player);
    virtual ~LevelChoiceScreen();
    void Init();
    void Start();
 
    void Draw();
    bool Update(double dt);
    void ProcessEvents(const SDL_Event& event);
 
    boost::shared_ptr<AppState> NextAppState() const;
 
    void SetPlayer(PlayerPtr player);
    

Jest to nasze pierwsze spotkanie z enable_shared_from_this, więc powiedzmy kilka słów o tej konstrukcji. Dlaczego jej potrzebujemy? Otóż, sprawcą zamieszania są wykorzystywane przez nas inteligentne wskaźniki (czyli wszechobecne boost::shared_ptr). Wykorzystują one mechanizm zliczania referencji, co oznacza, że wskaźnik przechowuje informację o łącznej liczbie wskaźników wskazujących na dany obiekt. Kiedy liczba ta zmniejszy się do 0, obiekt zostaje zwolniony. Pojawia się więc pytanie czy nie możemy użyć zwykłych wskaźników lub konstrukcji boost::shared_ptr(this)?

W pierwszym przypadki (użycie zwykłych wskaźników), problem polega na tym, że wszędzie używamy inteligentnych wskaźników, więc przekazanie "gołego" wskaźnika zaowocuje błędem kompilacji (niezgodność typów). Ponadto, zdecydowaliśmy się na inteligentne wskaźniki, aby nie martwić się o zarządzanie pamięcią, a zrobienie wyjątku od reguły prędzej czy później skończy się wyciekami pamięci.

W drugim przypadku (konstrukcja shared_prt(this)) wyobraźmy sobie sytuację, kiedy zwracamy (bądź gdzieś przekazujemy) boost::shared_ptr(this), ale nikt z tego wskaźnika nie korzysta (czyli np. nie zapamięta go). Wówczas jest on niszczony, więc licznik referencji zmniejsza się do 0 i obiekt jest usuwany. Może się niestety okazać, że istniały jeszcze inne inteligentne wskaźniki do tego obiektu i działanie programu kończy się naruszeniem ochrony pamięci (czyli także zakończeniem działania programu). Znalezienie takiego błędu potrafi być czasochłonne.

Zainteresowanym polecamy przeczytanie dokumentacji oraz zapoznanie się z typem boost::weak_ptr.

W drugiej części deklarujemy pomocnicze metody (stąd też słówko private) oraz strukturę Point, która jest wygodnym opakowaniem na współrzędne w kartezjańskim układzie współrzędnych. Wyczerpujące uwagi określające przeznaczenie poszczególnych metod znajdują się w ich komentarzach.

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
private:
 
    // następny węzeł (dowolny, z którym jest połączenie). 
    // Lub node, jeżeli nie ma połączeń
    int NextNode(size_t node) const;
 
    // zwraca nazwę poziomu dla wskazanego węzła
    std::string NodeToLevelName(int node);
 
    // Zmienia kierunek ruchu na 'idź w lewo' i zwraca true. 
    // Jeżeli zmiana nie była możliwa, zwraca false.
    // pozostałe metody analogicznie
    bool GoLeft();
    bool GoRight();
    bool GoUpward();
    bool GoDown();
 
    // Uruchamia konkretny poziom na podstawie węzła, 
    // w którym stoi postać. Jeżeli postać nie stoi, to nie robi nic.
    void RunLevelFromNode();
 
    // rysuje drogę z punktu from do punktu to
    void DrawRoad(size_t from, size_t to) const;
 
    struct Point {
        Point(double x, double y) : x(x), y(y) {}
        double operator[](int idx) const { return (idx==0?x:y); }
        double& operator[](int idx) { return (idx==0?x:y); }
        double x, y;
    };
 
    typedef std::vector<bool> BoolVector;
    typedef std::vector<int> IntVector;
 
  

Trzecia i ostatnia część klasy LevelChoiceScreen to przechowywane dane. Przede wszystkim umieszczamy tutaj graf połączeń. Składa się on między innymi z listy wierzchołków. Każdy z nich będzie miał swój numer (od 0 do n-1, gdzie n jest liczbą węzłów), pozycję na ekranie (reprezentowaną przez strukturę Point), a także odpowiadający mu poziom do uruchomienia. Krawędzie natomiast będziemy pamiętać jako listę sąsiedztwa.

Poza grafem będziemy przechowywać informacje o położeniu postaci na ekranie, a także o numerach węzłów, między którymi aktualnie się przemieszcza. Jeżeli postać stoi, to numery tych węzłów będą sobie równe. Ponadto będziemy dbali, żeby węzeł (węzły są reprezentowane przez liczby naturalne), do którego zmierza postać był zawsze w current_to_node. Dzięki temu różnica current_to_node-face_pos wyznacza nam wektor prędkości postaci.

Nie należy zapominać o tym, że aby wyświetlić cokolwiek na ekranie potrzebujemy sprite'ów. Stąd w tej części klasy umieszczamy kilka wskaźników na nie - po jednym dla każdego z elementów: droga pionowa oraz pozioma, węzeł do którego można (lub nie można) wejść, postać). Oto opisywana (trzecia) część pliku LevelChoiceScreen.h

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
    // pozycje węzłów
    std::vector<Point> m_positions;                
 
    // połączenia między węzłami
    std::vector<IntVector> m_connections;          
 
    // mapowanie węzeł -> nazwa poziomu
    std::map<int, std::string> m_node_to_level_name;
 
    Point m_face_pos;                    // położenie bohatera na ekranie
    int m_current_from_node;             // numer węzła początkowego
    int m_current_to_node;               // numer węzła docelowego
 
    SpritePtr m_horizontal_road_sprite;  // animacja drogi w poziomie
    SpritePtr m_vertical_road_sprite;    // animacja drogi w pionie
    SpritePtr m_entry_enabled_sprite;    // animacja aktywnego węzła
    SpritePtr m_entry_disabled_sprite;   // animacja nieaktywnego węzła
    SpritePtr m_face_sprite;             // animacja sprite'a bohatera
 
    double m_tile_width;                 // szerokość kafla na ekranie
    double m_tile_height;                // wysokość kafla na ekranie
 
    // wskaźnik na gracza, który przekażemy do instancji gry
    PlayerPtr m_player;             
    boost::shared_ptr<AppState> m_next_app_state;
};
 
#endif
  
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com