Godot: Improvements on the Keyboard action handling

Created on 14 Oct 2016  Â·  19Comments  Â·  Source: godotengine/godot

With the current input action system, if you press a modifier + key and there is no action registered for that combination no action gets pressed.
Even if there was one which had that key without modifier.

E.g.:

forward = W
left = A
right = D
shoot = Shift

If at some point I'm holding Shift to shoot, I cannot move (because the input event won't be W but Shift+W). This forces the dev to declare ActionMap this way:

forward = W, Shift+W
left = A, Shift+A
right = D, Shift+D
shoot = Shift

Which is annoying and hard to maintain if you have a good number of controls and are using one or two modifiers.

My proposal is to handle modifier this way:

  • Check if Modifier+Key is an action:

    • If yes, press that action

    • Else, check if there is an action which is just Key

    • If yes, press that action

I can start working on a PR if there is interest in this.
Any comment?

discussion enhancement core

Most helpful comment

I don't think modifier keys should be handled as modifier keys at all. It makes sense for a text editor, but no sense at all for games.

RTS games? Implementing unit grouping behaviours like in Age of Empires/Warcraft etc. is trivial when you support modifiers (1 selected, Ctrl+1 defines, Shift+1 removes selection, etc.). Basically any game with an advanced UI can benefit from modifiers. Sure it's not needed for the platformer demo, but keep in mind that Godot is meant to support all types of games.

All 19 comments

IIRC modifier+key pressed the action mapped to just key. In fact, I've seen some complaints about the behavior. When has this changed?

Anyway, this issue describes the intended behavior, so a PR would be great.

IIRC modifier+key pressed the action mapped to just key. In fact, I've seen some complaints about the behavior. When has this changed?

I think it all started with 5b96c3a5527c1b2989dbfbe625f1c763b8887334 which changed the behavior of is_action_pressed

I think it all started with 5b96c3a which changed the behavior of is_action_pressed

Can be. Likely related to #6388 then.

I don't think modifier keys should be handled as modifier keys at all. It makes sense for a text editor, but no sense at all for games. It means several of the most common keys used in games are basically unusable. If someone wants to use modifier keys in their game, that would be really easy to do with GDScript, but as you said, working around the way it currently works is a major hassle. Unfortunately it seems to be built into the base structure of InputEventKey.

As far as I know it's always been like this. Certainly before is_action_just_pressed was added.

I've been studying the code and thinking about a solution for a while now, here are my thoughts.

  • We will need to cycle the input map twice when a key is pressed, which will slow down parsing input when a lot of acitons/bindings are defined (not sure how much).
  • The exposed GDScript InputMap event_is_action might end up being unintuitive, by keeping the current behavior and doing a 1:1 match, and would have to be documented.

Another, more complex but optimized solution, could be to allow selecting which key events would ignore modifiers (or be blocked by them). CryEngine seems to do it this way (just by reading the docs, I have absolutely no experience with it).

@akien-mga , @vnen opinions?


@rgrams

I don't think modifier keys should be handled as modifier keys at all. It makes sense for a text editor, but no sense at all for games.

Yes, it's not very often needed maybe, but when you need it it's really nice to have it integrated.
Also, the editor uses them a lot. and other engines have it (e.g. Unreal), or somehow deal with it (e.g. CryEngine).
I think if implemented properly it will be a good feature

I don't think modifier keys should be handled as modifier keys at all. It makes sense for a text editor, but no sense at all for games.

RTS games? Implementing unit grouping behaviours like in Age of Empires/Warcraft etc. is trivial when you support modifiers (1 selected, Ctrl+1 defines, Shift+1 removes selection, etc.). Basically any game with an advanced UI can benefit from modifiers. Sure it's not needed for the platformer demo, but keep in mind that Godot is meant to support all types of games.

@akien-mga , @Faless OK, my comment was totally exaggerated. I'm sure it's debatable whether modifiers are used for the majority of games or not. But my point was: it's really easy to add them yourself and much harder to work around them if they're hard-coded in.

If you agree, then you could say on average it actually supports _all_ games better to _not_ have built-in modifier support :stuck_out_tongue:, Though I'm definitely not saying that's the right solution. I don't think a quick add-on fix for this is a good idea.


Here is the problem that I see:
Say you have three actions bound to these keys/combos: A, Control, and Control+A,
If you press Control+A (with a quick little gallop motion), you get the following sequence:

  • "Control" is pressed.
  • "Control+A" is pressed.
  • "Control" releases.
  • "A" releases.

