1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-23 15:16:17 +00:00

Add methods to check which event first triggered "just pressed/released" state.

This commit is contained in:
Pāvels Nadtočajevs
2025-08-12 11:56:35 +03:00
parent ca452113d4
commit 10fd7163d4
7 changed files with 114 additions and 17 deletions

View File

@@ -123,6 +123,8 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match"), &Input::is_action_pressed, DEFVAL(false)); 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_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("is_action_just_released", "action", "exact_match"), &Input::is_action_just_released, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_action_just_pressed_by_event", "action", "event", "exact_match"), &Input::is_action_just_pressed_by_event, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_action_just_released_by_event", "action", "event", "exact_match"), &Input::is_action_just_released_by_event, 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_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_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_axis", "negative_action", "positive_action"), &Input::get_axis);
@@ -411,6 +413,37 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
} }
} }
bool Input::is_action_just_pressed_by_event(const StringName &p_action, const Ref<InputEvent> &p_event, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
ERR_FAIL_COND_V(p_event.is_null(), false);
if (disable_input) {
return false;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
}
if (p_exact && E->value.exact == false) {
return false;
}
if (E->value.pressed_event_id != p_event->get_instance_id()) {
return false;
}
// 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();
} 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) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
@@ -437,6 +470,37 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
} }
} }
bool Input::is_action_just_released_by_event(const StringName &p_action, const Ref<InputEvent> &p_event, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
ERR_FAIL_COND_V(p_event.is_null(), false);
if (disable_input) {
return false;
}
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
}
if (p_exact && E->value.exact == false) {
return false;
}
if (E->value.released_event_id != p_event->get_instance_id()) {
return false;
}
// 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();
} 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) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
@@ -895,10 +959,12 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
_update_action_cache(E.key, action_state); _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. // 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) { if (action_state.cache.pressed && !was_pressed) {
action_state.pressed_event_id = p_event->get_instance_id();
action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
} }
if (!action_state.cache.pressed && was_pressed) { if (!action_state.cache.pressed && was_pressed) {
action_state.released_event_id = p_event->get_instance_id();
action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
} }
@@ -1027,6 +1093,7 @@ void Input::action_press(const StringName &p_action, float p_strength) {
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. // 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) { if (!action_state.cache.pressed) {
action_state.pressed_event_id = ObjectID();
action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
} }
@@ -1045,6 +1112,7 @@ void Input::action_release(const StringName &p_action) {
action_state.cache.strength = 0.0; action_state.cache.strength = 0.0;
action_state.cache.raw_strength = 0.0; action_state.cache.raw_strength = 0.0;
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
action_state.released_event_id = ObjectID();
action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
action_state.device_states.clear(); action_state.device_states.clear();

View File

@@ -109,6 +109,8 @@ private:
uint64_t pressed_process_frame = UINT64_MAX; uint64_t pressed_process_frame = UINT64_MAX;
uint64_t released_physics_frame = UINT64_MAX; uint64_t released_physics_frame = UINT64_MAX;
uint64_t released_process_frame = UINT64_MAX; uint64_t released_process_frame = UINT64_MAX;
ObjectID pressed_event_id;
ObjectID released_event_id;
bool exact = true; bool exact = true;
struct DeviceState { struct DeviceState {
@@ -306,6 +308,8 @@ public:
bool is_action_pressed(const StringName &p_action, bool p_exact = false) 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_pressed(const StringName &p_action, bool p_exact = false) const;
bool is_action_just_released(const StringName &p_action, bool p_exact = false) const; bool is_action_just_released(const StringName &p_action, bool p_exact = false) const;
bool is_action_just_pressed_by_event(const StringName &p_action, const Ref<InputEvent> &p_event, bool p_exact = false) const;
bool is_action_just_released_by_event(const StringName &p_action, const Ref<InputEvent> &p_event, bool p_exact = false) const;
float get_action_strength(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; float get_action_raw_strength(const StringName &p_action, bool p_exact = false) const;

View File

@@ -212,7 +212,20 @@
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[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] 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] 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. [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. See also [method is_action_just_pressed_by_event].
</description>
</method>
<method name="is_action_just_pressed_by_event" qualifiers="const">
<return type="bool" />
<param index="0" name="action" type="StringName" />
<param index="1" name="event" type="InputEvent" />
<param index="2" name="exact_match" type="bool" default="false" />
<description>
Returns [code]true[/code] when the user has [i]started[/i] pressing the action event in the current frame or physics tick, and the first event that triggered action press in the current frame/physics tick was [param event]. 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, and the action is processed during input handling (e.g. [method Node._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.
[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.
</description> </description>
</method> </method>
<method name="is_action_just_released" qualifiers="const"> <method name="is_action_just_released" qualifiers="const">
@@ -223,7 +236,19 @@
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. 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. [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. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
[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. [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. See also [method is_action_just_released_by_event].
</description>
</method>
<method name="is_action_just_released_by_event" qualifiers="const">
<return type="bool" />
<param index="0" name="action" type="StringName" />
<param index="1" name="event" type="InputEvent" />
<param index="2" name="exact_match" type="bool" default="false" />
<description>
Returns [code]true[/code] when the user [i]stops[/i] pressing the action event in the current frame or physics tick, and the first event that triggered action release in the current frame/physics tick was [param event]. It will only return [code]true[/code] on the frame or tick that the user releases the button.
This is useful when an action is processed during input handling (e.g. [method Node._input]).
[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.
</description> </description>
</method> </method>
<method name="is_action_pressed" qualifiers="const"> <method name="is_action_pressed" qualifiers="const">

View File

@@ -483,7 +483,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
} }
if (p_event->is_action("ui_down", true) && p_event->is_pressed()) { if (p_event->is_action("ui_down", true) && p_event->is_pressed()) {
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_down", true)) { if (!input->is_action_just_pressed_by_event("ui_down", p_event, true)) {
return; return;
} }
joypad_event_process = true; joypad_event_process = true;
@@ -526,7 +526,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
} }
} else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) { } else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) {
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_up", true)) { if (!input->is_action_just_pressed_by_event("ui_up", p_event, true)) {
return; return;
} }
joypad_event_process = true; joypad_event_process = true;

View File

@@ -136,7 +136,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
return; return;
} }
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_left", true)) { if (!input->is_action_just_pressed_by_event("ui_left", p_event, true)) {
return; return;
} }
set_process_internal(true); set_process_internal(true);
@@ -152,7 +152,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
return; return;
} }
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_right", true)) { if (!input->is_action_just_pressed_by_event("ui_right", p_event, true)) {
return; return;
} }
set_process_internal(true); set_process_internal(true);
@@ -168,7 +168,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
return; return;
} }
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_up", true)) { if (!input->is_action_just_pressed_by_event("ui_up", p_event, true)) {
return; return;
} }
set_process_internal(true); set_process_internal(true);
@@ -180,7 +180,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
return; return;
} }
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_down", true)) { if (!input->is_action_just_pressed_by_event("ui_down", p_event, true)) {
return; return;
} }
set_process_internal(true); set_process_internal(true);

