Talking about OOD, GUI hierarchies are often organized using the Composite pattern.
In order to connect button clicks with events like switching menus, colorizing GUI elements or similar, you could have a look at the Observer or Command pattern. One possibility consists of using abstract base classes, where derived classes implement the concrete action. C++11 offers an interesting alternative: std::function allows you to link arbitrary actions (even in a single line, using lambda expressions).
Concerning states, there are -- as mentioned by eXpl0it3r -- different possibilities. If two states are mutually exclusive, it will be enough to simply store the current state; otherwise you may think about an advanced architecture such as a stack. An easy way to refer to different states are enums, which can be mapped to the corresponding state instances.
I'm not going to separate the quote into different bits because I don't know where to split it, sorry.
std::function is indeed quite nice, and I've used it a few times, but it's not enough for what I want to do. Since I wanted something modular, having an enum for storing states wasn't much of a viable option, since I'd have to change it every time I add a state.
I came up with a base
State class, like this one:
class State
{
public:
StateEngine &parent;
const size_t name_hash;
virtual void activate() = 0;
virtual void deactivate() = 0;
virtual void loop() = 0;
State(StateEngine &parent_):parent(parent_){}
};
The
StateEngine class looks like this:
class StateEngine
{
private:
friend class State;
std::hash<std::string> hash_function;
std::vector<std::shared_ptr<State>> states;
size_t current_state_hash;
State &get_state(size_t &state_hash);
public:
void set_current_state(size_t state_hash);
State &get_current_state();
State &add_state(State &new_state);
State &operator[](size_t state_hash);
};
Example usage would be like this:
class Game : public State
{
private:
sf::RenderWindow &window;
sf::RectangleShape player;
size_t menu_hash;
public:
void activate()
{
player.setPosition({0.f,0.f});
player.setSize({30.f,30.f});
player.setFillColor(sf::Color::Red);
active = true;
}
void deactivate()
{
// We could do something like clear enemy vectors here..
active = false;
}
void loop()
{
sf::Event event;
while(window.pollEvent(event))
{
if(event.type == sf::Event::Closed)
window.close();
else if(event.type == sf::KeyEvent && event.key.code == sf::Keyboard::Escape)
parent.set_current_state(menu_hash);
}
player.setPosition({
static_cast<float>(sf::Mouse::getPosition(window).x),
static_cast<float>(sf::Mouse::getPosition(window).y)
});
window.clear();
window.draw(player);
window.display();
}
Game(StateEngine &parent_, sf::RenderWindow &window_) : State(parent_) window(window_)
{
name_hash = parent.hash_function("game");
menu_hash = parent.hash_function("menu");
}
};
class MenuScreen : public State
{
private:
sf::Text text;
sf::Font font;
sf::RenderWindow &window;
size_t game_hash;
public:
void activate()
{
font.loadFromFile("some_font.ttf");
text.setString("lol");
text.setCharacterSize(42);
text.setPosition({100.f,100.f});
active = true;
}
void deactivate()
{
active = false;
}
void loop()
{
sf::Event event;
while(window.pollEvent(event))
{
if(event.type == sf::Event::MousePressed)
if(text.getGlobalBounds().contains(sf::Mouse::getPosition(window))
parent.set_current_state(game_hash);
else if(event.type == sf::Event::Closed)
window.close();
}
}
Menu(StateEngine &parent_, sf::RenderWindow &window_) : State(parent_), window(window_)
{
game_hash = parent.hash_function("game");
name_hash = parent.hash_function("menu");
}
};
int main()
{
sf::RenderWindow window{{800,600}, "Test"};
StateEngine state_engine;
Game game_state(window);
Menu menu_state(menu);
while(window.isOpen())
{
state_engine.get_current_state().loop();
}
return 0;
}
I know the code is long, sorry. This code differs from my actual implementation, which isn't currently working.
I think this is a good way to make everything modular. If I get my implementation working, this could be great.
Any apparent flaws in the system? I'd be happy to have them pointed out to me.