Skip to content

Clarify is_action_pressed() for multiple assigned buttons#30890

Merged
akien-mga merged 1 commit into
godotengine:masterfrom
KoBeWi:how_to_action
Jul 29, 2019
Merged

Clarify is_action_pressed() for multiple assigned buttons#30890
akien-mga merged 1 commit into
godotengine:masterfrom
KoBeWi:how_to_action

Conversation

@KoBeWi
Copy link
Copy Markdown
Member

@KoBeWi KoBeWi commented Jul 28, 2019

Supposed to resolve #30888

@KoBeWi KoBeWi requested a review from a team as a code owner July 28, 2019 13:38
@bojidar-bg
Copy link
Copy Markdown
Contributor

Would it be better to change the implementation of is_action_pressed to not release until all bound buttons are released?

@Xrayez
Copy link
Copy Markdown
Contributor

Xrayez commented Jul 29, 2019

Would it be better to change the implementation of is_action_pressed to not release until all bound buttons are released?

It would make such behavior in alignment according to my previous observations in other PR #30792 (comment). If people want the behavior described here, they could use is_action_just_pressed variant to achieve this?

@akien-mga
Copy link
Copy Markdown
Member

I'll merge this for now as it properly describes the current behavior. Might be worth opening an issue to further discuss @bojidar-bg's proposal.

@akien-mga akien-mga merged commit 14e3d29 into godotengine:master Jul 29, 2019
@akien-mga
Copy link
Copy Markdown
Member

Thanks!

@KoBeWi KoBeWi deleted the how_to_action branch July 29, 2019 22:32
@henriiquecampos
Copy link
Copy Markdown
Contributor

henriiquecampos commented Oct 16, 2019

For anyone that reaches here and want this expected behavior for Input.is_action_pressed the work around I found for that is:

Add this to an AutoLoad

static func is_pressed(action):
	var is_pressed = false
	for event in InputMap.get_action_list(action):
		if event is InputEventKey:
			is_pressed = Input.is_key_pressed(event.scancode)
		elif event is InputEventJoypadButton:
			is_pressed = Input.is_joy_button_pressed(event.device, event.button_index)
		elif event is InputEventMouseButton:
			is_pressed = Input.is_mouse_button_pressed(event.button_index)
		elif event is InputEventJoypadMotion:
			is_pressed = Input.get_action_strength(action)
                #add more elif to treat the type accordingly
		else:
			continue
		
		if is_pressed:
			break
	return is_pressed

henriiquecampos added a commit to pigdevstudio/kitchen-tales that referenced this pull request Oct 17, 2019
This allows an ActionNotifier to be pressed as long as one of the events that build up its action is still pressed, check godotengine/godot/pull/30890 for more
@akien-mga
Copy link
Copy Markdown
Member

Cherry-picked for 3.1.2.

@jitspoe
Copy link
Copy Markdown
Contributor

jitspoe commented Dec 20, 2020

For anyone that reaches here and want this expected behavior for Input.is_action_pressed the work around I found for that is:

Add this to an AutoLoad

static func is_pressed(action):
	var is_pressed = false
	for event in InputMap.get_action_list(action):
		if event is InputEventKey:
			is_pressed = Input.is_key_pressed(event.scancode)
		elif event is InputEventJoypadButton:
			is_pressed = Input.is_joy_button_pressed(event.device, event.button_index)
		elif event is InputEventMouseButton:
			is_pressed = Input.is_mouse_button_pressed(event.button_index)
		elif event is InputEventJoypadMotion:
			is_pressed = Input.get_action_strength(action)
                #add more elif to treat the type accordingly
		else:
			continue
		
		if is_pressed:
			break
	return is_pressed

That seems like a good solution. Would be nice to have at the engine level to reduce script complexity.

@Calinou
Copy link
Copy Markdown
Member

Calinou commented Dec 20, 2020

@jitspoe This should be discussed in a proposal 🙂

@jitspoe
Copy link
Copy Markdown
Contributor

jitspoe commented May 6, 2021

For anyone that reaches here and want this expected behavior for Input.is_action_pressed the work around I found for that is:

Add this to an AutoLoad

static func is_pressed(action):
	var is_pressed = false
	for event in InputMap.get_action_list(action):
		if event is InputEventKey:
			is_pressed = Input.is_key_pressed(event.scancode)
		elif event is InputEventJoypadButton:
			is_pressed = Input.is_joy_button_pressed(event.device, event.button_index)
		elif event is InputEventMouseButton:
			is_pressed = Input.is_mouse_button_pressed(event.button_index)
		elif event is InputEventJoypadMotion:
			is_pressed = Input.get_action_strength(action)
                #add more elif to treat the type accordingly
		else:
			continue
		
		if is_pressed:
			break
	return is_pressed

I've extended this a bit to properly check the input axis values (since get_action_strength fails when another key is pressed and released) and add radial deadzones (so diagonal controls work better), in case this is useful to anyone:

