Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411485 Posts in 69371 Topics- by 58427 Members - Latest Member: shelton786

April 24, 2024, 03:59:50 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallForum IssuesArchived subforums (read only)TutorialsMinimal GUI (in lines of code)
Pages: [1]
Print
Author Topic: Minimal GUI (in lines of code)  (Read 1391 times)
Krux
Level 2
**



View Profile
« on: October 23, 2014, 01:10:59 PM »

Do you know this?: Your current game project needs a GUI, and then you check out for what is available out there and nothing really suits your needs. They are either way to complicated and lack of documentation (TWL), or they are super easy to integrate, but are only meant for debugging purpose and therefore to ugly for the final Product (AntTweakBar). Then you decide to make your own GUI and you want to do it right, so that you can use it for all your later projects (which you probably won't). And then writing the GUI system takes way to much resources from you and in the end you don't have a game, just a GUI. At least I do know projects that ended up like this and I even worked in one of them (it died).

So here is a tutorial on how to write your own GUI in a minimal way, so that your GUI ends up to be only a few lines as it should be. First of all it is important to understand, that less is more. People don't want to spend their time in a GUI, and you don't want to spend time developing the GUI. Good games don't even have a GUI.

This tutorial will cover code snippets that are written in C++ with SDL2. But it should be possible to adapt this to anything that you are using.


So first let's start with an example of what we would like to achieve:

sorry for jpeg quality, couldn't find anything better for now.

This game doesn't have much more in menus than a list of selectable items, and sub menus. Really that's it. Ok there are sliders in the options, but let's concentrate on the real important stuff here.


So let's first talk about our desired behavior of the menu. Menu items can be selected, and in case the items call a another menu it is possible to go back to the previous menu by selecting back.

For this behavior it is easiest to have a stack of menus, and always render the top menu. Like this it is super easy to always be able to go back. So from now on let's call the entire stack the menu, each element in the stack sub menu and each item in the sub menu we call menu item. So even the main menu is a sub menu in this definition.

So our menu is a stack of a list of menu items. In c++ std::vector is suitable for both, the stack and the list.
To make things pretty, we wrap our std::vector in nicely named classes Menu SubMenu and MenuItem.

Code:
class SubMenu {
  protected:
    std::vector<MenuItem> entries;
    [...]

  public:
    SubMenu() : entries(), sizes(), selected(0) {}
    SubMenu(std::vector<MenuItem> entries) : entries(move(entries)), sizes(this->entries.size()), selected(0) {}

    [...]
};

class Menu {
  protected:
    std::vector<SubMenu> menuStack;

  public:
    Menu() {}
    Menu(SubMenu menu) { menuStack.push_back(menu); }

    SubMenu& Current() { return menuStack.back(); }

    bool PopMenu() {
        menuStack.pop_back();
        return menuStack.empty();
    }

    void PushMenu(SubMenu menu) { menuStack.push_back(std::move(menu)); }

    [...]
};

So far we have not talked about the menu items. We could implement every MenuItem with an Effect Method, that does it's effect. But I have an alternative here with quite some advantages. Let's assume you have some base main class with a main loop that knows all your game world and state. We implement each effect of the menu items as methods in this class. Now we can simply implement all our desired effects, because we can simply access the game world or the menus or whatever we want to access. For example Quit sets the variable running on false, so that the main loop ends.

Code:

class GameObject {
  protected:
    Menu menu;
    World world;
    bool running;
    [...]
  public:
    GameObject(...);
    [...]
   
    //menu item actions
    void CallStartMenu();
    void CallIngameMenu();
    void StartGame();
    void SaveGame();
    void LoadGame();
    void ReturnToGame();
    void Quit();
    void ResetGame();
    void Screenshot();

    void MainLoop();
};


This also allows us to access all menu item effects from wherever we want as long as we have a reference to the GameObject. For example if we want to bind a hotkey to SaveGame, we can simply do it, without knowing any object of MenuItem.

now this is all that is left in a menu item:
Code:
typedef void (GameObject::*EffectMethod)();

struct MenuItem {
    const char *Text;  // you can also use std::string here if you prefer that
    EffectMethod EffectMethodPtr;
    MenuItem(const char *Text, EffectMethod EffectMethodPtr)
        : Text(Text), EffectMethodPtr(EffectMethodPtr) {}
};
in case you haven't seen it before, EffectMethod is a typedef for a pointer on a method of the class GameObject with zero arguments returning void. It is like a function pointer, but for methods instead of functions. To call them you always need an instance of GameObject.

Here an example of how menus can be populated:
Code:
void GameObject::CallStartMenu() {
    cout << "StartMenu" << endl;  // some logging
    menu.PushMenu({ { { "Start Game", &GameObject::StartGame },
                      { "Load Game", &GameObject::LoadGame },
                      { "Quit", &GameObject::Quit } } });
    state = GameState::Menu;      // tell the main loop that a menu has to be rendered
}

void GameObject::CallIngameMenu() {
    cout << "IngameMenu" << endl; // some logging
    menu.PushMenu({ { { "Return To Game", &GameObject::ReturnToGame },
                      { "Save Game", &GameObject::SaveGame },
                      { "Load Game", &GameObject::LoadGame },
                      { "Reset Game", &GameObject::ResetGame },
                      { "Quit", &GameObject::Quit } } });
    state = GameState::Menu; // tell the main loop that a menu has to be rendered
}

In order to make the menu controlable by keyboard, we also need to store one integer per sub menu to inticate which item is selected. We can't do that in the Menu (MenuStack) because when we want to pop the top sub menu we want to have a valid index afterwards that selects the item that called the popped sub menu. But this task is left you you to implement.

for rendering it is best to render each string in a texture, and then calculate a centralized position for all of them. Here it becomes hard to not disturb the Model View Controller. I gave the MenuItem an SDL_Texture* that go initialized whe first rendered, and a SDL_Rect for the position and size. If you insist on not having any rendering code in your MenuItem you can make a map<const char*, SDL_Texture*> in your renderer, which will work great if you don't plan to render tons of text in your game.

for the mouse suppert you can use the SDL_Rects that got initialized by the renderer to check weather the mouse hits the menu item.


So far, that's all. Happy coding and integrating in your project. This is how it looks in my game:

pretty bare bone, but it is meant to be minimal.

















Logged
Johnman
Level 0
**



View Profile
« Reply #1 on: October 26, 2014, 04:07:20 AM »

Nice and minimal. I like it.
Logged
Krux
Level 2
**



View Profile
« Reply #2 on: October 26, 2014, 12:00:06 PM »

thank you for your feedback. So in case you want to adapt this to your game, don't hesitate to ask questions.
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic