How to elegantly handle Entities Action and SDL_Events in a game loop

I’m slightly confused in how to “correctly” convert user input in entity (aka. player character) actions.

Right now I have something like (pseudo code below):

void game_handle_input(struct game *game) {
    while( SDL_PollEvent( game->eventpool ))
    { 
       // ...
        switch (e.type)
        {
        case SDL_KEYDOWN:
            switch(e.key.keysym.sym)
            {
                   case SDLK_UP:  player_move_up(player); break;
                    //...
            }
        //...
    }
}

The problem that the update action is called inside the event loop. It’s important to say that not every key would act in game loop. For example I could map ALT+x to close the game, or ALT+ENTER to toggle fullscreen. ESC to call a menu.

In my mind, I would like to, instead call the action, trigger a delayed event that will be handled in a proper place for example in game_update() call it will look for all game related queued events and apply them in a correct order, or, a ui_update() call will get all ui related events and execute them (like closing a window or changing a tab focus).

SDL has any data structure to handle this? Do I have to write down my own? Which tips the veterans has to say? Any good readings about doing that ? (pure C coding).

I’m using a struct that contain input flags (up, left, fire, quit…). Procedure is simple as well:

  1. clear flags
  2. read user input and set flags
  3. run game logic
  4. render

I’m not really queuing input data because there hasn’t been a need for that so far.

1 Like

I can’t answer all of your questions but I recently found a way to think about some of these things. When I build a game, I like to think in terms of “level”. I use C but it could be modified to be compatible with other langages / ways of expressing things. At the bottom level, I interact with the operating system (windows creation/destruction/manipulation, gathering input, initialize/shudown SDL modules, mesure time…). I put all of this, the “os level” stuff, in a “main” file. In another file, I put the game stuff. Game doesn’t have to know about operating system, windows or input gathering. In this “game” file/module I put gameplay related globals and a few functions like game_init, game_quit, game_update_and_render. These functions are called by the lower level, the “os level”. This is where it could be useful to you.

The os level handles event stuff like closing windows, going in or out of “full screen desktop” directly. It does not communicate at all with the game about this. The only info he notifies the game about in the event loop is the state of the keyboard/joypad. For the keyboard, I have two tables, a current_keys table and a previous_keys one. They represent the state of the keyboard during this frame and during the previous one. Before I enter the event loop, I copy the current_keys table content into the previous_keys one. Then If any keyboard event occur, I fill out the current_keys table. The game can manipulate these tables indirectly by calling 3 functions, keyboard_key_down(int key) , keyboard_key_released(int key) and keyboard_key_pressed(int key).

This way in gameplay code I can write things like this in the game_update_and_render function :
if(key_pressed(key_space)) fire_bullet(cannon.x, cannon.y);

1 Like

Here is how I handle the events in Wizard apprentice Lya (pure C and SDL2)

Events fall in 2 categories:

  • events that result in an immediate action (quit, toggle full-screen, …)
  • events that can be combined with other events (player movement, …)

I execute the immediate actions while processing the event queue.
I set flags for the events that can be combined and use the flags later.

int Right=0,Left=0,Down=0,Up=0;//held key flags

while(1){
    LastUpdate=SDL_GetTicks();
    while(SDL_PollEvent(&gEvent)){
      switch(gEvent.type){
        case SDL_QUIT:return 0;
        case SDL_KEYDOWN:
          if(gEvent.key.repeat==0){
            if(gEvent.key.keysym.sym==gKeycodeRight)Right=1;
            else if(gEvent.key.keysym.sym==gKeycodeLeft)Left=1;
            else if(gEvent.key.keysym.sym==gKeycodeDown)Down=1;
            else if(gEvent.key.keysym.sym==gKeycodeUp)Up=1;
            else if(gEvent.key.keysym.sym==gKeycodeToggleFullscreen)toggleFullscreen();
          }
          break;
        case SDL_KEYUP:
          if(gEvent.key.repeat==0){
            if(gEvent.key.keysym.sym==gKeycodeRight)Right=0;
            else if(gEvent.key.keysym.sym==gKeycodeLeft)Left=0;
            else if(gEvent.key.keysym.sym==gKeycodeDown)Down=0;
            else if(gEvent.key.keysym.sym==gKeycodeUp)Up=0;
          }
          break;
      }
    }
    
    gPlayerInputX=Right-Left;gPlayerInputY=Down-Up;
    //The "move player" function takes in gPlayerInputX and gPlayerInputY but not Right, Left, Down or Up.
}

Notes:
If there is a SDL_QUIT event I simply quit and don’t process the remaining events.
I use cascaded ifs instead of switch-case because gKeycodeXXX refer to player-configurable keys.
I only treat non-repeating key press / release thanks to if(gEvent.key.repeat==0) so toggleFullscreen() will never run twice in a single frame.