-
-
Notifications
You must be signed in to change notification settings - Fork 23.1k
Description
Godot version
v4.0.rc2.official [d2699dc]
Issue description
I chased something down a rabbit hole again.
So my understanding of Input.is_action_just_pressed
was that it returned true if there was an input event that came in at the beginning of a given frame. This, generally, is true. I was concerned about doubling up in heavy load cases with multiple physics frames, but that turned out to be unwarranted: It returns true for the first _physics_process
of the frame, but false for all others, and it returns true in that frame's _process
. All good, no problems here, grats to whoever wrote that code. Things got weird when I decided to clamp the FPS and physics ticks to low values.
So here's the best way I can describe the behaviour: Input._is_action_just_pressed
seems to return true only when the action is actually being held down. Inversely, Input._is_action_just_released
only returns true when an action is not being held.
I realize that may sound blistering obvious, but essentially, here's a sequence of events that can happen:
Input processing begins
An event is found stating an action is pressed
An event is found stating that same action is released
the frame starts
Input.is_action_just_pressed
is called
The action was pressed prior to the current frame
However, the action is not currently being pressed
Thus, Input.is_action_just_pressed
returns false
Essentially, actions being released clears the criteria for is_just_pressed
. Curiously, the inverse is also true: If you release-and-press-again between frames, it you won't get a call to Input.is_action_just_released
.
So here's what I think is happening without actually trying to dredge through the source:
The impression I'm getting is that Input.is_action_just_pressed
and Input.is_action_just_released
is checking for "current" values, and them comparing those values to what they were the last time _process
or _physics_process
was called. This works in most cases, but it can break down if the internal state of an action toggles an even number of times between frames. Admittedly, this is probably not that critical an issue, but it's possible that in certain situations it could lead to dropped inputs if people are doing just
checks on action presses that are faster than the current framerate.
Edit: Upon further investigation, I don't think pressed and released are based solely on the state of the button last frame, but there still does seem to be a constraint where just-pressed and just-released aren't triggering unless the action is also up/down.
Again, I don't know what the source looks like, but I feel like ideally you'd have pressed
and released
be independent boolean sets that are cleared at the end of each frame and then set based on whatever input events were just received. You'd occasionally get delayed presses, and sometimes you'd have pressed
and released
both returning true on the same frame, but you'd never have dropped inputs. It might also provide a path to dealing with the infamous mouse wheel events.. but I get ahead of myself.
If I've learned only one thing from this, it's that I really should be putting my input checks in _unhandled_input
instead of _physics_process
.
Steps to reproduce
The clearest way I've found to observe this in action is with aggressive use of print statements and the FPS limiter and physics ticks set to 1. That way you can easily press and release an action in the same frame, within the frame.
MRP
So this is a crude tool that monitors the input of ui_accept
(bound to space/enter by default I believe) and prints the output at a framerate of 1. If you don't want to bother downloading, the relevant script is here:
extends Node
func _init():
Engine.max_fps = 1
func _input(event):
var frame := Engine.get_frames_drawn()
if event.is_action_pressed("ui_accept"):
print("EVENT: \"ui_accept\" pressed")
if event.is_action_released("ui_accept"):
print("EVENT: \"ui_accept\" released")
var input_cache : bool = false
func _process(delta):
var frame := Engine.get_frames_drawn()
var pressed := Input.is_action_pressed("ui_accept")
var just_pressed := Input.is_action_just_pressed("ui_accept")
var just_released := Input.is_action_just_released("ui_accept")
print("--- Frame %s ---" % frame)
print("Last frame pressed: %s" % input_cache)
print("Input.is_action_pressed(\"ui_accept\") == %s" % pressed)
print("Input.is_action_just_pressed(\"ui_accept\") == %s" % just_pressed)
print("Input.is_action_just_released(\"ui_accept\") == %s" % just_released)
print("\n")
input_cache = pressed