5
\$\begingroup\$

I'm working on an entity component based game and I ran into this problem. I've got a component called PlayerControlComponent:

void PlayerControlComponent::HandleInput() {
    SDL_Event event;
    while(SDL_PollEvent(&event) != 0) {
        if(event.key.keysym.sym == SDLK_RIGHT) {
            Entity->MoveRight();
        }
        else if(event.key.keysym.sym == SDLK_RIGHT) {
            Entity->MoveLeft();
        }
        else {
            Entity->Stay();
        }
    }
}

and I've got a GameManager class that handles the input for the window, like if you hit escape it will quit:

void GameManager::HandleInput() {
    SDL_Event event;
    while(SDL_PollEvent(&event) != 0) {
        switch(event.key.keysym.sym) {
        case SDL_QUIT:
        case SDLK_ESCAPE:
            Quit();
        }
    }
}

After I put everything in my main function, it looks like this:

while(GameManager::GetInstance().IsRunning()) {
        //Inputs
        GameManager::GetInstance().HandleInput();
        player->GetComponent<PlayerControlComponent>()->HandleInput();
        //Update, Draw, etc..

My problem is that wich ever I put first gets executed only. So in this example the player won't handle inputs. If change it up and put the player first like so:

while(GameManager::GetInstance().IsRunning()) {
            //Inputs
            player->GetComponent<PlayerControlComponent>()->HandleInput();
            GameManager::GetInstance().HandleInput();
            //Update, Draw, etc..

..my player will move but than the window won't quit if I hit escape. For some reason wich ever input handling function is called first, gets executed. I figured out it has something to do with SDL_PollEvent(). So I created a global SDL_Event variable and changed up the code a bit so there is only 1 SDL_PollEvent() call wich happens in main:

while(GameManager::GetInstance().IsRunning()) {
        //Inputs
        while(SDL_PollEvent(&event) != 0) {
            GameManager::GetInstance().HandleInput();
            player->GetComponent<PlayerControlComponent>()->HandleInput();
        }
        //Update, Drawing, etc..

So as I said, the event variable is now global and both GameManager and Player uses the (only) same event variable.b Now everything works fine, but is that really the only option? (If so, than I don't know why would they allow us to declare multiple SDL_Events.) So my question is:

Is there a way around this, can I handle multiple input events?

\$\endgroup\$
1
  • \$\begingroup\$ you can pass event as parameter to the HandleInput functions. \$\endgroup\$
    – tp1
    Commented Dec 15, 2014 at 21:37

4 Answers 4

8
\$\begingroup\$

When you run your while loop in each of your HandleInput functions, you're burning through the entire event queue up to that moment in time. So yes, whichever event handler you call first will eat all of the events.

There are a few ways to handle this problem, but obviously, paramount to every possible solution, is the removal of your destructive SDL_PollEvent while loops every time you want to handle input. The solution most similar to your current implementation would be of this nature:

std::vector<SDL_Event>& GetFrameEvents()
{
    static std::vector<SDL_Event> frame_events;
    return frame_events;
}

// main loop
while(running)
{
    SDL_Event event;
    while(SDL_PollEvent(&event) != 0)
    {
        GetFrameEvents().push_back(event);
    }

    // other main loop stuff...

    GetFrameEvents().clear();
}

Just loop through frame_events, which you can retrieve via the global function GetFrameEvents, in your HandleInput functions instead of your while loops on SDL_PollEvent.

\$\endgroup\$
4
  • \$\begingroup\$ Thank you! The one thing I don't understand is why do I need a vector of events? Why not make a function that returns a single event? I really have to find a PROPER tutorial on SDL_Events.. \$\endgroup\$ Commented Dec 15, 2014 at 22:17
  • \$\begingroup\$ Also, if I understood this correctly, there is no way of doing it, without a use of a global function or variable right? Same goes with the renderer.. \$\endgroup\$ Commented Dec 15, 2014 at 22:46
  • 1
    \$\begingroup\$ Once you poll the event, it's removed from the SDL event queue, so you have to persist it in some way if you want multiple receivers. It's a vector because you have to persist every event of the frame, unless you only want to handle one event per frame...in which case you wouldn't run the while loop on SDL_PollEvent. \$\endgroup\$
    – Nox
    Commented Dec 15, 2014 at 22:48
  • 1
    \$\begingroup\$ R.E. globals, you can do it without globals. You can store the frame_events vector wherever you want, wrapped in an object or what not, then just pass it around wherever you need to respond to events. \$\endgroup\$
    – Nox
    Commented Dec 15, 2014 at 22:51
10
\$\begingroup\$

Another (albeit a little more complicated) thing you could do is have your components register for specific event types:

class EventHandler {
public:
  using EventCallback = std::function<void(SDL_Event const&)>;

  void register(SDL_EventType type, EventCallback callback) {
    _registeredCallbacks[type].push_back(callback);
  }

  void handleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event) != 0) {
      for (auto& cb : _registeredCallbacks[event.type]) {
        cb(event);
      }
    }
  }

private:
  std::map<SDL_EventType, std::vector<EventCallback>> _registeredCallbacks;
};

class GameManager {
public:

  void registerWithEventHandler(EventHandler& handler) {
    handler.register(SDL_KEYUP, [this](SDL_Event const& event){
      if (event.key.keysym.sym == SDLK_ESCAPE) {
        Quit();
      }
    });
    handler.register(SDL_QUIT, [this](SDL_Event const& event){
      Quit();
    });
  }

}

And then call handleEvents() once per game loop execution. This example uses some C++11-specific features, but you can basically do the same with '03 if you use (member) function pointers or something similar. And with a little more work, you could also register callbacks for single key presses.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ To anyone trying to use this verbatim don't use register as a method name like above: "register" is a reserved keyword in C++. If you do you'll get all sorts of strange compiler errors which probably won't make a ton of sense. \$\endgroup\$
    – bpw1621
    Commented Jul 22, 2017 at 23:54
  • 1
    \$\begingroup\$ This code works in SDL1, for SDL2 you need to change _registeredCallbacks[event.type] to _registeredCallbacks[static_cast<SDL_EventType>(event.type)] \$\endgroup\$
    – Pavel
    Commented Jul 8, 2020 at 4:12
1
\$\begingroup\$

You are polling all the events the first time you handle them and not using all cases at that time. That will leave some untouched. Instead of having multiple event polls, have it all processed in one function. (Preferably in your main class or somewhere able to access all things needed to be updated by events)

\$\endgroup\$
-1
\$\begingroup\$

To handle multiple events, you need to cache them, so they can be dealt with once per frame.

You need an abstraction of a controller, such as a keyboard:

class Keyboard
{
private:
    bool pressedBuffer[256];
    bool heldBuffer[256];
public:
    void handleEvent(SDL_Event const& e)
    {
        if (e.type == SDL_KEYDOWN)
        {
            m_pressedBuffer[event.key.keysym.sym] = true;
        }
        else
        {
            m_pressedBuffer[event.key.keysym.sym] = false;
        }
    }
    KeyState getKeyState(Key key)
    {
        if (m_pressedBuffer[(int)key] && !m_heldBuffer[(int)key])
            return KeyState::Pressed;
        if (m_pressedBuffer[key] && m_heldBuffer[key])
            return KeyState::Held;
        if (!m_pressedBuffer[key] && m_heldBuffer[key])
            return KeyState::Released;
        return KeyState::None;
    }
    void update()
    {
        for (int i = 0; i < 256; ++i)
        {
            if (m_pressedBuffer[i])
                m_heldBuffer[i] = true;
            else m_heldBuffer[i] = false;
        }
    }

};

Then, in your basic loop:

while( SDL_PollEvent( &event ) )
{
    m_controller.handleEvent(event);
}

And in your update cycle:

App::update(float delta)
{
    if (m_controller.getKeyState(Key::A) == KeyState::Pressed
    && ... //repeat for as many keys as you want)
    {
         //Do something
    }
    m_controller.update();
}

The basic idea, is to parse events, and store them as the state of the controller.

Then the engine/game can query the state, and make changes to the game state. In this way, you can handle as many different combinations of keypresses, mouse motion etc as you want.

I prefer this approach, as opposed to function callbacks, as it lends itself very well to scripting. But this is just my own personal opinion.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .