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

01.10.2012 - Marcin Milewski
TrudnośćTrudność

Zobaczmy teraz, co kryje się metodach SaveGame oraz LoadGame. Pierwsza z nich kopiuje obiekt bohatera do zmiennej m_stored_player oraz pole m_player_saved_pos_x do m_stored_saved_player_pos_x.
Druga z nich, co jest trochę zaskakujące, niczego nie przywraca. Wszystko za sprawą użycia efektu przejścia. W momencie utraty życia przez gracza, gra wykona efektowne przejście... sama w siebie. Zgodnie z podanym niżej przepisem nastąpi zaciemnienie, a potem przełączenie na stan spod zmiennej game równej shared_from_this() (odpowiednik this). Stąd też wzięło się pole m_should_load_when_active_again. W momencie uruchomienia metody Start w klasie App, gra musi wiedzieć, że nastąpił respawn i przenieść gracza w odpowiednie miejsce.

Oczywiście cała ta gimnastyka jest zbędna jeżeli zadowolimy się zwykłym przeskoczeniem obrazu do nowego stanu. Można jednak założyć się, że gracz byłby co najmniej zaskoczony nagłą zmianą scenerii. Dlatego warto poświęcić chwilę na dodanie efektu płynnego przejścia. Oto ciała metod SaveGame i LoadGame:

1
2
3
4
5
6
7
8
9
10
11
12
13
void Game::SaveGame(PlayerPtr player) {
    m_saved_player.reset(new Player(*(m_player.get())));
    m_saved_stored_player_pos_x = m_stored_player_pos_x;
}
 
void Game::LoadGame() {
    m_should_load_when_active_again = true;
 
    GamePtr game = shared_from_this();
    tefPtr fadeout = TransitionEffect::PrepareFadeOut(game)
                                 .to(game).duration(1).delay(.2, .2).Build();
    m_next_app_state = fadeout;
} 

Właściwe odtworzenie stanu znajduje się w metodzie Game::Start (która poprzednio składała się wyłącznie z linii odgrywającej muzykę):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Plik: Game.cpp
void Game::Start() {
    Engine::Get().GetSound()->PlayMusic("game");               // STARE
    if (m_should_load_when_active_again) {
        m_player->RespawnFrom(m_saved_player);
        m_stored_player_pos_x = m_saved_stored_player_pos_x;
        m_should_load_when_active_again = false;
    } else {
        // Zapisujemy na początku poziomu
        SaveGame(m_player);
    }
    SetDone(false);
    m_next_app_state.reset();
}  

Do metody Init dopisujemy sprawdzenie stanu flagi m_should_load_when_active_again. Jeżeli jest ustawiona, to znaczy że poziom już został załadowany z pliku, więc procedura kończy swoje działanie.

1
2
3
4
5
6
7
8
9
10
void Game::Init() {
    if (m_should_load_when_active_again) {        // NOWE
        return;                                   // NOWE
    }                                             // NOWE
 
    Engine& engine = Engine::Get();
 
    if (!m_level) {
    // (...)
}  

Przytoczoną przed chwilą metodę LoadGame wywołujemy w Game::Update po aktualizacji stanu bohatera. Oczywiście najpierw sprawdzamy czy postać sobie tego życzy.

1
2
3
4
5
6
7
// Plik: Game.cpp, metoda Update
    // uaktualnij obiekt reprezentujący gracza
    m_player->Update(dt, m_level);
    if (m_player->ShouldBeRespawned()) {                                 // NOWE
        LoadGame();                                                      // NOWE
        SetDone();                                                       // NOWE
    }                                                                    // NOWE

Ostatnia zmiana to usunięcie z metody SweepAndAddEntities kawałka kodu usuwającego jednostki, których gracz nie może już zobaczyć. Niestety, teraz bohater może się cofnąć na planszy, więc dlaczego mamy mu ułatwiać zadanie usuwając przeciwników z którymi się jeszcze nie uporał?

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
void Game::SweepAndAddEntities(double /* dt */) {
// TEN FRAGMENT USUWAMY
//    // oznacz jednostki, które są za lewą krawędzią ekranu jako martwe
//    const double distance_of_deletion =
//          Engine::Get().GetRenderer()->GetHorizontalTilesOnScreenCount();
//    for (std::vector<EntityPtr>::iterator it = m_entities.begin();
//         it != m_entities.end();
//         ++it) {
//        EntityPtr e = *it;
//        if (e->GetX() + distance_of_deletion < m_player->GetX()) {
//            e->SetIsDead(true);
//        }
//    }
 
    // usuń martwe jednostki
    for (size_t i = 0; i < m_entities.size(); ++i) {
        if (m_entities.at(i)->IsDead()) {
            for (size_t c = i; c < m_entities.size() - 1; ++c) {
                m_entities.at(c) = m_entities.at(c + 1);
            }
            m_entities.resize(m_entities.size() - 1);
        }
    }
 
    // dodaj kolejne jednostki z listy do gry
    // (...)
}  

Testujemy!

W celu przetestowania jak działa nasz kod, możemy dodać punkt zapisu gdzieś na planszy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ale po co jej szukać gdzieś w pliku?
player 1 2
 
# kasa, kasa, kasa!
orb 2 8
orb 3 7
orb 4 7
 
# pozostałe obiekty
mush 2 1
mush 4 1
mush 5 1
 
twinshot_upgrade 4 3
 
savepoint 4 4                 # NOWE
  
A oto efekt końcowy:

Masz pytanie, uwagę? Stworzyłeś poziom, którym chcesz się podzielić? A może zauważyłeś błąd? Powiedz o tym na forum.

Pobierz końcowy kod źródłowy do tego artykułu.

Zadania dla dociekliwych

  1. Dodaj do gry bonus pozwalający graczowi na wyższy skok.
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com