func is_pressed(action : String):
	var is_pressed := false
	for event in InputMap.get_action_list(action):
		if event is InputEventKey:
			is_pressed = Input.is_key_pressed(event.scancode)
		elif event is InputEventJoypadButton:
			is_pressed = Input.is_joy_button_pressed(event.device, event.button_index)
		elif event is InputEventMouseButton:
			is_pressed = Input.is_mouse_button_pressed(event.button_index)
		elif event is InputEventJoypadMotion:
			var axis : int = event.axis
			var axis_sign := sign(event.axis_value)
			var axis_val := Input.get_joy_axis(ControllerDevice, event.axis)# * axis_sign
			var axis_positive := axis_val * axis_sign
			var perpendicular_axis_val := 0.0
			if (axis == JOY_ANALOG_LX):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_LY)
			elif (axis == JOY_ANALOG_LY):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_LX)
			elif (axis == JOY_ANALOG_RX):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_RY)
			elif (axis == JOY_ANALOG_RY):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_RX)
			var value_2d := Vector2(axis_val, perpendicular_axis_val)
			var length := value_2d.length()
			if (length > ControllerDeadzone):
				var deadzone := (value_2d.normalized() * ControllerDeadzone).x  # Circular deadzone
				var wedge_deadzone := 0.4142135623730950488016887242097 * abs(perpendicular_axis_val) # 1 / tan(3/4 90 degrees).  The slope of 1/8 of a circle
				if (wedge_deadzone > deadzone):
					deadzone = wedge_deadzone
				if (axis_positive > deadzone):
					is_pressed = true
		else:
			continue
		if is_pressed:
			break
	return is_pressed

Note: Can't be a static function anymore (unless the deadzone value and controller ID are passed in).

@sicienss
Copy link
Copy Markdown

sicienss commented Dec 11, 2021

Thank you for this workaround.

I believe this should be the intended functionality in Godot, rather than what is currently there.

Just consider the common use case of creating an action for "move_right", which devs will typically poll with Input.get_action_pressed("move_right").

On Godot's current way of doing things, if player accidentally touches d-pad right when primarily using analog stick right, or vice versa, the polling of the method is bound to return false, and suddenly player has no input until they release and repress.

@Calinou
Copy link
Copy Markdown
Member

Calinou commented Dec 11, 2021

On Godot's current way of doing things, if player accidentally touches d-pad right when primarily using analog stick right, or vice versa, the polling of the method is bound to return false, and suddenly player has no input until they release and repress.

This should be addressed by #47599 if it's merged.

@afk-mario
Copy link
Copy Markdown

For anyone that reaches here and want this expected behavior for Input.is_action_pressed the work around I found for that is:
Add this to an AutoLoad

static func is_pressed(action):
	var is_pressed = false
	for event in InputMap.get_action_list(action):
		if event is InputEventKey:
			is_pressed = Input.is_key_pressed(event.scancode)
		elif event is InputEventJoypadButton:
			is_pressed = Input.is_joy_button_pressed(event.device, event.button_index)
		elif event is InputEventMouseButton:
			is_pressed = Input.is_mouse_button_pressed(event.button_index)
		elif event is InputEventJoypadMotion:
			is_pressed = Input.get_action_strength(action)
                #add more elif to treat the type accordingly
		else:
			continue
		
		if is_pressed:
			break
	return is_pressed

I've extended this a bit to properly check the input axis values (since get_action_strength fails when another key is pressed and released) and add radial deadzones (so diagonal controls work better), in case this is useful to anyone:

func is_pressed(action : String):
	var is_pressed := false
	for event in InputMap.get_action_list(action):
		if event is InputEventKey:
			is_pressed = Input.is_key_pressed(event.scancode)
		elif event is InputEventJoypadButton:
			is_pressed = Input.is_joy_button_pressed(event.device, event.button_index)
		elif event is InputEventMouseButton:
			is_pressed = Input.is_mouse_button_pressed(event.button_index)
		elif event is InputEventJoypadMotion:
			var axis : int = event.axis
			var axis_sign := sign(event.axis_value)
			var axis_val := Input.get_joy_axis(ControllerDevice, event.axis)# * axis_sign
			var axis_positive := axis_val * axis_sign
			var perpendicular_axis_val := 0.0
			if (axis == JOY_ANALOG_LX):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_LY)
			elif (axis == JOY_ANALOG_LY):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_LX)
			elif (axis == JOY_ANALOG_RX):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_RY)
			elif (axis == JOY_ANALOG_RY):
				perpendicular_axis_val = Input.get_joy_axis(ControllerDevice, JOY_ANALOG_RX)
			var value_2d := Vector2(axis_val, perpendicular_axis_val)
			var length := value_2d.length()
			if (length > ControllerDeadzone):
				var deadzone := (value_2d.normalized() * ControllerDeadzone).x  # Circular deadzone
				var wedge_deadzone := 0.4142135623730950488016887242097 * abs(perpendicular_axis_val) # 1 / tan(3/4 90 degrees).  The slope of 1/8 of a circle
				if (wedge_deadzone > deadzone):
					deadzone = wedge_deadzone
				if (axis_positive > deadzone):
					is_pressed = true
		else:
			continue
		if is_pressed:
			break
	return is_pressed

Note: Can't be a static function anymore (unless the deadzone value and controller ID are passed in).

For some reason this didn't work with my keyboard but I added the physical_scancode and it worked

...
if event is InputEventKey:
    is_pressed = Input.is_key_pressed(event.scancode) or Input.is_key_pressed(event.physical_scancode)
...

@Benpaste
Copy link
Copy Markdown

Hey, I'm trying to use this extended is_pressed() function, but Godot complains about ControllerDevice not being defined.
Could anyone provide some assistance/explanation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input.is_action_pressed() inconsistent for multiple key bindings

10 participants