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

25.08.2010 - Marcin Milewski
TrudnośćTrudność

Aktualizowanie stanu

Jednym z ważniejszych elementów (obok przetwarzania zdarzeń i rysowania) implementacji nowego stanu aplikacji jest aktualizowanie wszystkich obiektów składowych. W ten sposób dochodzimy do implementacji metody LevelChoiceScreen::Update. Składa się ona z kilku części:

  1. Określenie prędkości postaci. Nie może ona być ani za duża ani zbyt mała. Gracz powinien być w stanie obserwować proces przemieszczania się postaci, ale nie powinno wprowadzać go to w stan uśpienia :) Wartości prędkości najprościej jest dobrać drogą eksperymentu.
  2. Określenie odległości postaci do węzła m_current_to_node oraz wektora prędkości (czyli kierunku, w którym porusza się postać).
  3. Uaktualnieniu położenia postaci oraz jej zatrzymaniu, jeśli cel został osiągnięty.
  4. Aktualizacji stanu wszystkich sprite'ów.
Oto kawałek kodu, który implementuje powyższe punkty:

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
bool LevelChoiceScreen::Update(double dt) {
    // uaktualnij położenie twarzy postaci
    const double face_velocity_x = .6;   // prędkość postaci w poziomie
    const double face_velocity_y = .5;   //  i pionie
    const Point to_node_pos = m_positions.at(m_current_to_node);
    const double dist_x = to_node_pos.x - m_face_pos[0];
    const double dist_y = to_node_pos.y - m_face_pos[1];
    double vel_x = face_velocity_x * sgn(dist_x);
    double vel_y = face_velocity_y * sgn(dist_y);
 
    // sprawdź czy postać należy zatrzymać (bo jest w węźle)
    if (fabs(dist_x) < .01 && fabs(dist_y) < .01) {
        m_current_from_node = m_current_to_node;
        vel_x = vel_y = 0;
        m_face_pos[0] = to_node_pos.x;
        m_face_pos[1] = to_node_pos.y;
    }
 
    // uaktualnij położenie na podstawie prędkości
    m_face_pos[0] += vel_x * dt;
    m_face_pos[1] += vel_y * dt;
 
    // uaktualnij animacje
    m_horizontal_road_sprite->Update(dt);
    m_vertical_road_sprite->Update(dt);
    m_entry_enabled_sprite->Update(dt);
    m_face_sprite->Update(dt);
 
    return !IsDone();
}
  

Rysowanie grafu na ekranie

W tym momencie naszym zadaniem jest wyświetlenie obiektów na ekranie. Jest to drugi element (pewnie nawet najważniejszy, gdyż bez niego nic nie zobaczymy), o który należy zadbać podczas implementacji nowego stanu. Obiekty, które będziemy chcieli wyświetlić to:

  1. Graf, czyli wierzchołki oraz krawędzie.
  2. Postać, którą gracz będzie mógł sterować.
  3. Napis "wybierz poziom" na górze ekranu.

Na Procedura rysująca składa się z dwóch części: pierwszej (głównej), która rysuje węzły, postać, napis oraz deleguje rysowanie drogi do drugiej (pomocniczej) - rysującej drogę między dwoma wierzchołkami. Aby łatwiej było zrozumieć działanie oraz wyłapać ewentualne błędy we fragmencie rysującym drogi, jego działanie podajemy także w pseudokodzie (implementację trudniejszych kawałków kodu zawsze dobrze jest zacząć od zapisania algorytmu w pseudokodzie):

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
void LevelChoiceScreen::Draw() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
 
    // DZIAŁANIE
    // dla każdego węzła i  (i oznacza numer węzła)
    //   dla jego każdego sąsiada j
    //     jeżeli i < j:
    //       narysuj drogę z i do j
    //     else:
    //       jeżeli nie istnieje droga z i do j:
    //         narysuj drogę z j do i
 
    // narysuj drogi
    for (size_t from_node = 0; 
         from_node < m_connections.size(); 
         ++from_node) {
        const IntVector roads = m_connections.at(from_node);
        for (IntVector::const_iterator to_node = roads.begin(); 
             to_node != roads.end(); 
             ++to_node) {
            if (from_node < (size_t)*to_node) {
                DrawRoad(from_node, (size_t)*to_node);
            } else if ((size_t)*to_node < from_node) {
                // drogi dwukierunkowe rysujemy jednokrotnie
                const IntVector cs = m_connections.at(*to_node);
                if (std::find(cs.begin(), cs.end(), from_node) 
                    == cs.end()) {
                    DrawRoad(*to_node, from_node);
                }
            }
        }
    }
  

