Gra 2D, część 12: Edytor poziomów cz. 2

15.03.2011 - Marcin Milewski
TrudnośćTrudność

Kompresja grafiki, dodanie przezroczystości

Format BMP ma bardzo prostą konstrukcję, ale ponieważ nie jest skompresowany, to zajmuje dużo miejsca. Pojawia się więc pytanie, ile można zyskać? Okazuje się, że całkiem sporo. Nasz nieskompresowany plik z atlasem sprite'ów (tex.bmp) zajmuje 4 MB, natomiast po kompresji (tex.png) tylko 0.18 MB. Ponieważ przerobienie kodu tak, aby obsługiwał nowy format atlasu, jest bardzo proste, warto poświęcić na to chwilę czasu. Pierwszą rzeczą, jaką należy zmienić, jest sposób ładowania obrazka z pliku. Zamieniamy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Plik: Renderer.cpp
(...)
void Renderer::LoadTexture(const std::string & filename) {
    std::cout << "Ładowanie obrazka z pliku " + filename + "\n";
 
    // załaduj z pliku
    SDL_Surface* surface = SDL_LoadBMP(filename.c_str());
    if (!surface) {
 
(...)
 
    GLenum format;
    switch (surface->format->BytesPerPixel) {
    case 3:
        format = GL_BGR;         // w BMP skrajne składowe są zamienione
        break;
    case 4:
        format = GL_BGRA;
        break;
    default:
        std::cerr << "Nieznany format pliku " + filename + "\n";
        exit(1);
    }
  

na

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Plik: Renderer.cpp
(...)
void Renderer::LoadTexture(const std::string & filename) {
    std::cout << "Ładowanie obrazka z pliku " + filename + "\n";
 
    // załaduj z pliku
    SDL_Surface* surface = IMG_Load(filename.c_str());
    if (!surface) {
 
(...)
 
    GLenum format;
    switch (surface->format->BytesPerPixel) {
    case 3:
        format = GL_RGB;         // w PNG jest normalnie
        break;
    case 4:
        format = GL_RGBA;
        break;
    default:
        std::cerr << "Nieznany format pliku " + filename + "\n";
        exit(1);
    }
  

Jest to funkcja z biblioteki SDL_image, więc należy uaktualnić opcje budowania projektu (dodać wspomnianą bibliotekę do opcji konsolidatora):

1
2
3
4
5
6
// Plik: SConstruct
// zamieniamy
env.MergeFlags("-lSDL -lGL -lGLU -lSDL_mixer");
// na
env.MergeFlags("-lSDL -lGL -lGLU -lSDL_mixer -lSDL_image")
  

Skoro zmieniliśmy nazwę pliku, to trzeba uaktualnić ją w metodzie App::Run:

1
2
3
4
5
6
// Plik: App.cpp
// zamieniamy
    const std::string atlas_filename = "data/tex.bmp";
// na
    const std::string atlas_filename = "data/tex.png";
  

Przeroczystość

Skoro jesteśmy przy temacie grafiki, warto zmienić jeszcze jedną rzecz z nią związaną. Włączmy obsługę przezroczystości w naszej grze. Z biblioteką OpenGL jest to dziecinnie proste. Jedno, o czym należy pamiętać, to aby odłożyć na stos atrybuty odpowiedzialne za włączenie przezroczystości oraz ustawienie odpowiedniego trybu. Dodane wiersze kodu zostały oznaczone gwiazdką "*":

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
36
37
38
// Plik: Game.cpp
// Fragment metody Game::Draw
(...)
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    {
*       glPushAttrib(GL_COLOR_BUFFER_BIT);
*       {
*           glEnable(GL_BLEND);
*           glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
            double player_x =
                -(m_stored_player_pos_x
                  * Engine::Get().GetRenderer()->GetTileWidth() - 0.45);
            glTranslated(player_x, 0, 0);
            glMatrixMode(GL_MODELVIEW);
 
            m_level_view.SetLevel(m_level, m_stored_player_pos_x);
            m_level_view.Draw(m_stored_player_pos_x);
 
            // narysuj postać gracza
            m_player->Draw();
 
            // narysuj pozostałe obiekty
            for (std::vector<EntityPtr>::const_iterator it = m_entities.begin();
                 it != m_entities.end();
                 ++it) {
                const EntityPtr e = *it;
                if (e->IsAlive()) {
                    e->Draw();
                }
            }
*       }
*       glPopAttrib();
    }
    glMatrixMode(GL_PROJECTION);
(...)
  

Podsumowanie

Odkąd wynaleziono graficzny interfejs użytkownika, korzystanie z wielu aplikacji stało się prostsze. Chociaż wciąż wiele osób korzysta z możliwości podawania komend w konsoli, to wygoda jaką daje GUI sprawia, że warto zadbać o taki sposób komunikacji z użytkownikiem.

W tym artykule zaimplementowaliśmy podstawową kontrolkę -- SpriteButton, czyli przycisk na którym można wyświetlić obrazek. Wyodrębniliśmy także klasy bazowe dla widżetów oraz GUI tak, aby mogły zostać ponownie użyte w przyszłości -- na przykład do implementacji kolejnych kontrolek.

Teraz nasz edytor zapewnia już efektywny sposób komunikacji z graczem. Z planszy, którą można ukryć mamy możliwość wyboru który element chcemy dodać do planszy. Kiedy uznamy, że nasz poziom jest wystarczająco kompletny, wystarczy naciśnięcie jednego przycisku, aby z trybu edycji przełączyć się do gry i przetestować dzieło. Dzięki możliwości ustalenia pozycji początkowej gracza, możemy szybko rozegrać dowolny kawałek planszy.

Zachęcamy do własnych eksperymentów, realizacji pomysłów zarówno własnych jak i tych zebranych poniżej.

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. Usuwanie elementów w edytorze działa tylko dla pól (czyli głównie dla podłoża). Zaimplementuj usuwanie obiektów.
  2. Przedstawiona implementacja pozwala na ukrywanie GUI. Przerób kod tak, aby możliwe było płynne (animowane) jego pokazywanie/ukrywanie. Sposób animacji należy wybrać z listy: przenikanie (manipulacja przezroczystością), najazd z lewej, najazd z dołu.
  3. Zmodyfikuj kod tak, aby przycisk, nad którym znajduje się kursor powiększał się (czyli kiedy kursor wyjdzie poza obszar przycisku ten powinien powrócić do swojego pierwotnego rozmiaru). Zadanie być może jest nietrywialne - wymaga zastanowienia.
  4. Przerób kod z poprzedniego zadania tak, aby zmiana rozmiaru była płynna.
  5. Wiele użytkowników do przyspieszenia swojej pracy wykorzystuje skróty klawiaturowe. Pod klawisz 'G' podepnij wybranie gumki, a pod 'P' -- pozycji początkowej dla gracza.
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com