CharacterBody2D position jitters between two Vector2s

Godot Version

4.2.1

Question

I’m coding a top-down movement system in which I want a CharacterBody2D node to follow waypoints given by the player. Currently, I’m storing clicks in an Array and trying to cycle through the clicks.position as the node reaches each one. Then I want the node to detect whether it has reached the destination by using the following code:

func detect_target() -> void:
	current_pos = self.global_position
	
	if current_pos == target_position:
		print("target reached")
		

My problem is that, most of the time (but not all of the time), the node’s position doesn’t really settle on a definite Vector2 but instead “jitters” between, say, (252, 86) and (251,87). And since the target_position is (251,86), the target is never reached.

The code to set the target_position is the following:

func _input(event: InputEvent) -> void:
	if event.is_action_pressed("left_mouse"):
		mov_pos_array.append(get_global_mouse_position().floor())

func load_next_target() -> void:
	if not mov_pos_array.is_empty() and not has_target:
		print("loading next target")
		target_position = mov_pos_array.pop_front()
		has_target = true

And I’m using _physics_process to handle movement.

func _physics_process(_delta: float) -> void:
		direction = global_position.direction_to(target_position)
		velocity = character_speed * direction
		move_and_slide()

I used a CharacterBody2D because I wanted to handle collisions and navigation later, although I’m not sure that having a CB2D is a requirement for those. I was really experimenting with building movement modes and, even before moving onto pathfinding, I found that I couldn’t stop the CB2D from “jittering” in place, which causes it to never detect that it has arrived to its destination.

I’ve done this while using both Vector2 and Vector2i for my variables. I tried using Vector2’s is_equal_approx() but it didn’t seem to be able to match both positions either.

I’m attaching a video showing the issue by using a couple of labels for debug, as seen on the top of the screen. You’ll notice the current_pos label doing the jittery thing and, when inspecting the node under Remote, its current_position and actual node position are both unable to reach a point. You may need to make sure the video is playing 60fps as the jitter is too fast to see otherwise.

Thanks in advance for your help!

The problem is that your code doesn’t consider the fact that your character might (and likely will!) move past the target. You correctly calculate velocity as the product of character_speed and direction. Here, the character_speed determines the length of the velocity vector, so you need to cap it at precisely the point where your character would overshoot the target. It’s not a problem if it’s smaller (i.e. the character moves towards the target, but won’t reach it in the next frame), but it cannot be bigger! Calculate the distance to the target_position and divide it by delta to determine the maximum speed your character could move with the next frame without shooting past the target. Then simply take the minimum of your usual character_speed and that max_speed:

func _physics_process(delta: float) -> void:
    var direction = global_position.direction_to(target_position)
    var distance = global_position.distance_to(target_position)
    var max_speed = (distance / delta)
    velocity = min(character_speed, max_speed) * direction
    move_and_slide()
1 Like

I never thought about manipulating velocity like that, thank you! And double thanks for including the explanation, since that made the root of the problem that much clearer.