So the walking animation for my Enemy is very jittery and it seems to be because the velocity of the Enemy instantly goes to 0 and then backup again (when the player is close) and it makes the Enemy switch animations rapidly.

If the player keeps on walking close to the enemy the enemy’s velocity will go to 0 and then up again rapidly making the animation switch back and forth from walk and idle. Making it look jittery.

I have tried await animation_finished which kind of fixes it but it makes it look bad because then it will play the movement animation until finished when standing still.

I have attempted adding acceleration/deceleration to the Enemy. The idea is that the velocity should smoothly go down to 0 when the enemy comes to a stop and not crash down to 0. But i have not been able to get it to work properly.

Here is the code related to movement:

extends Enemy

@export var animation_player_legs: AnimationPlayer

func _ready():
	super()
	

func _physics_process(delta):
	if velocity != Vector2.ZERO:
		animation_player_legs.play("Walk1Legs")
	elif velocity == Vector2.ZERO:
		animation_player_legs.play("Idle1Legs")
	

func move_to_position(target_position: Vector2):
	#set the velocity of the nav_agent to the target_position
	var motion = position.direction_to(target_position).normalized() * current_speed
	nav_agent.set_velocity(motion)


func _on_navigation_agent_2d_velocity_computed(safe_velocity):
	#Modifies the velocity set in move_to_position to implement avoidance
	velocity = safe_velocity
	if state_machine.check_if_can_move():
		move_and_slide()

And here is the Chase state if needed:

class_name ChaseEnemyState extends State


const DISTANCE_TO_STOP_CHASING: int = 2000
const CHANCE_TO_STOP_CHASING: float = .5

var refresh_timer := Timer.new()
var transition_timer := Timer.new()


@export var refresh_target_position_time: float = 0.5 ##Refreshes the target position incase a new path opens up for example.

func _ready():
	super()
	
	refresh_timer.wait_time = refresh_target_position_time
	refresh_timer.one_shot = true
	refresh_timer.connect("timeout", _on_refresh_timer_timeout)
	add_child(refresh_timer)
	
	transition_timer.wait_time = 5
	transition_timer.one_shot = true
	transition_timer.connect("timeout", func(): transition.emit("Idle"))
	add_child(transition_timer)


func on_physics_process(delta: float) -> void:
	if nav_agent.is_navigation_finished():
		if try_to_stop_chasing():
			return
		set_next_chasing_target_point()
	var next_position = nav_agent.get_next_path_position()
	(owner as Creature).move_to_position(next_position)
	
	play_chase_anim()
	transition_to_idle_2()


func enter():
	if owner.is_queued_for_deletion():
		return
	owner.current_speed = owner.chasing_speed
	start_chasing()

func exit():
	print("exit")
	refresh_timer.stop()
	if animation_player: animation_player.stop()


func try_to_stop_chasing() -> bool:
	if player == null:
		return true
	var distance_to_player = Global.calculate_distance_to_player(owner)
	if distance_to_player > DISTANCE_TO_STOP_CHASING && randf() > CHANCE_TO_STOP_CHASING:
		stop_chasing()
		return true
	return false


func stop_chasing() -> void:
	var new_state = ["Idle", "Wandering"].pick_random()
	transition.emit(new_state)


func _on_refresh_timer_timeout() -> void:
	if try_to_stop_chasing():
		return
	start_chasing()


func start_chasing() -> void:
	set_next_chasing_target_point()
	refresh_timer.start()


func set_next_chasing_target_point() -> void:
	if player == null:
		return
	var player_position = player.global_position
	var navigation_point = NavigationServer2D.map_get_closest_point(nav_agent.get_navigation_map(), player_position)
	nav_agent.target_position = navigation_point


func transition_to_idle_2():
	if owner.ray_cast_2d.get_collider() != player or not owner.ray_cast_2d.is_colliding():
		if transition_timer.is_stopped():
			transition_timer.start() # Start the timer when the player isn't detected and the timer is stopped.
	else:
		transition_timer.stop() # Stop the timer when the player is detected. timer.stop() doesnt trigger timeout signal