Add device parameters to input action methods

Add `device` parameter to `is_action_pressed`

Changes `code` to `param` in docs

Fix `is_action_pressed` returning true when action is not pressed

Add `device` parameter to `is_action_just_pressed`

Fix incorrect `device_state` process frame assignment

Add `device` parameter to `is_action_just_released`, and fix bug in `is_action_just_pressed`

Add `device` parameter to `get_action_raw_strength`

Add `device` parameter to `get_action_strength`

Add `device` parameter to `get_axis`

Changes `-1` defaults to `InputMap::ALL_DEVICES`

Add `device` parameter to `get_vector`
This commit is contained in:
Carson Reader 2024-09-29 14:13:34 -07:00
parent f4af8201ba
commit 20ab419518
3 changed files with 113 additions and 38 deletions

View file

@ -101,13 +101,13 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_key_label_pressed", "keycode"), &Input::is_key_label_pressed);
ClassDB::bind_method(D_METHOD("is_mouse_button_pressed", "button"), &Input::is_mouse_button_pressed);
ClassDB::bind_method(D_METHOD("is_joy_button_pressed", "device", "button"), &Input::is_joy_button_pressed);
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match"), &Input::is_action_pressed, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action", "exact_match"), &Input::is_action_just_pressed, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_action_just_released", "action", "exact_match"), &Input::is_action_just_released, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &Input::get_action_strength, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action", "exact_match"), &Input::get_action_raw_strength, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action"), &Input::get_axis);
ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::get_vector, DEFVAL(-1.0f));
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match", "device"), &Input::is_action_pressed, DEFVAL(false), DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action", "exact_match", "device"), &Input::is_action_just_pressed, DEFVAL(false), DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("is_action_just_released", "action", "exact_match", "device"), &Input::is_action_just_released, DEFVAL(false), DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match", "device"), &Input::get_action_strength, DEFVAL(false), DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action", "exact_match", "device"), &Input::get_action_raw_strength, DEFVAL(false), DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action", "device"), &Input::get_axis, DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone", "device"), &Input::get_vector, DEFVAL(-1.0f), DEFVAL(InputMap::ALL_DEVICES));
ClassDB::bind_method(D_METHOD("add_joy_mapping", "mapping", "update_existing"), &Input::add_joy_mapping, DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_joy_mapping", "guid"), &Input::remove_joy_mapping);
ClassDB::bind_method(D_METHOD("is_joy_known", "device"), &Input::is_joy_known);
@ -298,17 +298,28 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
return joy_buttons_pressed.has(_combine_device(p_button, p_device));
}
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
bool Input::is_action_pressed(const StringName &p_action, bool p_exact, int p_device) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
if (!E || (p_exact && !E->value.exact)) {
return false;
}
return E->value.cache.pressed && (p_exact ? E->value.exact : true);
if (p_device != -1) {
if (E->value.device_states.has(p_device)) {
const ActionState::DeviceState &device_state = E->value.device_states[p_device];
for (int i = 0; i < Input::MAX_EVENT; i++) {
if (device_state.pressed[i]) {
return true;
}
}
}
return false;
}
return E->value.cache.pressed;
}
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact, int p_device) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
@ -322,14 +333,27 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
// Backward compatibility for legacy behavior, only return true if currently pressed.
bool pressed_requirement = legacy_just_pressed_behavior ? E->value.cache.pressed : true;
if (Engine::get_singleton()->is_in_physics_frame()) {
return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
// device is specified
if (p_device != InputMap::ALL_DEVICES) {
if (!E->value.device_states.has(p_device)) {
return false;
}
if (Engine::get_singleton()->is_in_physics_frame()) {
return pressed_requirement && E->value.device_states[p_device].pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return pressed_requirement && E->value.device_states[p_device].pressed_process_frame == Engine::get_singleton()->get_process_frames();
}
} else {
return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames();
// unspecified device, check cache for all devices
if (Engine::get_singleton()->is_in_physics_frame()) {
return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames();
}
}
}
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
bool Input::is_action_just_released(const StringName &p_action, bool p_exact, int p_device) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
@ -342,15 +366,25 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
// Backward compatibility for legacy behavior, only return true if currently released.
bool released_requirement = legacy_just_pressed_behavior ? !E->value.cache.pressed : true;
if (Engine::get_singleton()->is_in_physics_frame()) {
return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
if (p_device != InputMap::ALL_DEVICES) {
if (!E->value.device_states.has(p_device)) {
return false;
}
if (Engine::get_singleton()->is_in_physics_frame()) {
return released_requirement && E->value.device_states[p_device].released_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return released_requirement && E->value.device_states[p_device].released_process_frame == Engine::get_singleton()->get_process_frames();
}
} else {
return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames();
if (Engine::get_singleton()->is_in_physics_frame()) {
return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames();
}
}
}
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
float Input::get_action_strength(const StringName &p_action, bool p_exact, int p_device) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
@ -360,11 +394,20 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const
if (p_exact && E->value.exact == false) {
return 0.0f;
}
if (p_device != InputMap::ALL_DEVICES) {
if (!E->value.device_states.has(p_device)) {
return 0.0f;
}
float strength = 0.0f;
for (int i = 0; i < Input::MAX_EVENT; i++) {
strength = MAX(strength, E->value.device_states[p_device].strength[i]);
}
return strength;
}
return E->value.cache.strength;
}
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact, int p_device) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
@ -374,18 +417,27 @@ float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) c
if (p_exact && E->value.exact == false) {
return 0.0f;
}
if (p_device != InputMap::ALL_DEVICES) {
if (!E->value.device_states.has(p_device)) {
return 0.0f;
}
float strength = 0.0f;
for (int i = 0; i < Input::MAX_EVENT; i++) {
strength = MAX(strength, E->value.device_states[p_device].raw_strength[i]);
}
return strength;
}
return E->value.cache.raw_strength;
}
float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const {
return get_action_strength(p_positive_action) - get_action_strength(p_negative_action);
float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action, int p_device) const {
return get_action_strength(p_positive_action, false, p_device) - get_action_strength(p_negative_action, false, p_device);
}
Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone) const {
Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone, int p_device) const {
Vector2 vector = Vector2(
get_action_raw_strength(p_positive_x) - get_action_raw_strength(p_negative_x),
get_action_raw_strength(p_positive_y) - get_action_raw_strength(p_negative_y));
get_action_raw_strength(p_positive_x, false, p_device) - get_action_raw_strength(p_negative_x, false, p_device),
get_action_raw_strength(p_positive_y, false, p_device) - get_action_raw_strength(p_negative_y, false, p_device));
if (p_deadzone < 0.0f) {
// If the deadzone isn't specified, get it from the average of the actions.
@ -773,6 +825,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
// Update the action's per-device state.
ActionState::DeviceState &device_state = action_state.device_states[device_id];
bool device_was_pressed = device_state.pressed[event_index];
device_state.pressed[event_index] = is_pressed;
device_state.strength[event_index] = p_event->get_action_strength(E.key);
device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key);
@ -783,18 +836,23 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
action_state.api_strength = 0.0;
}
action_state.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);
bool was_pressed = action_state.cache.pressed;
_update_action_cache(E.key, action_state);
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
if (action_state.cache.pressed && !was_pressed) {
action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
if (!action_state.cache.pressed && was_pressed) {
} else if (!action_state.cache.pressed && was_pressed) {
action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
}
if (is_pressed && !device_was_pressed) {
device_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
device_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
} else if (!is_pressed && device_was_pressed) {
device_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
device_state.released_process_frame = Engine::get_singleton()->get_process_frames();
}
}
if (event_dispatch_function) {

View file

@ -32,6 +32,7 @@
#define INPUT_H
#include "core/input/input_event.h"
#include "core/input/input_map.h"
#include "core/object/object.h"
#include "core/os/keyboard.h"
#include "core/os/thread_safe.h"
@ -115,6 +116,10 @@ private:
bool pressed[MAX_EVENT] = { false };
float strength[MAX_EVENT] = { 0.0 };
float raw_strength[MAX_EVENT] = { 0.0 };
uint64_t pressed_physics_frame = UINT64_MAX;
uint64_t pressed_process_frame = UINT64_MAX;
uint64_t released_physics_frame = UINT64_MAX;
uint64_t released_process_frame = UINT64_MAX;
};
bool api_pressed = false;
float api_strength = 0.0;
@ -292,14 +297,14 @@ public:
bool is_key_label_pressed(Key p_keycode) const;
bool is_mouse_button_pressed(MouseButton p_button) const;
bool is_joy_button_pressed(int p_device, JoyButton p_button) const;
bool is_action_pressed(const StringName &p_action, bool p_exact = false) const;
bool is_action_just_pressed(const StringName &p_action, bool p_exact = false) const;
bool is_action_just_released(const StringName &p_action, bool p_exact = false) const;
float get_action_strength(const StringName &p_action, bool p_exact = false) const;
float get_action_raw_strength(const StringName &p_action, bool p_exact = false) const;
bool is_action_pressed(const StringName &p_action, bool p_exact = false, int p_device = InputMap::ALL_DEVICES) const;
bool is_action_just_pressed(const StringName &p_action, bool p_exact = false, int p_device = InputMap::ALL_DEVICES) const;
bool is_action_just_released(const StringName &p_action, bool p_exact = false, int p_device = InputMap::ALL_DEVICES) const;
float get_action_strength(const StringName &p_action, bool p_exact = false, int p_device = InputMap::ALL_DEVICES) const;
float get_action_raw_strength(const StringName &p_action, bool p_exact = false, int p_device = InputMap::ALL_DEVICES) const;
float get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const;
Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f) const;
float get_axis(const StringName &p_negative_action, const StringName &p_positive_action, int p_device = -1) const;
Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f, int p_device = InputMap::ALL_DEVICES) const;
float get_joy_axis(int p_device, JoyAxis p_axis) const;
String get_joy_name(int p_idx);

