SDL_GameController events mirrored in SDL_Joysticks

I notice while using SDL_GameController, that events as SDL_CONTROLLERBUTTONDOWN are also mirrored as SDL_Joystick events, in this case SDL_JOYBUTTONDOWN.

To avoid that I created some guards inside Joystick events. As:

case SDL_JOYDEVICEREMOVED:
    /*
    * This guard is necessary for two reasons:
    * 1. If a joystick event was received but we will handle
    *    as a GameController;
    * 2. If it was ALREADY handled as game controller, hence
    *    we will not be able to fetch the controller or the
    *    joystick id
    */
    joystick = SDL_JoystickFromInstanceID(event.jdevice.which);
    if (SDL_GameControllerFromInstanceID(event.jdevice.which) || !joystick)
    {
        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "JOYDEVREM: Guard triggered\n");
        break;
    }

or

 case SDL_JOYBUTTONDOWN:
 case SDL_JOYBUTTONUP:
    /* This device is a Game Controller we will handle elsewhere */
    if (SDL_GameControllerFromInstanceID(event.jdevice.which))
    {
        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "JOYDEVBUT: Guard triggered\n");
        break;
    }

Is this the recommended approach? If not, how to correctly handle this?

Full source code for this example can be seen here: gp.c (7.5 KB)

First of all, your example code is wrong:
For SDL_JOYBUTTONDOWN/...UP events, you can’t use event.jdevice.* but have to use event.jbutton.* (in this case event.jbutton.which).
Same for all other kinds of event: every SDL_EventType has an associated SDL_*Event struct in the SDL_Event union, and you can only use the corresponding member of SDL_Event to access the contents of events (exception: you can always read event.type, of course).
See https://wiki.libsdl.org/SDL_Event#table (“Relationships between event types and union members”) for which SDL_Event member belongs to which event-type.

To answer your question:

The simplest way is to only support Joystick or Controller input, but not both at the same time (and ignore the other kind of event).

Slightly more work: Somehow select one Joystick or Controller to be used for input (+ a flag whether it’s Joystick or Controller) and ignore all Joystick/Controller events from other devices (and if it’s a controller, ignore joystick events from that device).

The proper way: Associate actions not just with “joystick button one” or “joystick axis one”, but with “joystick with ID 42, button one”, i.e. only execute your action (like “jump”) if it is:

  1. coming from the right joystick device
  2. the right kind of device (if it’s a controller, ignore joystick events)
  3. is the right button/axis/… (this part you probably already do)

(In contrast to the “slightly more work” option this allows players to use multiple joystick/controller devices at the same time, which makes sense for some games)

Orthogonal to all this, you could decide not to use controller-events at all, but treat everything as a joystick.
To still make it look like “proper” Controller support to your users, with the familiar button names (and maybe default bindings), use https://wiki.libsdl.org/SDL_GameControllerGetBindForAxis and https://wiki.libsdl.org/SDL_GameControllerGetBindForButton which will tell you which logical controller button/axis/… (like SDL_CONTROLLER_AXIS_LEFTX or SDL_CONTROLLER_BUTTON_B) belongs to which Joystick axis/button/… index.
On the one hand, all this mapping between controller and joystick is extra work on your side, on the other hand it allows you to only handle joystick events and ignore all controller events - in fact, as long as you keep the corresponding Joystick device open, you can even close the Controller device (SDL_GameControllerClose()) after setting up the mapping, so SDL doesn’t even create any Controller Events.
If this makes sense depends on your code, maybe this makes it simpler overall, or maybe more complex…

1 Like

Agreed, isn’t guaranteed that which struct member would be in same offset across all union members. Fixed.

Yes. I’m going in this way, in fact. I have a struct player player[PLAYER_MAX], where one of the members is a pointer to the GameController (not using joysticks right now, more on that later). And I also use a struct action actions[PLAYER_MAX] related to each player given a processed input.

When some event come in I search in player array if is related to some player controller. If it is, I check for the correct action (lets say jump) and update the correct actions field.

Then later when processing updates I check for the events in the struct action related to each player and update the game states as necessary.

In the end everything seems too convoluted, and I keep thinking if there isn’t better ways to handle it. During this explanation I thought that would be simpler if the controller is associated with a player instead.

I know about the int SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller) and SDL_GameController *SDL_GameControllerFromPlayerIndex(int player_index); (by the way, those are NOT on official documentation, I had to read source code for that, another rant.) and I choose not use those since SDL assign a player index to each controller and I don’t want that. In my game only after a key is pressed (lets say START button), that a player is assigned, before it, all controllers are just hanging around opened.

Nice to know that, I will definitively try, at least in a small test application. Even being more complex, using this approach, I think is better suited since we can keep a uniform handling of all game input inside the code without ignore joysticks without proper binding. Not correctly mapped controllers it will not work nice, but at least it will work somehow. Maybe an in game input mapper (that is also on my plans) solve this issue.