Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Menu Systems in Games  (Read 8986 times)

0 Members and 1 Guest are viewing this topic.

Aster

  • Full Member
  • ***
  • Posts: 130
    • View Profile
Menu Systems in Games
« on: August 08, 2013, 07:55:15 am »
Hi all,

I've been getting more and more interested in doing things The Right Way™, and this time, I want to write a good menu/status system for my game. I want something modular, maintainable, and more importantly, I want to write it myself.

Essentially, what I'd want to write is a system like this:
  • Main menu, with: Play, Level editor, Options, Quit.
  • Both play and level editor will be making use of the game's engine, and they'll be dynamic.
  • In both, I'll need a way to summon the "pause" menu, and in the level editor, I'll probably want some keyboard input in a side-menu.
  • Options will do what every functional options menu has always done.
  • Quit will obviously quit.

That should give a simple idea as to what I'd like to do.
Since I've never really done such a thing, and a lot of the people here are better at this than I am, I'm asking for a few tips on how to write such a thing in a clean way, and not diving into a doom of inheritance and possibly explosions.

Keep in mind, I'm not being lazy and asking for ready-made implementations. I'm just asking for the best way to go about a clean and modular menu system. (Buttons and things have pretty obvious implementations, but if there's some extra hax involved for some reason, please do mention it. :) )

Thanks!
Aster

EDIT: Damn, I swear I had posted this in the general discussions thread.. Any chance of it being moved?
« Last Edit: August 08, 2013, 08:09:35 am by Aster »

Semicolon

  • Newbie
  • *
  • Posts: 3
    • View Profile
Re: Menu Systems in Games
« Reply #1 on: August 08, 2013, 10:11:50 am »
There is actually a post on the wiki just for this! I haven't actually read it through but I assume it's up to par.
Here's the link

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
Re: Re: Menu Systems in Games
« Reply #2 on: August 08, 2013, 01:15:02 pm »
There is actually a post on the wiki just for this! I haven't actually read it through but I assume it's up to par.
Here's the link
I can't really advise to use that code (c class prefix, raw pointers, etc), but maybe it helps to understand the concept.

Unfortunately there's no The Right Way regarding screen/state management, mainly because the concept has to be made fit your overall design (keep in mind that state management is essentially part of "the engine").
You can make things as easy as each state creates and returns the next state. Or you can go to very complex systems, where multiple states are kept alive and add different switching possibilities or even use it to manage different in-game states. The possibilities are (nearly) infinit.

After having thought of different complex systems, I personally came to the conclusion that simple system work very nicely and save you a lot of time.
I guess an important role in deciding played the fact, that one should write games and not engines. The engine is what you end up with when the game is done. ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Aster

  • Full Member
  • ***
  • Posts: 130
    • View Profile
Re: Menu Systems in Games
« Reply #3 on: August 09, 2013, 03:32:53 pm »
Alright, thanks for the advice, guys. If I come up with something nice, I'll put it up on the wiki.

magneonx

  • Full Member
  • ***
  • Posts: 141
    • MSN Messenger - magnumneon04@hotmail.com
    • View Profile
Re: Menu Systems in Games
« Reply #4 on: August 10, 2013, 05:41:55 am »
I guess an important role in deciding played the fact, that one should write games and not engines. The engine is what you end up with when the game is done. ;)

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Menu Systems in Games
« Reply #5 on: August 10, 2013, 12:11:53 pm »
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.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Aster

  • Full Member
  • ***
  • Posts: 130
    • View Profile
Re: Menu Systems in Games
« Reply #6 on: August 11, 2013, 05:46:24 pm »
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. :P

I think this is a good way to make everything modular. If I get my implementation working, this could be great. :D

Any apparent flaws in the system? I'd be happy to have them pointed out to me.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
Re: Menu Systems in Games
« Reply #7 on: August 11, 2013, 11:05:35 pm »
I guess there are some mistakes:
class MenuScreen : public State
// ...
Menu menu_state(menu);