View file

@ -57,27 +57,33 @@
<return type="float" />
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" type="bool" default="false" />
<param index="2" name="device" type="int" default="-1" />
<description>
Returns a value between 0 and 1 representing the raw intensity of the given action, ignoring the action's deadzone. In most cases, you should use [method get_action_strength] instead.
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[param device] is the device id of the input device to be queried. If it is left at it's default, [code]-1[/code], all devices will be queried.
</description>
</method>
<method name="get_action_strength" qualifiers="const">
<return type="float" />
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" type="bool" default="false" />
<param index="2" name="device" type="int" default="-1" />
<description>
Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis such as the keyboard, the value returned will be 0 or 1.
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[param device] is the device id of the input device to be queried. If it is left at it's default, [code]-1[/code], all devices will be queried.
</description>
</method>
<method name="get_axis" qualifiers="const">
<return type="float" />
<param index="0" name="negative_action" type="StringName" />
<param index="1" name="positive_action" type="StringName" />
<param index="2" name="device" type="int" default="-1" />
<description>
Get axis input by specifying two actions, one negative and one positive.
This is a shorthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code].
[param device] is the device id of the input device to query, [code]-1[/code], all devices will be queried.
</description>
</method>
<method name="get_connected_joypads">
@ -199,10 +205,12 @@
<return type="bool" />
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" type="bool" default="false" />
<param index="2" name="device" type="int" default="-1" />
<description>
Returns [code]true[/code] when the user has [i]started[/i] pressing the action event in the current frame or physics tick. It will only return [code]true[/code] on the frame or tick that the user pressed down the button.
This is useful for code that needs to run only once when an action is pressed, instead of every frame while it's pressed.
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[param device] is the device id of the input device to be queried. If it is left at it's default, [code]-1[/code], all devices will be queried.
[b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] pressed. An action can be pressed and released again rapidly, and [code]true[/code] will still be returned so as not to miss input.
[b]Note:[/b] Due to keyboard ghosting, [method is_action_just_pressed] may return [code]false[/code] even if one of the action's keys is pressed. See [url=$DOCS_URL/tutorials/inputs/input_examples.html#keyboard-events]Input examples[/url] in the documentation for more information.
[b]Note:[/b] During input handling (e.g. [method Node._input]), use [method InputEvent.is_action_pressed] instead to query the action state of the current event.
@ -212,10 +220,12 @@
<return type="bool" />
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" type="bool" default="false" />
<param index="2" name="device" type="int" default="-1" />
<description>
Returns [code]true[/code] when the user [i]stops[/i] pressing the action event in the current frame or physics tick. It will only return [code]true[/code] on the frame or tick that the user releases the button.
[b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] not pressed. An action can be released and pressed again rapidly, and [code]true[/code] will still be returned so as not to miss input.
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[param device] is the device id of the input device to be queried. If it is left at it's default, [code]-1[/code], all devices will be queried.
[b]Note:[/b] During input handling (e.g. [method Node._input]), use [method InputEvent.is_action_released] instead to query the action state of the current event.
</description>
</method>
@ -223,9 +233,11 @@
<return type="bool" />
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" type="bool" default="false" />
<param index="2" name="device" type="int" default="-1"/>
<description>
Returns [code]true[/code] if you are pressing the action event.
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[param device] is the device id of the input device to be queried. If it is left at it's default, [code]-1[/code], all devices will be queried.
[b]Note:[/b] Due to keyboard ghosting, [method is_action_pressed] may return [code]false[/code] even if one of the action's keys is pressed. See [url=$DOCS_URL/tutorials/inputs/input_examples.html#keyboard-events]Input examples[/url] in the documentation for more information.
</description>
</method>