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

15.03.2011 - Marcin Milewski
TrudnośćTrudność

Ostatnim elementem, który należy omówić, jest klasa EditorGui. Reprezentuje ona konkretne GUI, więc dziedziczy po klasie Gui. Jej zadaniem jest przechowywanie kontrolek oraz reagowanie na komunikaty od użytkownika wydawane za pomocą myszy -- zmiana położenia oraz naciśnięcie lewego przycisku. Ta klasa wie także, który pędzel jest aktywny, czyli który przycisk na interfejsie został kliknięty jako ostatni. Poniżej znajduje się deklaracja klasy EditorGui:

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
#ifndef __EDITORGUI_H_INCLUDED__
#define __EDITORGUI_H_INCLUDED__
 
#include "../gui/Gui.h"
#include "BrushButton.h"
 
class EditorGui;
typedef boost::shared_ptr<EditorGui> EditorGuiPtr;
 
class EditorGui : public Gui, public boost::enable_shared_from_this<EditorGui> {
public:
    explicit EditorGui();
    void Start();
    void Init();
    void Draw();
    void Update(double dt);
 
    bool OnMouseMove(double x, double y);
    bool OnMouseDown(Uint8 button, double x, double y);
 
    BrushPtr GetActiveBrush() const {
        return m_active_brush;
    }
 
private:
    typedef std::vector<BrushButtonPtr> BrushButtonContrainer;
    BrushButtonContrainer m_buttons;
    BrushButtonPtr m_hovered_button;
    BrushPtr m_active_brush;
};
 
#endif
  

Hierarchia dziedziczenia dla graficznego interfejsu użytkownika edytora.

Definicja tej klasy zaczyna się od dodania odpowiednich kontrolek do edytora. Konkretnym miejscem, w którym będą przechowywane jest prywatny wektor m_buttons. Dla skrócenia zapisu, do tworzenia kontrolek wykorzystujemy makra. Jeżeli ktoś jest ich przeciwnikiem, to może użyć w tym miejscu funkcji. Nie ma to dużego znaczenia, a w razie potrzeby kod można zawsze szybko przystosować do nowych warunków. Oto ciała pierwszych funkcji:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Plik: editor/EditorGui.cpp
#include "../StdAfx.h"
 
#include "../Engine.h"
#include "EditorGui.h"
 
EditorGui::EditorGui() {
}
 
void EditorGui::Start() {
}
 
void EditorGui::Init() {
    m_buttons.clear();
    const Size default_size = Size(.1, .1);
#define BUTTON(name,pos,size,type) \
    BrushButtonPtr(new BrushButton( Sprite::GetByName(name), pos, size, \
                                    Brush::New(Sprite::GetByName(name), type)))
#define ADD_BUTTON(name,pos,size,type) \
    m_buttons.push_back(BUTTON(name,pos,size,type))
 
    // nazwy sprite'ów zdefiniowane są w SpriteConfig::SpriteConfig
    ADD_BUTTON("gui_eraser",
        Position( 0, .0), default_size*2, Brush::ST::Eraser);
    // rozwinięcie powyższego makra (ponad 2 razy dłuższe):
    // m_buttons.push_back(BrushButtonPtr(new BrushButton(
    //     Sprite::GetByName("gui_eraser"), Position( 0, .0), default_size*2,
    //     Brush::New(Sprite::GetByName("gui_eraser"), Brush::ST::Eraser))));
 
    ADD_BUTTON("player_stop",
        Position(.1, .8), default_size, Brush::ST::Player);
    ADD_BUTTON("EndOfLevel",
        Position(.2, .8), default_size, FT::EndOfLevel);
    ADD_BUTTON("twinshot_upgrade",
        Position(.4, .8), default_size, ET::TwinShot);
    ADD_BUTTON("mush_stop",
        Position(.8, .8), default_size, ET::Mush);
 
    ADD_BUTTON("PlatformTopLeft",
        Position(.3, .5),  default_size, FT::PlatformTopLeft);
    ADD_BUTTON("PlatformTop",          
        Position(.4, .5),  default_size, FT::PlatformTop);
    ADD_BUTTON("PlatformTopRight",     
        Position(.5, .5),  default_size, FT::PlatformTopRight);
    ADD_BUTTON("PlatformLeft",         
        Position(.3, .4),  default_size, FT::PlatformLeft); 
    ADD_BUTTON("PlatformMid",          
        Position(.4, .4),  default_size, FT::PlatformMid);
    ADD_BUTTON("PlatformRight",        
        Position(.5, .4),  default_size, FT::PlatformRight);
    ADD_BUTTON("PlatformLeftRight",    
        Position(.4, .15), default_size, FT::PlatformLeftRight);
    ADD_BUTTON("PlatformLeftTopRight", 
        Position(.4, .25), default_size, FT::PlatformLeftTopRight);
 
#undef ADD_BUTTON
#undef BUTTON
}
  

