Input Map using multiple keys like Ctrl Up

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By throwcode

Hi, not sure how this is supposed to work but when I open a project and write code in script to make a player jump (default value in Input Map where “ui_up” is mapped to the keyboards UP key. I find that I can use any of the following to initiated a jump - alt up, shift up, ctrl up, and up!

This becomes a problem when I want to assign a specific action to say the “ctrl up” keyboard combination - as a climb command.

Am I missing something or is this how the Input Map is designed to behave? If so, how do I setup game commands that require multiple key inputs?

TC

:bust_in_silhouette: Reply From: flurick

Yes it seems like Input Map does not check all other actions if they overlap, this can be done manually at least a couple of different ways, and I imagine the details of how to do this are so much up to each case that this makes the most sense.

New actions can be named at the top of the Input Map window and created by clicking the “Add” button. Keyboard keys and/or joystick buttons can then be set to trigger this new action via the + button next to the each actions name (you probably have to scroll down in the list of actions to see the newly added). When typing in the new input keys you can use modifier keys like shift or control etc.

To only trigger one of the many overlapping actions you can check all the once using the same main key, for example the “up” key, in the same if/elfi/else code block in your script.

Other ways more focused on code could be:

func _process(delta):
		
	if Input.is_action_just_pressed("ui_up"):
		if Input.is_key_pressed(KEY_SHIFT):
			print("A")
		else:  
			print("a")

or without any actions:

func _input(event):
	if event is InputEventKey:
		if event.scancode == KEY_UP:
			if event.shift:
				print("A")
			else:
				print("a")
:bust_in_silhouette: Reply From: Lyceq

As flurick mentioned, Input Map does not check all other actions if they overlap. One of the main benefits (to me at least) of the Input Map is allowing the user to create custom mappings. However, in this setup we don’t know ahead of time which actions will have key modifiers.

One common scenario I can think of is selection control. Suppose you want to have controls to cycle your selection through various possible targets. Your default mapping is to use E to increment through possible targets and Q to decrement. Then the user changes the mapping to be Tab for increment and Shift+Tab to decrement.

In the default mapping you would not check for modifiers. After the user changes the mapping then Tab will work fine but Shift+Tab will both increment and decrement, leaving the selection as if nothing had happened. It seems the only way to handle this scenario is to check to make sure modifier key states match whatever is assigned to the input action.

To solve this, I add this function to be available to input processors:

func _check_modifiers_match(event: InputEvent, action: String) -> bool:
if not event is InputEventWithModifiers: return true
for e in InputMap.get_action_list(action):
	if e is InputEventWithModifiers:
		if e.alt == event.alt and \
		   e.command == event.command and \
		   e.control == event.control and \
		   e.meta == event.meta and \
		   e.shift == event.shift:
			return true
return false

This function returns true unless the event is a type with modifiers and the modifiers do not match any of the mappings assigned to associated action. Note that this does not check to make sure keys other than the modifiers do not match. This function should only be called after you are sure you have the right action.

Here is how you would invoke the function:

if event.is_action_pressed("cycle_selection_forward") and _check_modifiers_match(event, "cycle_selection_forward"):
# increment selection
if event.is_action_pressed("cycle_selection_backward") and _check_modifiers_match(event, "cycle_selection_backward"):
# decrement selection

Basically, check if the action is in the state you want and that the modifiers match. Do this for all actions. Note that this will block actions that you want to do regardless of modifiers. Perhaps you have some movement related actions that you want to perform no matter what modifiers are done. Simply don’t include this check for those actions.

I haven’t tested this thoroughly, but it has worked well for me so far. Please let me know how it goes if you try it out!

:bust_in_silhouette: Reply From: Valk

Hello,

I think you can do that using, not that efficient but works :slight_smile:

	if Input.is_action_pressed("firstKey"):
[ident]if Input.is_action_just_pressed("secondKey"):
		# action

Happy coding.