Search code examples
c++classsfml

Why do my button class items share the same lambda functions?


I am trying to make my own reusable button class in SFML. This would allow me to create a button and add a callback function to it in order to make the creation of buttons much easier.

Here is my hpp file:

#ifndef Button_hpp
#define Button_hpp

#include <stdio.h>
#include <SFML/Graphics.hpp>
#include "View.hpp"
#include "State.hpp"
#include "Window.hpp"
namespace kge {
  class Button: public View{
  private:
    sf::RectangleShape* _buttonOutline;
    sf::RenderWindow* _window;
    sf::Clock _clock;
    std::string _textString;
    sf::Text* _text;
  public:
    Button(Window*, std::string);
    ~Button();
    virtual void update(float td);
    std::function<void(void)> callback;
    void setPosition(float x, float y);
  };
}
#endif /* Button_hpp */

And here is where I generate the buttons:

_restartButton = new kge::Button(_window, "Restart");
_restartButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 300);
_restartButton->callback = ([this](){
  State::instance().currentView = new GameView(this->_window);
  this->_window->setView(State::instance().currentView);
});
_exitButton = new kge::Button(_window, "Quit");
_exitButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 500);
_exitButton->callback = ([this](){
  this->_window->close();
});

Finally, I tell the button to update and do it's checks in my window update, button->update(td)

All my buttons seem to all do the action of the last set callback. In this case, my restart button executes my exit code.

Why is this happening and how would I fix it?

Edit

Here is my generation code:

#ifndef GameOver_hpp
#define GameOver_hpp

#include <stdio.h>
#include "View.hpp"
#include "Button.hpp"
#include "GameView.hpp"
#include "State.hpp"

namespace kge{
  class View;
};

class GameOver: public kge::View{
private:
  sf::Text* _text;
  kge::Button* _restartButton;
  kge::Button* _exitButton;

  void restartFunction(void){

  }
public:
  GameOver(kge::Window* screen) : View(screen){
    int fontSize = 50;
    _text = new sf::Text();
    _text->setFont(kge::AssetManager::mainBundle().getFontNamed("mainfont"));
    _text->setString("Game Over");
    _text->setCharacterSize(fontSize);
    _text->setPosition(getCenterOfScreen().x-((9*fontSize)/2), 100);
    _text->setFillColor(sf::Color(255,0,0));

    _restartButton = new kge::Button(_window, "Restart");
    _restartButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 300);

    _exitButton = new kge::Button(_window, "Quit");
    _exitButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 500);

    _restartButton->callback = ([this](){
//      State::instance().currentView = new GameView(this->_window);
//      this->_window->setView(State::instance().currentView);
      puts("Restart");
    });

    _exitButton->callback = ([this](){
//      this->_window->close();
      puts("Restart");
    });


    this->addItemToView(_text);
    this->addItemToView(_restartButton);
    this->addItemToView(_exitButton);
  }

  void update(float td){
    _restartButton->update(td);
    _exitButton->update(td);

  }

  ~GameOver(){
    delete _text;
    delete _restartButton;
  }
};

#endif /* GameOver_hpp */

Note, kge::View is just a custom sf::Drawable class (how I create my own "views")

Edit 2 Button update function:

  void Button::update(float td){
    if(_clock.getElapsedTime().asMilliseconds() < 400) return;
    if(!sf::Mouse::isButtonPressed(sf::Mouse::Left)) return;
    if(mouseIsIn(*_buttonOutline, _window)){
      callback();
      _clock.restart();
    }
  }

Please note: _clock is an sf::Clock that is stored privately in the button class.


Solution

  • The issue was resolved in chat. To summarize, the mouseIsIn function that @iProgram posted did not check collision correctly, leading to multiple buttons triggering at the same time.

    The original function:

    bool mouseIsIn(sf::RectangleShape shape, sf::RenderWindow* window){
        sf::FloatRect shapeBounds = shape.getGlobalBounds();
        sf::Vector2i mousePos = sf::Mouse::getPosition(*window);
        sf::FloatRect mouseRect = sf::FloatRect(mousePos.x, mousePos.y, mousePos.x+shapeBounds.width, mousePos.y+shapeBounds.height);
        return mouseRect.intersects(shapeBounds);
    }
    

    The fixed function:

    bool mouseIsIn(sf::RectangleShape shape, sf::RenderWindow* window){
        sf::FloatRect shapeBounds = shape.getGlobalBounds();
        sf::Vector2i mousePos = sf::Mouse::getPosition(*window);
        return shapeBounds.contains(mousePos.x, mousePos.y);
    }