Zauważmy, że pojawił się tutaj element gui_eraser. Będzie on odpowiadał rodzajowi pędzla przeznaczonego do usuwania elementów z poziomu. Odpowiadający mu sprite dodajemy do naszego atlasu, a w pliku SpriteConfig.cpp podajemy jego położenie na obrazku:

1
2
3
4
    // Plik: SpriteConfig.cpp; konstruktor klasy SpriteConfig
    Insert("gui_eraser",     SpriteConfigData(DL::Entity, 1, 1,  10*32, 12*32,
                                              32, 32, true, false));
  

Mając zdefiniowane widżety możemy je rysować oraz aktualizować. Całe GUI będzie rysowane na nieco ciemniejszym tle, dlatego metoda EditorGui::Draw zaczyna się od wywołania funkcji DrawQuad klasy Renderer. Następnie rysujemy wszystkie przyciski poza aktywnym (tym pod kursorem myszy), który rysujemy osobno dodając pod nim podświetlenie. Do aktualizacji wykorzystujemy algorytm z biblioteki standardowej -- std::for_each. Oto kod obu metod:

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 EditorGui::Draw() {
    // ciemne tło
    Engine::Get().GetRenderer()->DrawQuad(0, 0, 1, 1, 0,0,0,.7);
 
    // widoczne kontrolki
    for (BrushButtonContrainer::const_iterator i=m_buttons.begin();
         i!=m_buttons.end(); ++i) {
        if ((*i)->IsVisible() && (*i)!=m_hovered_button) {
            (*i)->Draw();
        }
    }
    
    // przycisk, nad którym jest kursor, rysujemy osobno.
    if (m_hovered_button) {
        m_hovered_button->Draw();
        const Aabb& box = m_hovered_button->GetAabb();
        Engine::Get().GetRenderer()->DrawQuad(box.GetMinX(), box.GetMinY(),
                                              box.GetMaxX(), box.GetMaxY(),
                                              1,1,1, .4);
    }
}
 
void EditorGui::Update(double dt) {
    std::for_each(m_buttons.begin(), m_buttons.end(),
                  boost::bind(&GuiWidget::Update, _1, dt));
}
  

Zostały jeszcze dwie metody -- OnMouseMove oraz OnMouseDown. Pierwsza obsługuje zmianę położenie kursora na ekranie. Ponieważ w naszym GUI jest mało kontrolek, to możemy sobie pozwolić na liniowe przejrzenie wszystkich, aby znaleźć tę, nad którą znajduje się wskaźnik.

Druga z poniższych metod odpowiada za reakcję na naciśnięcie przycisku myszy. Jest ona bardzo prosta, gdyż jedynie sprawdza czy użytkownik wskazuje kursorem na którąś z kontrolek. Jeżeli tak jest, to zmienia aktywny pędzel na właściwy dla wskazywanego widżetu. Poniżej kod obu metod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool EditorGui::OnMouseMove(double x, double y) {
    m_hovered_button.reset();
    const Aabb cursor_aabb = Aabb(x, y, x+.02, y+0.02);
    for (BrushButtonContrainer::const_iterator i=m_buttons.begin();
         i!=m_buttons.end(); ++i) {
        if ((*i)->IsVisible() && (*i)->GetAabb().Collides(cursor_aabb)) {
            m_hovered_button = *i;
        }
    }
    return bool(m_hovered_button);
}
 
bool EditorGui::OnMouseDown(Uint8 /* button */, double /* x */, double /* y */){
    if (!m_hovered_button) {
        return false;
    }
    m_active_brush = m_hovered_button->GetBrush();
    return true;
}
  

Przy okazji definiowania kontrolki BrushButton wykorzystywaliśmy klasę Brush, która była przez nią opakowywana. Do tej pory znaliśmy jednak jedynie zalążek tej klasy. Za chwilę poznamy całą jej implementację.

0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com