Quick answer: Don’t check is_action_just_pressed in both _process and _physics_process — pick one. For event-based handling, filter echoes with event.is_echo() and use _unhandled_input instead of _input.
The player presses Jump once and the character jumps twice or jumps to twice the height. You add a log, see two consecutive just_pressed calls in the same press, and wonder how Godot can detect two key downs from a single physical press. It can’t. The duplication is in your callback wiring.
Cause 1: Checked in Two Places
The most common cause: Input.is_action_just_pressed is checked in both _process and _physics_process in the same frame. The flag stays true for a full frame, so both callbacks see it as true and both jump.
# Anti-pattern: both callbacks consume the same press
func _process(delta):
if Input.is_action_just_pressed("jump"):
queue_jump_animation()
func _physics_process(delta):
if Input.is_action_just_pressed("jump"):
velocity.y = jump_force # also fires this frame
If the press is registered between a physics tick and the following render frame, both callbacks observe the rising edge. The animation queues twice, or two physics frames apply jump_force back-to-back.
The fix is to read the press in one place and store a one-frame queued action:
var jump_queued: bool = false
func _process(delta):
if Input.is_action_just_pressed("jump"):
jump_queued = true
func _physics_process(delta):
if jump_queued:
velocity.y = jump_force
queue_jump_animation()
jump_queued = false
The press is captured once in _process (rising-edge accuracy) and consumed once in _physics_process (deterministic physics timing).
Cause 2: Unfiltered Key Echoes
If you handle keys in _input directly:
func _input(event):
if event is InputEventKey and event.pressed and event.keycode == KEY_SPACE:
jump() # fires repeatedly while space is held
The OS sends key-repeat events while a key is held. The InputEventKey has is_echo() == true for those repeats. Filter them out:
func _input(event):
if event is InputEventKey and event.pressed and not event.is_echo() and event.keycode == KEY_SPACE:
jump()
Better: use the InputMap and check event.is_action_pressed("jump"), which has an optional allow_echo argument defaulting to false:
func _input(event):
if event.is_action_pressed("jump"): # ignores echoes by default
jump()
Cause 3: Both _input and _unhandled_input Handling the Same Event
Godot dispatches input events to _input first, then to _unhandled_input if nothing called get_viewport().set_input_as_handled(). If you implement jump handling in both, a single key press fires both callbacks. Pick one:
# Use _unhandled_input for gameplay so UI takes priority
func _unhandled_input(event):
if event.is_action_pressed("jump"):
jump()
get_viewport().set_input_as_handled()
Verifying
Add a print with frame number:
func _unhandled_input(event):
if event.is_action_pressed("jump"):
print("jump on frame ", Engine.get_process_frames())
One print per physical press means the fix is in. Two prints on the same frame — you’re still double-binding.
“Pick one callback. Filter one echo. Set input as handled. The whole class of input double-fire bugs disappears.”
Almost all “double-jump on one press” reports trace back to _process+_physics_process both checking is_action_just_pressed.