Gra 2D, część 14: Lepszy respawn i kilka szczegółów

01.10.2012 - Marcin Milewski
TrudnośćTrudność

Zapis gry i lepszy respawn bohatera

Zajmiemy się teraz dodaniem do gry elementu oznaczającego pozycję, do której zostanie przeniesiony bohater po utracie życia.

Zaczniemy od utworzenia klasy SavePoint, która będzie reprezentacją obiektu zapisującego pozycję bohatera. Kiedy postać wejdzie z nim w kolizję, wtedy zacznie on być aktywny. W momencie, w którym bohater wypadnie z planszy, zostanie cofnięty do miejsca ostatniego zapisu gry lub na początek poziomu w przypadku jego braku.

Klasa SavePoint, podobnie jak każda encja, dziedziczy po Entity. Domyślne implementacje metod Update oraz Draw zastąpimy własnymi, gdyż obiekt posiada dwa naturalne stany: aktywny oraz nieaktywny, co nie odpowiada tym z klasy bazowej. Każdemu z nich przypiszemy animację. Ten opis prowadzi nas do następującej deklaracji klasy SavePoint:

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
// Plik: SavePoint.h
#ifndef __SAVEPOINT_H_INCLUDED__
#define __SAVEPOINT_H_INCLUDED__
 
#include "Entity.h"
 
class SavePoint : public Entity {
public:
    explicit SavePoint(double x, double y);
    virtual ET::EntityType GetType() const { return ET::SavePoint; }
 
    virtual void Update(double dt, LevelPtr level);
    virtual void Draw() const;
    void SetSprites(SpritePtr active, SpritePtr inactive);
 
    void Activate();
    bool IsActive() const     { return m_is_active; }
    bool IsInactive() const   { return !IsActive(); }
 
private:
    bool m_is_active;                   // Czy punkt jest aktywny
    SpritePtr m_sprite_active;          // Animacja aktywnego punktu
    SpritePtr m_sprite_inactive;        // Animacja nieaktywego punktu
};
 
typedef boost::shared_ptr<SavePoint> SavePointPtr;
 
#endif  

Obiekt tej klasy zaczyna swoje istnienie w stanie nieaktywnym. Po zetknięciu się z bohaterem staje się aktywny, co realizujemy przez wywołanie metody Activate. Zastanawiające może być, po co klasa ma pole m_is_active? Odpowiedź jest bardzo prosta: gdyby go nie było, kolizja postaci z tym obiektem powodowałaby jego ponowną aktywację, a więc również zresetowanie animacji. Zdecydowanie chcemy tego uniknąć.

Jak widać, w powyższym kodzie występuje ET::SavePoint. Taki element należy dodać do wyliczenia ET w pliku Types.h. W bliźniaczym Types.cpp dodajemy wiersz odpowiedzialny za zmianę ET::SavePoint na ciąg znaków "savepoint". Podobne uaktualnienie należy wprowadzić do fabryki jednostek (plik EntityFactory.cpp), a odpowiednie sprite'y zdefiniować w konstruktorze klasy SpriteConfig. Nie opisujemy tutaj dokładnie tych zmian, gdyż niemal identyczne postępowanie było już przeprowadzane w tym artykule.

Wróćmy do klasy SavePoint i zobaczmy jak wygląda jej implementacja. W konstruktorze wywołujemy konstruktor nadklasy, w którym jako wektor prędkości przekazujemy (0, 0) oraz nadajemy początkową wartość polu m_is_active.
Metody SetSprites oraz Activate służą do ustawienia odpowiednich pól. W funkcji Update aktualizujemy animację sprite'ów. Ta operacja jest na tyle tania, że możemy ją przeprowadzić na każdym sprite'cie bez względu na aktualny stan jednostki. Warto też zaznaczyć, że nasze animacje będą odtwarzane w pętli, więc możemy o nich myśleć, że nie mają jasno wyznaczonego początku. Stąd wniosek, że opisany sposób aktualizacji jest ok.

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
// Plik: SavePoint.h
#include "StdAfx.h"
 
#include "Sprite.h"
#include "SavePoint.h"
 
SavePoint::SavePoint(double x, double y)
  : Entity(x, y, 0, 0),
    m_is_active(false) {
 
}
 
void SavePoint::SetSprites(SpritePtr active, SpritePtr inactive) {
    m_sprite_active = active;
    m_sprite_inactive = inactive;
}
 
void SavePoint::Activate() {
    m_is_active = true;
}
 
void SavePoint::Update(double dt, LevelPtr level) {
    m_sprite_active->Update(dt);
    m_sprite_inactive->Update(dt);
}  

Ostatnią metodą, którą należy zaimplementować jest Draw. Całe jej ciało jest bardzo podobne do Entity::Draw. Różnica polega na tym, że rysujemy któryś z dwóch, a nie trzech sprite'ów, a decyzję podejmujemy na podstawie flagi aktywności obiektu, a nie jego ruchu, jak to miało miejsce w klasie Entity. Dlatego nadpisujemy domyślną implementację z klasy bazowej.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Plik: SavePoint.cpp
void SavePoint::Draw() const {
    const double tile_width = Engine::Get().GetRenderer()->GetTileWidth();
    const double tile_height = Engine::Get().GetRenderer()->GetTileHeight();
 
    // wylicz pozycję gracza na ekranie
    const double pos_x = m_x * tile_width;
    const double pos_y = m_y * tile_height;
 
    if (IsActive()) {
       m_sprite_active->DrawCurrentFrame(pos_x, pos_y, tile_width, tile_height);
    } else {
       m_sprite_inactive->DrawCurrentFrame(pos_x, pos_y, tile_width, tile_height);
    }
}  
Animacje obiektu, który pozwala na zapisanie gry. Sprite bez wypełnienia oznacza, że element nie został aktywowany.
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com