|
Gra 2D, część 12: Edytor poziomów cz. 2
15.03.2011 - Marcin Milewski
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ę.
|
|