The problem here is that "A" never gets its "pressed" call, and "Control+A" never gets released. Two actions are stuck halfway. If you do the same keystroke with slightly different timing (release Control and A at exactly the same time), "Control+A" and then "Control" are released. That makes more sense, but still the "A" action is being skipped entirely. And that difference in timing is pretty small; not something you want to rely upon.

The same thing happens in any combination of course. If you press "A", press "Control", then release "A", then release "Control". You get: "A pressed", "Control pressed", "Control+A released", "Control released". "A" never gets released and "Control+A" never gets pressed. If you're doing, say a 3rd-person game with WASD movement and Shift to crouch, you're screwed.

I get why this is, a lot of the time you'll have "Ctrl+1" mapped to something and you don't want it to fire the command on "1" at the same time. In those cases you won't have anything mapped to Ctrl, so that first key press doesn't matter.

. . . hmm . . . kind of a black and white problem I guess. _With_ modifiers you need to ignore a bunch of input, _without_ them you need to get everything. Maybe that Cryengine "noModifiers" flag isn't a bad idea. (though "Ignore Modifiers" would be clearer.)

The problem here is that "A" never gets its "pressed" call, and "Control+A" never gets released. Two actions are stuck halfway

Yes, that is exactly the bug in #6388 but those cases are easily solvable by saying that the release events ignore modifiers. (eg here, I'll PR that soon).

If you're doing, say a 3rd-person game with WASD movement and Shift to crouch, you're screwed.

I agree, but it doesn't have to be a black or white situation.
What I'd like to achieve is to support both behaviors without requiring game devs to code them.


We can solve it with a best match approach (eg. described at top) or a specify behavior one (eg. specifying in input maps if the key ignore or gets blocked by modifiers).
There is even a third option I was thinking about which is setting engine behavior for every modifier via engine.cfg (eg, input/shift_as_modifier) and GDScript (and InputMap.set_shift_as_modifier(bool enable)).

I keep throwing in new ideas but this is not an easy problem and deserves a refined solution :tophat:

Hmm.. I think I have an idea, though I'm not sure how good it would be...

First of all, what about allowing all keys to act as modifiers? That way, we would have a (probably) better solution, since we would have to hardcode way less things.

Second, I think that we should say for each combination of keys/modifiers if it is "singular" "exclusive".
Then, we might have the following:

  • // A, S, E are three normal actions, A+D is normal as well, S+D, E+D, C+E+A are three exclusive actions
  • // (Keyboard) -> (Action results) -> (Total action state)
  • A pressed -> A.press -> A
  • S pressed -> S.press -> A S
  • D pressed -> S.release, S+D.press, A+D.press -> A A+D S+D

    • S gets released, since S+D takes precedence.

  • S released -> S+D.release -> A A+D
  • E pressed -> A+D.release, E+D.press -> A E+D
  • D released -> E+D.release E.press -> A E
  • C pressed -> A.release E.release C+E+A.press -> C+E+A
  • E released -> C+E+A.release A.press -> A

    • A gets pressed, since C+E+A is no longer overriding it.

those cases are easily solvable by saying that the release events ignore modifiers.

Really? Won't that(#6862) just patch up some use cases but leave just as many issues? Then you can still get a release event without getting a pressed event. That's exactly the kind of "fix" that worries me.

Say you give the following input with your keyboard:
Shift-pressed, A-pressed, A-released, Shift-released

If you make released events ignore modifiers:
Result: Shift-pressed, Shift-A-pressed, Shift-A-released, A-released, Shift-released.

Current behavior:
Result: Shift-pressed, Shift-A-pressed, Shift-A-released, Shift-released.

So in this situation it gets worse. The way I see it you're just moving the problem from one use case to another.

@Faless The per-action specify method is starting to look like the best option to me. Your third option, specifying it globally could be cool too, but might exclude some possible uses. I'd say if you could specify it for each action, but set the global default, that would be best. That way you'd have all the options, but no one would ever be stuck toggling it for every action.

@bojidar-bg Allowing any key combinations sounds great to me. Perhaps a better word would be "exclusive"? I see a problem though. Say you have A and S as normal actions, and A+S as an "exclusive" action. Then wouldn't pressing A and S release both and press A+S? Or if only one key is released because of the exclusive action, how do you decide which one? Only the last?

@rgrams Ahhh, right, "exclusive" was the word!

