Quick answer: Oscillation happens when additional forces or logic move the node away from the target after move_toward() places it there. The fix is to check distance_to() before moving and stop when within a small threshold.
Here is how to fix Godot move toward not reaching target. You are using move_toward() to smoothly move a node to a target position, but instead of arriving cleanly, the node oscillates back and forth, jitters in place, or hovers a fraction of a pixel away from the destination. This is one of the most common movement bugs in Godot 4, and it comes down to how move_toward() interacts with delta time, step size, and arrival detection.
The Symptom
You call position = position.move_toward(target, speed) every frame. The node either overshoots and snaps back each frame (visible oscillation), moves at different speeds depending on frame rate, or gets extremely close but never triggers your arrival check. You test position == target and it is never true because the position is Vector2(199.9999847, 100.0000076) instead of Vector2(200, 100).
What Causes This
1. Delta not multiplied by speed. If you pass speed without multiplying by delta, the node moves speed pixels per frame, not per second. At 60 FPS this might look fine, but at 144 FPS it moves 2.4x faster. The overshoot behavior changes with frame rate.
2. Floating point drift after arrival. move_toward() clamps to the target when remaining distance is less than the step. But if you apply additional movement afterward — gravity, move_and_slide() — floating point drift pushes the position slightly off, making position == target fail.
3. No arrival check. Calling move_toward() unconditionally every frame is harmless with a static target, but with a moving target or other forces, the node keeps adjusting after arrival, causing jitter at the destination.
The Fix
Always multiply speed by delta and add an arrival check to stop movement once the node reaches the target:
extends Node2D
var target := Vector2(400, 300)
var speed := 200.0 # pixels per second
var arrived := false
func _process(delta):
if arrived:
return
position = position.move_toward(target, speed * delta)
# Exact check works because move_toward clamps
if position == target:
arrived = true
print("Arrived at target")
If other forces act on the node after move_toward(), use a distance threshold instead of exact comparison. For CharacterBody2D, prefer direction_to() with move_and_slide() to respect collisions:
extends CharacterBody2D
var target := Vector2(400, 300)
var speed := 200.0
const ARRIVAL_THRESHOLD := 2.0
func _physics_process(delta):
if global_position.distance_to(target) < ARRIVAL_THRESHOLD:
velocity = Vector2.ZERO
return
velocity = global_position.direction_to(target) * speed
move_and_slide()
Related Issues
If your node oscillates specifically when following a moving target, you may have a state machine issue where follow and idle states fight each other — see state machine stuck in wrong state. If movement jitters only in web exports, variable browser frame rates can amplify delta bugs — check web export issues for guidance on browser-specific timing.
Always multiply speed by delta. Always check for arrival. Those two rules fix 95% of move_toward issues.