Oto pozostała część metody LevelChoiceScreen::Draw

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
    // narysuj węzły
    for (size_t node_id = 0; node_id < m_positions.size(); ++node_id) {
        const double x = m_positions[node_id][0] - m_tile_width / 2;
        const double y = m_positions[node_id][1] - m_tile_height / 2;
 
        // czy można skorzystać z tego węzła 
        // (na razie wszystkie węzły są aktywne)
        bool node_enabled = true; 
        if (node_enabled) {
            m_entry_enabled_sprite->DrawCurrentFrame(x, y, 
                                                     m_tile_width, 
                                                     m_tile_height);
        } else {
            m_entry_disabled_sprite->DrawCurrentFrame(x, y, 
                                                     m_tile_width, 
                                                     m_tile_height);
        }
    }
 
    // narysuj postać
    m_face_sprite->DrawCurrentFrame(m_face_pos[0] - m_tile_width / 2,
                                    m_face_pos[1] - m_tile_height / 2,
                                    m_tile_width,
                                    m_tile_height);
 
 
    // tekst na górze ekranu
    Text t;
    t.SetSize(.05, .06);   // rozmiar dobrany eksperymentalnie
    t.DrawText("WYBIERZ POZIOM", .2, .85);
 
    //
    SDL_GL_SwapBuffers();
}
  

Zobaczmy teraz jak wygląda metoda rysująca drogę, tzn. konkretny jej kawałek. Aby narysować połączenie między dwoma węzłami pobieramy ich pozycje na ekranie, a następnie rysujemy odpowiednio długi prostokąt w poziomie lub pionie. Prostokąt rysujemy podając najpierw jego lewy (odpowiednio dolny) wierzchołek, stąd wykorzystanie funkcji std::swap. Przydaje nam się w tym momencie założenie o tym, że drogi nie mogę mieć dowolnych kształtów. Zobaczmy więc kod implementujący rysowanie drogi:

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
void LevelChoiceScreen::DrawRoad(size_t from, size_t to) const {
    Point from_node_pos = m_positions.at(from);
    Point to_node_pos = m_positions.at(to);
    // jeśli droga jest pionowa
    if (from_node_pos[0] - to_node_pos[0]) {
        if (from_node_pos[0] > to_node_pos[0]) {
            std::swap(from_node_pos, to_node_pos);
        }
        m_vertical_road_sprite->SetRepeat(m_tile_width, m_tile_height);
        m_vertical_road_sprite->DrawCurrentFrame(
 
                from_node_pos[0],     
                from_node_pos[1] - m_tile_height / 2,
                to_node_pos[0] - from_node_pos[0],  
                m_tile_height);
    }
    // jeśli droga jest pozioma
    else if (from_node_pos[1] - to_node_pos[1]) {
        if (from_node_pos[1] > to_node_pos[1]) {
            std::swap(from_node_pos, to_node_pos);
        }
        m_horizontal_road_sprite->SetRepeat(m_tile_width, m_tile_height);
        m_horizontal_road_sprite->DrawCurrentFrame(
                from_node_pos[0] - m_tile_width / 2,  
                from_node_pos[1],
                m_tile_width,                         
                to_node_pos[1] - from_node_pos[1]);
    }
}
  
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com