Handling of modifier keys

Started by
4 comments, last by Stefan Fischlschweiger 9 years, 1 month ago

I recently redesigned our game's input system based on the robust input handling article, and it works perfectly. However something I've noticed -- not just with this design but with others I've used as well -- is that modifier key behavior can be tricky to handle correctly, and it's not something I've seen covered in detail by resources on the subject.

The issue with modifier keys (Ctrl, Alt, and Shift for the purpose of this post) is that they can either be bound individually to game actions ('Shift' -> 'Action_Jump'), or in combination with a non-modifier key ('Ctrl+A' -> 'Select_All'). As you can imagine, this results in some interesting behavior if you have a modifier key that is both bound to an action on its own and modifiers another key bound to another action. If you want to trigger the latter action, you'll necessarily trigger the former when you first press the modifier. I don't have a problem with this though, since I'd say it's expected behavior when the user binds their actions in such a way (where an 'action' is a single-shot event).

However when the modifier key is bound to a state, the expected behavior is not as clear, especially if that state is designed to modify other actions that are triggered when that state is active. To take a specific example, in our game the user can press the 'Q' key to queue a single unit for production at a factory. If they're holding down the 'Shift' key when they do this, the unit is queued in a special mode that tells it to autonomously re-queue itself upon completion. At least this is what's supposed to happen, however if the user is holding down the 'Shift' key when they press the 'Q' key, the input system doesn't see a 'Q' press, it sees a 'Shift+Q' press, and no action is triggered.

The solution that made the most sense to me was to add some special logic for modifier keys bound to states. When the user presses a key, it first attempts to map that key with currently held modifiers. If it doesn't find any results, it then checks to see if there are any active states mapped to modifier keys (under the assumption that this could be why the original mapping failed). If there are, it re-maps the key without those modifier states, and uses those results instead. My reasoning behind this approach is that the user wouldn't expect a modifier key bound to a state, to interfere with any actions meant to be triggered while that state is active. The only side-effect is that if the user actually had another action bound to the modified key, like 'Shift+Q', that action would trigger instead of the 'Q' action. However I can live with that.

I'm curious as to whether or not this approach makes sense to anyone else, or whether they've found another way to deal with modifier key ambiguity? My above solution fixes the cases I need it to fix, however that doesn't mean there aren't some other nasty side-effects I haven't thought of yet that could appear with the right combination of key bindings. If possible, I'd like to move the special case handling out of code and somehow make it work by adding more options to the key binding data, but I'm not sure what that would look like...

Advertisement

If you are fine with this behavior:

- Shift+Q doesn't trigger individual 'Shift' actions and doesn't trigger individual Q actions

Then maybe you can just bit-OR the keys together with modifier-key masks to create their own IDs.

Let's say 'Q' is (internally) '16', and Shift is 42.

If 'Q' is pressed, the value '16' (binary: 00010000) gets sent to your game logic.

If 'Shift' is pressed, the value '42' is sent to your game logic.

But if Shift is used in combination with 'Q', then send the base key ID (00010000) with a higher bit flipped on as well (so 10000000 000100000).

I suggest that the 16 low bits be used for the keyboard key ID (including modifier keys used on their own), and the 16 high bits be used for modifier keys.

This way you can support up to 16 different unique "modifier keys" (LShift,RShift, LCtrl, RCtrl, LAlt, RAlt, Winkey, and whatever else you want - like maybe Tab or Enter as modifiers).

This also lets players bind Shift+Alt+Q as a distinctly different combo than Alt+Q, Q, or Shift+Q, or Shift+Alt. But Shift+Alt+Q is still identical to Alt+Shift+Q, or Q+Shift+Alt, which is the desired and expected behavior.

If your game supports multiple local players, you can also reserve a few bits to store an input device ID (so Button1 on Gamepad1 is different from Button1 on Gamepad2).

A far simpler way to handle this behavior is to handle key events as just that - raw keyboard input. There shouldn't be any sort of translation of modifier keys done in the input system, that is application-level behavior. The input system collects the raw key presses/releases/repeats from the operating system and then forwards them to other systems via callbacks or events.

To handle modifiers, you just keep track of the current state of every key on the keyboard (or mouse or gamepad). This state is updated by the raw events sent by the input system (e.g. Shift key event, then Q key event).

Whenever you wish to handle a key action that can have a modifier (such as pressing Q), you check to see what the current known state of the Shift key is. If pressed, you handle the modified action or not. This is the job of the input translation system that translates from raw events to high-level actions dependent on the current application or game state.

I agree with Aressera, and would suggest you go a bit further. Only your game should know what a modifier key is, and so for the sake of discussion, for the rest of my post i will refer to that key as the combo key. The OS or input system should only take care of sending your game what keys are down/up.

If you do it this way, you can make any key a combo key. TAB+Q could do the same thing from your example. L+Q could, also.
The only thing that really matters is that your game defines that there is a key which will be used as a combo key for other actions, and the game should decide which key that is. The game should also prevent the user from binding the combo key to an action, because a combo key cannot stand on it's own, it has to be pressed together with an action key.

devstropo.blogspot.com - Random stuff about my gamedev hobby

On top of Aressera's and Strewya's posts:

The problem comes from looking at input as events. There is no need to send input asynchronously to any and all sub-systems, so don't do so. Instead collect (more or less) raw input from the OS, encode it into a unified structure including a time stamp, enqueue them, and let the sub-systems access the queue to investigate the current state and the (short time) history of input. This allows for arbitrary key press combos as already mentioned, but it also allows to easily check for temporal dependencies (e.g. key presses in sequence and whether a combo key was pressed in time).

You could also prevent this altogether by disallowing binding modifier keys alone. At least I noticed this behaviour in some games

This topic is closed to new replies.

Advertisement