Game(StateEngine &parent_, sf::RenderWindow &window_)
// ...
Game game_state(window);

Menu(StateEngine &parent_, sf::RenderWindow &window_)
// ...
Menu menu_state(menu);

Missing implementation of StateEngine.

Anyways as for the code design:
  • In C++ size_t is defined in the std namespace, so you should be using std::size_t instead.
  • Personally I don't feel that loop() is a fitting function name, since it's too generic and doesn't really describe the functionality.
  • What is the reason, that you use a std::hash and map it somehow to a std::vector? Why don't you simply go for std::unordered_map, which basically is a hash table?
  • Are you sure, you want to use shared_ptr rather than unique_ptr? I.e. should your states ever be shared?
  • static_cast<sf::Vector2f>() does the trick as well, if you don't need to change x or y.
  • Wouldn't it be a good idea to integrate the split between update and draw at the state level?
  • I highly suggest to use some kind of indicator in the variable name, to point out what's a member variable and what's a local variable. It will make your code much more readable, especially once you've way more content in there.
  • How is the method of using hashes more flexible than enums? Currently you've to define them in every state...
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Aster

  • Full Member
  • ***
  • Posts: 130
    • View Profile
Re: Menu Systems in Games
« Reply #8 on: August 11, 2013, 11:38:33 pm »
I guess there are some mistakes:
class MenuScreen : public State
// ...
Menu menu_state(menu);

Game(StateEngine &parent_, sf::RenderWindow &window_)
// ...
Game game_state(window);

Menu(StateEngine &parent_, sf::RenderWindow &window_)
// ...
Menu menu_state(menu);

Missing implementation of StateEngine.

Yeah, the point wasn't to copy-paste my code into here, since it's not working. I literally wrote all of that in this forum post editor because I don't want to show an implementation, I want to show how my system would work. It's all theory.

As for the actual mistakes, yeah, those might be a bit confusing.

Quote
Personally I don't feel that loop() is a fitting function name, since it's too generic and doesn't really describe the functionality.

It is, because the state isn't necessarily graphical. It is only what happens in the loop. In the Game class, I could have separated it into multiple functions, which I'm already doing.

Quote
What is the reason, that you use a std::hash and map it somehow to a std::vector? Why don't you simply go for std::unordered_map, which basically is a hash table?
How is the method of using hashes more flexible than enums? Currently you've to define them in every state...

Hashes are good because they're fast. It's more flexible than an enum because I don't need all the states predefined somewhere. I could remove the Game state entirely and it would still compile and run. As for the unordered_map, I'm not sure what you're talking about, as I'm not storing hashes in a std::vector?

Quote
Are you sure, you want to use shared_ptr rather than unique_ptr? I.e. should your states ever be shared?
I highly suggest to use some kind of indicator in the variable name, to point out what's a member variable and what's a local variable. It will make your code much more readable, especially once you've way more content in there.

As I may have badly conveyed, I'm only demonstrating the design of this state system. I might use unique_ptr later, and since the whole thing was written on the fly, I didn't really care for variable naming.

Quote
static_cast<sf::Vector2f>() does the trick as well, if you don't need to change x or y.

I didn't know that worked. Thanks.

Ixrec

  • Hero Member
  • *****
  • Posts: 1241
    • View Profile
    • Email
Re: Menu Systems in Games
« Reply #9 on: August 12, 2013, 01:06:12 am »
Hashes are good because they're fast. It's more flexible than an enum because I don't need all the states predefined somewhere. I could remove the Game state entirely and it would still compile and run. As for the unordered_map, I'm not sure what you're talking about, as I'm not storing hashes in a std::vector?

You are storing the states in a std::vector, however. Since you're using hashes to get to those states, it's basically equivalent to using a std::unordered_map as he said, and you might as well actually use that instead (if your compiler supports C++11 of course).