1
\$\begingroup\$

As the title states I am creating a game with SFML being the only library used.

I am entirely unsure how to go about this.

I want the buttons to be activated by simple mouse clicks (when the mouse is within the defined area of the button).

My question however is how do I do this with regards to a standard Game Loop, with the following methods HandleInput(), Update() and Draw().

  • Should I implement it so that HandleInput() calls a function (on left mouse click) which stores the mouse coordinates and in Update() every button checks if it has been clicked on?

  • Or should I (attempt to) implement Observer pattern?

  • Or is there a method I haven't even covered?

I hope this question is relevant to GameDev I believe it is.

\$\endgroup\$
2
\$\begingroup\$

Modern GUI libraries often use some sort of callbacks (classical Observer pattern, or function objects in languages that support them).

From the way you ask your question, I assume you want to keep HandleInput() and Update() strictly separate, where the latter executes the logic associated with the button. You can achieve this by determining and storing which buttons were clicked in HandleInput(), and invoke the corresponding callbacks later in Update(). A map allows you to associate buttons (more precisely, their unique identifiers, e.g. an int or the text written on them) with callbacks.

In C++, an easy way is to use std::function. Your class could look as follows:

std::vector<int>                     pressedButtons;
std::map<int, std::function<void()>> callbackMap;

void HandleInput()
{
   sf::Event event;
   while (window.pollEvent(event))
   {
      if (event.type == sf::Event::MouseButtonPressed)
      {
         for (Button& b : buttons)
         {
            if (IsOnButton(b, event.mouseButton))
                pressedButtons.push_back(button.GetId());
         }
      }
   }
}

void Update()
{
   for (int id : pressedButtons)
   {
      auto itr = callbackMap.find(id);
      if (itr != callbackMap.end())
         (*itr)();
   }
   pressedButtons.clear();
}

void RegisterCallback(const Button& b, std::function<void()> f)
{
   callbackMap[b.GetId()] = std::move(f);
}

You may ask why I store a container and not a single button, as users are very unlikely to click more than one button per frame. That is partially true, but not for special cases like overlapping buttons. Furthermore, this approach can easily be applied to other scenarios, where a container makes sense.

Since you're using SFML, there are existing solutions on the Wiki or in the Thor library that will help you with this design. For example, Thor allows you to define "actions" that map arbitrary combinations of SFML events to your callbacks.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.