View File

@@ -315,7 +315,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid()); bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid());
if (p_event->is_action("ui_right", true)) { if (p_event->is_action("ui_right", true)) {
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_right", true)) { if (!input->is_action_just_pressed_by_event("ui_right", p_event, true)) {
return; return;
} }
set_process_internal(true); set_process_internal(true);
@@ -325,7 +325,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
} }
} else if (p_event->is_action("ui_left", true)) { } else if (p_event->is_action("ui_left", true)) {
if (is_joypad_event) { if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_left", true)) { if (!input->is_action_just_pressed_by_event("ui_left", p_event, true)) {
return; return;
} }
set_process_internal(true); set_process_internal(true);

View File

@@ -2301,15 +2301,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (joypadmotion_event.is_valid()) { if (joypadmotion_event.is_valid()) {
Input *input = Input::get_singleton(); Input *input = Input::get_singleton();
if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed(SNAME("ui_focus_next"))) { if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_next"), p_event)) {
next = from->find_next_valid_focus(); next = from->find_next_valid_focus();
} }
if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed(SNAME("ui_focus_prev"))) { if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_prev"), p_event)) {
next = from->find_prev_valid_focus(); next = from->find_prev_valid_focus();
} }
if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop")) && input->is_action_just_pressed(SNAME("ui_accessibility_drag_and_drop"))) { if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop")) && input->is_action_just_pressed_by_event(SNAME("ui_accessibility_drag_and_drop"), p_event)) {
if (gui_is_dragging()) { if (gui_is_dragging()) {
from->accessibility_drop(); from->accessibility_drop();
} else { } else {
@@ -2317,19 +2317,19 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
} }
} }
if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed(SNAME("ui_up"))) { if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed_by_event(SNAME("ui_up"), p_event)) {
next = from->_get_focus_neighbor(SIDE_TOP); next = from->_get_focus_neighbor(SIDE_TOP);
} }
if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed(SNAME("ui_left"))) { if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed_by_event(SNAME("ui_left"), p_event)) {
next = from->_get_focus_neighbor(SIDE_LEFT); next = from->_get_focus_neighbor(SIDE_LEFT);
} }
if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed(SNAME("ui_right"))) { if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed_by_event(SNAME("ui_right"), p_event)) {
next = from->_get_focus_neighbor(SIDE_RIGHT); next = from->_get_focus_neighbor(SIDE_RIGHT);
} }
if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed(SNAME("ui_down"))) { if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed_by_event(SNAME("ui_down"), p_event)) {
next = from->_get_focus_neighbor(SIDE_BOTTOM); next = from->_get_focus_neighbor(SIDE_BOTTOM);
} }
} else { } else {