Well, only one of them would get the release event, which would be the first one to press, since it would be the only one for which a press event was generated.
When releasing a combo, there would be a quick press/release of the last released key, since we have no idea that you want to release the whole combo and not only a key from it. Maybe an additional option would be needed for this :/

This "exclusive" stuff seems very complicated. If you have an action mapped to A, it should be pressed/release when the A key is pressed/released, no matter what the other keys' states are.

If you have an action mapped to S+A, it should be pressed when both keys are pressed and released when either key is released.

Those aren't mutually exclusive, so you may have both pressed at the same time. Now if want "exclusive" behavior for some reason, it's easy to code:

if is_action_pressed('single') and not is_action_pressed('combo'):
    # do stuff

If you want combo to take precedence then it might be even easier:

if is_action_pressed('combo'):
    # do stuff
elif is_action_pressed('single'):
    # do other stuff

I'm not sure it's that useful to have such behavior embedded in the engine. Is there some use case as an example?

Keys must be treated as just keyboard signals IMO. None must be special for the engine.

So

"Control" is pressed.
"A" is pressed. Thus --> "Control+A" is pressed.
"Control" released. Thus --> "Control+A" is released.
"A" released.

and

"B" is pressed.
"A" is pressed. Thus --> "B+A" is pressed.
"B" released. Thus --> "B+A" is released.
"A" released.

Must always behave similar I think.

@rgrams thanks for reviewing my PR. It should be fixed now.

I also uploaded my input test project on github so you can do some tests ( https://github.com/Faless/godot-input-test ). It's still pretty simple.

It would be nice to create automated tests for input.

@vnen Maybe I indeed don't have an actual usecase, hehe. So, just think of all keys are modifiers, and leave exclusiveness to users..

What's the status on this issue?

What's the status on this issue?

Well, I guess I got sucked into the networking part and I forgot about this.

To sum it up, these are the alternative proposals:

  1. Drop modifiers (_I don't like the idea of losing a feature_)
  2. Use best match approach (i.e. use action F when Shift+F is pressed if no Shift+F action is defined) (_could be counterintuitive_)
  3. Add option for ignoring modifiers (i.e. checkbox in InputMap event), if true extra modifiers will be ignored eg. action ctrl+F with ignore_modifers = true will fire when ctrl+shif+F is pressed but not when shift+F is pressed. (_gives good control over input without losing the modifier feature_)

Additional optional feature of 2 or 3:

  • Possibility to use any key as modifier (eg. A+E is a key combination) (_could be heavy_)

    • Ordered (_seems complex_)

    • Unordered (_better, but it still seems too much for me_)

I would go for option 3 without the additional feature which could be coded via script anyway.

I think we also need an opinion from @reduz

I like option 3, it's pretty common to have for example ctrl+1 and 1 be
different actions (like assign control group 1 and use control group one),
you don't want the engine doing guess work behind your back

On 25 January 2017 at 13:33, Fabio Alessandrelli notifications@github.com
wrote:

What's the status on this issue?

Well, I guess I got sucked into the networking part and I forgot about
this.

To sum it up, these are the alternative proposals:

  1. Drop modifiers (I don't like the idea of losing a feature)
  2. Use best match approach (i.e. use action F when Shift+F is pressed
    if no Shift+F action is defined) (could be counterintuitive)
  3. Add option for ignoring modifiers (i.e. checkbox in InputMap
    event), if true extra modifiers will be ignored eg. action ctrl+F with ignore_modifers
    = true will fire when ctrl+shif+F is pressed but not when shift+F is
    pressed. (gives good control over input without losing the modifier
    feature
    )

Additional optional feature of 2 or 3:

  • Possibility to use any key as modifier (eg. A+E is a key
    combination) (could be heavy)

    • Ordered (seems complex)

    • Unordered (better, but it still seems too much for me)

I would go for option 3 without the additional feature which could be
coded via script anyway.

I think we also need an opinion from @reduz https://github.com/reduz

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/godotengine/godot/issues/6826#issuecomment-275157885,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGVmPby6gkr1acy3lF2_dueD5fe7m9arks5rV3lmgaJpZM4KW8fI
.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Spooner picture Spooner  Â·  3Comments

EdwardAngeles picture EdwardAngeles  Â·  3Comments

testman42 picture testman42  Â·  3Comments

RayKoopa picture RayKoopa  Â·  3Comments

nunodonato picture nunodonato  Â·  3Comments