Skip to content

Commit

Permalink
Input - fix just pressed and released with short presses
Browse files Browse the repository at this point in the history
Previously if an action was both pressed and released on the same tick or frame, `is_action_just_pressed()` would return false, resulting in missed input.

This PR separately the timestamp for pressing and releasing so each can be tested independently.
  • Loading branch information
lawnjelly committed Jun 12, 2023
1 parent f3026c2 commit 63d208d
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 32 deletions.
6 changes: 4 additions & 2 deletions doc/classes/Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,10 @@
<argument index="0" name="action" type="String" />
<argument index="1" name="exact" type="bool" default="false" />
<description>
Returns [code]true[/code] when the user starts pressing the action event, meaning it's [code]true[/code] only on the frame that the user pressed down the button.
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 [code]exact[/code] 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>
</method>
Expand All @@ -217,8 +218,9 @@
<argument index="0" name="action" type="String" />
<argument index="1" name="exact" type="bool" default="false" />
<description>
Returns [code]true[/code] when the user stops pressing the action event, meaning it's [code]true[/code] only on the frame that the user released 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.
If [code]exact[/code] 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] 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.
</description>
</method>
<method name="is_action_pressed" qualifiers="const">
Expand Down
5 changes: 5 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,11 @@
Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (idle) frame, because they can't run at the target frame rate.
[b]Note:[/b] Currently implemented only in Android.
</member>
<member name="input_devices/compatibility/legacy_just_pressed_behavior" type="bool" setter="" getter="" default="false">
If [code]true[/code], [method Input.is_action_just_pressed] and [method Input.is_action_just_released] will only return [code]true[/code] if the action is still in the respective state, i.e. an action that is pressed [i]and[/i] released on the same frame will be missed.
If [code]false[/code], no input will be lost.
[b]Note:[/b] You should in nearly all cases prefer the [code]false[/code] setting. The legacy behavior is to enable supporting old projects that rely on the old logic, without changes to script.
</member>
<member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter="" default="true">
If [code]true[/code], sends mouse input events when tapping or swiping on the touchscreen.
</member>
Expand Down
66 changes: 42 additions & 24 deletions main/input_default.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include "core/input_map.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "main/default_controller_mappings.h"
#include "scene/resources/texture.h"
#include "servers/visual_server.h"
Expand Down Expand Up @@ -120,10 +121,13 @@ bool InputDefault::is_action_just_pressed(const StringName &p_action, bool p_exa
return false;
}

// Backward compatibility for legacy behavior, only return true if currently pressed.
bool pressed_requirement = legacy_just_pressed_behavior ? E->get().pressed : true;

if (Engine::get_singleton()->is_in_physics_frame()) {
return E->get().pressed && E->get().physics_frame == Engine::get_singleton()->get_physics_frames();
return pressed_requirement && E->get().pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return E->get().pressed && E->get().idle_frame == Engine::get_singleton()->get_idle_frames();
return pressed_requirement && E->get().pressed_idle_frame == Engine::get_singleton()->get_idle_frames();
}
}

Expand All @@ -138,10 +142,13 @@ bool InputDefault::is_action_just_released(const StringName &p_action, bool p_ex
return false;
}

// Backward compatibility for legacy behavior, only return true if currently released.
bool released_requirement = legacy_just_pressed_behavior ? !E->get().pressed : true;

if (Engine::get_singleton()->is_in_physics_frame()) {
return !E->get().pressed && E->get().physics_frame == Engine::get_singleton()->get_physics_frames();
return released_requirement && E->get().released_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return !E->get().pressed && E->get().idle_frame == Engine::get_singleton()->get_idle_frames();
return released_requirement && E->get().released_idle_frame == Engine::get_singleton()->get_idle_frames();
}
}

Expand Down Expand Up @@ -494,19 +501,26 @@ void InputDefault::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool

for (const Map<StringName, InputMap::Action>::Element *E = InputMap::get_singleton()->get_action_map().front(); E; E = E->next()) {
if (InputMap::get_singleton()->event_is_action(p_event, E->key())) {
Action &action = action_state[E->key()];

// If not echo and action pressed state has changed
if (!p_event->is_echo() && is_action_pressed(E->key(), false) != p_event->is_action_pressed(E->key())) {
Action action;
action.physics_frame = Engine::get_singleton()->get_physics_frames();
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = p_event->is_action_pressed(E->key());
if (p_event->is_action_pressed(E->key())) {
action.pressed = true;
action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action.pressed_idle_frame = Engine::get_singleton()->get_idle_frames();
} else {
action.pressed = false;
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_idle_frame = Engine::get_singleton()->get_idle_frames();
}
action.strength = 0.0f;
action.raw_strength = 0.0f;
action.exact = InputMap::get_singleton()->event_is_action(p_event, E->key(), true);
action_state[E->key()] = action;
}
action_state[E->key()].strength = p_event->get_action_strength(E->key());
action_state[E->key()].raw_strength = p_event->get_action_raw_strength(E->key());

action.strength = p_event->get_action_strength(E->key());
action.raw_strength = p_event->get_action_raw_strength(E->key());
}
}

Expand Down Expand Up @@ -627,29 +641,27 @@ void InputDefault::iteration(float p_step) {
}

void InputDefault::action_press(const StringName &p_action, float p_strength) {
Action action;
// Create or retrieve existing action.
Action &action = action_state[p_action];

action.physics_frame = Engine::get_singleton()->get_physics_frames();
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action.pressed_idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = true;
action.exact = true;
action.strength = p_strength;
action.raw_strength = p_strength;
action.exact = true;

action_state[p_action] = action;
}

void InputDefault::action_release(const StringName &p_action) {
Action action;
// Create or retrieve existing action.
Action &action = action_state[p_action];

action.physics_frame = Engine::get_singleton()->get_physics_frames();
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = false;
action.strength = 0.f;
action.raw_strength = 0.f;
action.exact = true;

action_state[p_action] = action;
action.strength = 0.0f;
action.raw_strength = 0.0f;
}

void InputDefault::set_emulate_touch_from_mouse(bool p_emulate) {
Expand Down Expand Up @@ -821,6 +833,12 @@ InputDefault::InputDefault() {

fallback_mapping = -1;

legacy_just_pressed_behavior = GLOBAL_DEF("input_devices/compatibility/legacy_just_pressed_behavior", false);
if (Engine::get_singleton()->is_editor_hint()) {
// Always use standard behaviour in the editor.
legacy_just_pressed_behavior = false;
}

// Parse default mappings.
{
int i = 0;
Expand Down
15 changes: 9 additions & 6 deletions main/input_default.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,17 @@ class InputDefault : public Input {
Vector3 gyroscope;
Vector2 mouse_pos;
MainLoop *main_loop;
bool legacy_just_pressed_behavior = false;

struct Action {
uint64_t physics_frame;
uint64_t idle_frame;
bool pressed;
bool exact;
float strength;
float raw_strength;
uint64_t pressed_physics_frame = UINT64_MAX;
uint64_t pressed_idle_frame = UINT64_MAX;
uint64_t released_physics_frame = UINT64_MAX;
uint64_t released_idle_frame = UINT64_MAX;
bool pressed = false;
bool exact = true;
float strength = 0.0f;
float raw_strength = 0.0f;
};

Map<StringName, Action> action_state;
Expand Down

0 comments on commit 63d208d

Please sign in to comment.