How do I make AI character stop?

Godot Version

4.6.1 stable

Question

I am trying to make an AI character move to a certain spot, then stop. Instead of stopping, it moves past the point by a small amount, then turns back, goes past the point again, turns back and repeats the process indefinitely, which results in this buggy, flickering effect. How do I prevent this from happening?
The code is:

func _physics_process(_delta: float) -> void:
	var direction = global_position.direction_to(spot.global_position)
	if lets_go: #lets_go is a var that becomes true after a certain button is pressed.
		velocity = direction * movement_speed
		move_and_slide()
		print(velocity)

Thank you in advance for answering!

direction_to doesn’t take distance into account, you could also use distance_to or more optimally use the underlying math to compose a direction that doesn’t overshoot.

Direction to works by getting the difference in positions and normalizing it

var difference := spot.global_position - global_position
var direction := difference.normalize()

Normalize will reduce any vector over a length of 1.0, but it will also raise any vector below a length of 1.0 up. To only lower vectors over our desired 1.0 we could use limit_length(1.0)

var difference := spot.global_position - global_position
var direction := difference.limit_length(1.0)

This may work OK for 2D games since 1.0 is one pixel, but for 3D games 1.0 is a meter, and such smaller velocities means it could take an entire second to finally cross one meter to the target position. In either case you can prevent this slow down by diving by delta, because move_and_slide normally multiplies velocity by delta to produce frame-independent movement this will undo that to figure out the distance left for this frame, rather than the distance left for once second of movement.

var difference := spot.global_position - global_position
var direction := (difference / delta).limit_length(1.0)
1 Like

Thank you for the thorough explanation!

So, I used your suggestions and here is the result:

  1. trying to use “:=” to declare a var results in an error - “Cannot infer the type of “difference” variable because the value doesn’t have a set type”. I used “=” instead and it worked.
  2. Now, when character reaches the spot, it visibly stops moving, but print(velocity) still returns (-0.001526, 0.0)
    (0.001526, 0.0), which, if I understand correctly, is just delta*1. So it is technically still flickering by a fraction of a pixel, right? I wonder if it will be relevant for animations, because right now I dont use any, so it looks like it is not moving, but if I add movement animation will it result in a visual bug?
    I guess I just don’t have to tie walking animation to velocity, then it doesn’t really matter.
    The code now looks like this:

func _physics_process(_delta: float) → void:
var difference = spot.global_position - global_position
var direction = difference.limit_length(1.0)
if lets_go:
velocity = direction * movement_speed
move_and_slide()
print(velocity)

Did I miss anything?

1 Like

Must mean your var spot does not have a static type, this is fine and if you do not want static typing on difference that is fine too.

Sounds correct, micro-oscillations may still occur because floating point values are never exact. If you are hoping to animate with a velocity.x == 0.0 then it will likely never be true, even with perfect math. is_equal_approx or is_zero_approx are good functions for near-equality checking on a float.

1 Like

Thanks again, I’ll keep that in mind.