How do you do event handling in/with SDL2?

Hi, I’m making a game and I’m stumped when it comes to event handling I believe having SDL_PollEvent paired with a switch (which is what I do at the moment) or if statement is a basic form of event handling, however, I probably don’t want all my logic in one spot especially if I have multiple things interested in a particular event that’ll probably get messy so I probably want something more decoupled and modular(?). I know there are probably a plethora of solutions to this, but what should my next step be or what would be a typical solution observer pattern? callbacks? Trying to get a better idea of things I could do.

Probably not useful, but here’s the code I have so far.

while (running) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            imguiCtx.processEvent(event);
            rmluiCtx.processEvent(event);
            switch (event.type) {
            case SDL_QUIT:
                spdlog::info("SDL_QUIT");
                running = false;
                break;
            case SDL_WINDOWEVENT:
                switch (event.window.event) {
                case SDL_WINDOWEVENT_RESIZED:
                    spdlog::info("SDL_WINDOWEVENT_RESIZED");
                    break;
                case SDL_WINDOWEVENT_CLOSE:
                    spdlog::info("SDL_WINDOWEVENT_CLOSE");
                    running = false;
                    break;
                }
                break;
            case SDL_KEYDOWN:
                spdlog::info("SDL_KEYDOWN");
                break;
            case SDL_KEYUP:
                spdlog::info("SDL_KEYUP");
                break;
            }
        }

As you mentioned, there are many possibilities! This may be totally different from what’ll work best for you, but I can describe the process I’m using to handle events in my project:

  1. define a set of user-accessible functions. They are highly abstract and all share the basic prototype void function(void). they read directly from and modify a global program state.
  2. define ‘input modes’ that group those functions and map particular inputs keyboard inputs to particular functions.
  3. track the state of key modifiers (ctrl, alt, shift, etc) in a bitfield
  4. store information about user actions (struct containing mode, keybinding, function) in a hash table
  5. upon SDL_KEYDOWN, hash the keycode and modifier state to get the appropriate entry in the hash table; if collisions or if a binding has different meanings in different modes, follow the linked list until you get the right input for the current mode
  6. do the function stored there

This only really applies for keyboard inputs, so my SDL_KEYDOWN case is nice and concise, but I still have logic for things like mouse motion inside of the switch statement.

I find things can easily get messy when state changes necessarily occur over multiple frames, like animations. I generally store information about in-process changes in the state and try to keep a limited set of high-level functions that run every frame to push those changes along when needed based on the current state.

The path you choose depends entirely on the specifics of your application, but I suppose it’s always worth thinking about:

A. All of the things the user can do to interact with the program (e.g. press a key)
B. All the abstract ways the program state can be altered by the user (e.g. ‘move character forward’)
C. What does the map between A and B look like, and what kind of data structure would lend itself most elegantly to that map?

I dunno if any of this is helpful to you, but hope it provides some food for thought at the very least!

(C++ OOP)

I have a hierarchy of objects starting with a pointer to a “Scene” which you might call “Board” or “Level” that has a handleEvent function that updates the states of all of the Scene’s child (component) objects. Some of those more complex child object will also contain this function:

bool handleEvent(SDL_Event * ev);
[ I keep pointers to these objects in a vector/array in the Scene class to make sure I’m not trying to call the handleEvent() function on everything in the Scene. ]

The Scene pointer can be swapped out for the next level/system as needed, and each Scene object has a “request” string that indicates which scene it thinks it should be replaced with. All my scene pointers are stored in an std::unordered_map <std::string, Scene *> sceneMap, which makes this swap easier.

You have the option of returning true or false from the Scene->handleEvent() function.
If Scene->handleEvent(&ev) returns false, then the event is considered “not consumed”.
If the event was not consumed, then in the main event loop I will run the switch/case as you have shown above where I process big events like closing the window, setting certain global machine states like mouse positions, and pausing/resuming the game in this space.

C and Function Pointers

User Kismet recently posted a link to their project, where they developed a framework they named Easy_SDL and a tetris clone. To make things more modular, their event handler is a function pointer (Callback), which lets them swap between the menu-system handler, the game-system handler, and any other type of system handler as needed.

void (*handle_event)(SDL_Event *) = NULL;

void gameHandler(SDL_Event * event)
{
    // game logic switch-case
}

void menuHandler(SDL_Event* event)
{
    // switch-case to handle the event
    if(SelectedOption == 2)
    handle_event = &gameHandler;
}

int main()
{
    handle_event = &menuHandler;

//   setup, etc.
    while(runGame)
    {
        SDL_Event ev;
        while(SDL_PollEvent(&ev))
        {
            handle_event(&ev);
            if(ev.type == SDL_QUIT:
            {
                runGame = false;
            }
        }
    //draw screen
    }
    //exit routines
}

I could imagine a struct that holds both a pointer to an event handler and an update function pointer to help keep the system’s logic paired correctly.