Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input - fix just pressed and released with short presses #77055

Merged
merged 1 commit into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions core/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,13 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
return false;
}

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

if (Engine::get_singleton()->is_in_physics_frame()) {
return E->value.pressed && E->value.physics_frame == Engine::get_singleton()->get_physics_frames();
return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return E->value.pressed && E->value.process_frame == Engine::get_singleton()->get_process_frames();
return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames();
}
}

Expand All @@ -315,10 +318,13 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
return false;
}

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

if (Engine::get_singleton()->is_in_physics_frame()) {
return !E->value.pressed && E->value.physics_frame == Engine::get_singleton()->get_physics_frames();
return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
return !E->value.pressed && E->value.process_frame == Engine::get_singleton()->get_process_frames();
return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames();
}
}

Expand Down Expand Up @@ -686,19 +692,24 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em

for (const KeyValue<StringName, InputMap::Action> &E : InputMap::get_singleton()->get_action_map()) {
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.process_frame = Engine::get_singleton()->get_process_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_process_frame = Engine::get_singleton()->get_process_frames();
} else {
action.pressed = false;
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_process_frame = Engine::get_singleton()->get_process_frames();
}
action.strength = 0.0f;
action.raw_strength = 0.0f;
Copy link
Member Author

@lawnjelly lawnjelly Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two do get overridden below (providing there are no side effects, which I don't think there are), but we could leave this in for historical / safety. It will be compiled out. But am equally happy to remove.

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 @@ -813,29 +824,27 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con
}

void Input::action_press(const StringName &p_action, float p_strength) {
Action action;
// Create or retrieve existing action.
Action &action = action_state[p_action];
lawnjelly marked this conversation as resolved.
Show resolved Hide resolved

action.physics_frame = Engine::get_singleton()->get_physics_frames();
action.process_frame = Engine::get_singleton()->get_process_frames();
action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
action.pressed = true;
action.strength = p_strength;
action.raw_strength = p_strength;
action.exact = true;

action_state[p_action] = action;
}

void Input::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.process_frame = Engine::get_singleton()->get_process_frames();
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_process_frame = Engine::get_singleton()->get_process_frames();
action.pressed = false;
action.strength = 0.f;
action.raw_strength = 0.f;
action.strength = 0.0f;
action.raw_strength = 0.0f;
action.exact = true;

action_state[p_action] = action;
}

void Input::set_emulate_touch_from_mouse(bool p_emulate) {
Expand Down Expand Up @@ -1531,6 +1540,12 @@ Input::Input() {
parse_mapping(entries[i]);
}
}

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;
}
}

Input::~Input() {
Expand Down
15 changes: 9 additions & 6 deletions core/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@ class Input : public Object {
Vector3 gyroscope;
Vector2 mouse_pos;
int64_t mouse_window = 0;
bool legacy_just_pressed_behavior = false;

struct Action {
uint64_t physics_frame;
uint64_t process_frame;
bool pressed;
bool exact;
float strength;
float raw_strength;
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 pressed = false;
bool exact = true;
float strength = 0.0f;
float raw_strength = 0.0f;
};

HashMap<StringName, Action> action_state;
Expand Down
6 changes: 4 additions & 2 deletions doc/classes/Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" 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 [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>
</method>
Expand All @@ -190,7 +191,8 @@
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" 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.
[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>
</method>
Expand Down
5 changes: 5 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,11 @@
Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (process) frame, because they can't run at the target frame rate.
[b]Note:[/b] Currently implemented only on 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/pen_tablet/driver" type="String" setter="" getter="">
Specifies the tablet driver to use. If left empty, the default driver will be used.
[b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
Expand Down