How to link up enemy animations?

Godot Version

4.2.2

How do I link up enemy animations?

I am making my first game now, it is a top-down RPG game, so the characters move in both X and Y axis. I have written an animation script for the player node linked to the input and now I am trying to use the same script for the enemy animation, but instead of input link it to the enemy facing direction or player position relative to the enemy. Not sure which one is better. Any advice on how to make the enemy animation so it fits in with the character animation?

Here is the player script:

extends CharacterBody2D;

const max_speed = 100;
const accel = 600;
const friction = 600;

var input = Vector2.ZERO
var current_dir = Vector2.ZERO
var current_animation = ""

func _ready():
	$AnimatedSprite2D.play("idle_down")

func _physics_process(delta):
	player_movement(delta)
	play_direction_anim(delta)


func get_input():
	input.x = int(Input.is_action_pressed("ui_right")) - int(Input.is_action_pressed("ui_left"))
	input.y = int(Input.is_action_pressed("ui_down")) - int(Input.is_action_pressed("ui_up"))
	return input.normalized()
	

func player_movement(delta):
	input = get_input()
	
	if input == Vector2.ZERO:
		if velocity.length() > (friction * delta):
			velocity -= velocity.normalized() * (friction * delta)
		else:
			velocity = Vector2.ZERO
	else:
		velocity += (input * accel * delta)
		velocity = velocity.limit_length(max_speed)
	
	move_and_slide()

func play_direction_anim(delta):
	var anim = $AnimatedSprite2D

	var new_animation = ""

	if input.x != 0 or input.y != 0:
		current_dir = input

	if current_dir.x > 0 and velocity.length() > 0:
		new_animation = "walk_right"
	elif current_dir.x < 0 and velocity.length() > 0:
		new_animation = "walk_left"
	elif current_dir.y > 0 and velocity.length() > 0:
		new_animation = "walk_down"
	elif current_dir.y < 0 and velocity.length() > 0:
		new_animation = "walk_up"
	else:
		if current_animation.ends_with("_right") and velocity.length() == 0:
			new_animation = "idle_right"
		elif current_animation.ends_with("_left") and velocity.length() == 0:
			new_animation = "idle_left"
		elif current_animation.ends_with("_down") and velocity.length() == 0:
			new_animation = "idle_down"
		elif current_animation.ends_with("_up") and velocity.length() == 0:
			new_animation = "idle_up"

	if new_animation != current_animation:
		anim.play(new_animation)
		current_animation = new_animation

And here is the enemy script i have right now:

extends CharacterBody2D

var speed = 35
var player_chase = false
var player = null

func _physics_process(delta):
	if player_chase:
		position += (player.position - position)/speed
		
		

func _on_detection_area_body_entered(body):
	player = body
	player_chase = true

func _on_detection_area_body_exited(body):
	player = null
	player_chase = false
	

you can basically do the same as the player-script:

var direction

func _physics_process(delta):
	if not player_chase:
		return
	
	enemy_movement(delta)
	play_direction_anim()

func enemy_movement(delta):
	direction = global_position.direction_to(player.global_position)
	velocity = direction * speed * delta
	move_and_slide()
	
func play_direction_anim():
	# Put your animation code here
	# Instead 'input' you use 'direction'
		
1 Like

It works! But I think I messed something up anyway, because the enemy is only playing the ‘_right’ animations now. Could it be because it sees direction relatively to the world’s 0.0 coords, rather than node’s new location and direction to player?

Again, adding the whole script, because I might be wrong about the location of the mistake…

extends CharacterBody2D

var speed = 45
var player_chase = false
var player = null
var direction
var current_dir = Vector2.ZERO
var current_animation = ""

func _physics_process(delta):
	if player_chase:
		position += (player.position - position)/speed
	
	if not player_chase:
		return
	
	enemy_movement(delta)
	play_direction_anim()
		

func _on_detection_area_body_entered(body):
	player = body
	player_chase = true

func _on_detection_area_body_exited(body):
	player = null
	player_chase = false
	
func enemy_movement(delta):
	direction = global_position.direction_to(player.global_position)
	velocity = direction * speed * delta
	move_and_slide()
	
func play_direction_anim():
	var anim = $AnimatedSprite2D

	var new_animation = ""

	if direction.x != 0 or direction.y != 0:
		current_dir = direction

	if current_dir.x > 0 and velocity.length() > 0:
		new_animation = "slime_walk_right"
	elif current_dir.x < 0 and velocity.length() > 0:
		new_animation = "slime_walk_left"
	elif current_dir.y > 0 and velocity.length() > 0:
		new_animation = "slime_walk_down"
	elif current_dir.y < 0 and velocity.length() > 0:
		new_animation = "slime_walk_up"
	else:
		if current_animation.ends_with("_right") and velocity.length() == 0:
			new_animation = "slime_idle_right"
		elif current_animation.ends_with("_left") and velocity.length() == 0:
			new_animation = "slime_idle_left"
		elif current_animation.ends_with("_down") and velocity.length() == 0:
			new_animation = "slime_idle_down"
		elif current_animation.ends_with("_up") and velocity.length() == 0:
			new_animation = "slime_idle_up"

	if new_animation != current_animation:
		anim.play(new_animation)
		current_animation = new_animation

i think a problem could be that you are checking if velocity.length() is greater then 0 after the velocity has been applied. When you call move_and_slide() the velocity value gets changed based on the velocity thats not been moved yet, for example when you hit a wall.
Heres a slightly more optimized method for animations:

func play_direction_anim():
	var anim = $AnimatedSprite2D

	var new_animation = ""

	if direction != Vector2.ZERO:
		current_dir = direction
		new_animation = play_walk()
	else:
		new_animation = play_idle()

	if new_animation != current_animation:
		anim.play(new_animation)
		current_animation = new_animation

func play_walk():
	if current_dir.x > 0:
		return "slime_walk_right"
	elif current_dir.x < 0 and velocity.length() > 0:
		return "slime_walk_left"
	elif current_dir.y > 0 and velocity.length() > 0:
		return "slime_walk_down"
	elif current_dir.y < 0 and velocity.length() > 0:
		return "slime_walk_up"

func play_idle():
	if current_dir.x > 0:
		return "slime_idle_right"
	elif current_dir.x < 0 and velocity.length() > 0:
		return "slime_idle_left"
	elif current_dir.y > 0 and velocity.length() > 0:
		return "slime_idle_down"
	elif current_dir.y < 0 and velocity.length() > 0:
		return "slime_idle_up"

I would recommend to look up state-machines on youtube, they will help you write much simpler code and are good the bigger your games get.

Also a little more complex, but worth a try are animationtrees. They are very helpful with animations in multiple directions.

Heres a tutorial-playlist that helped me a lot in the past. Note that its in Godot 3 and some things have changed:
https://www.youtube.com/playlist?list=PL9FzW-m48fn2SlrW0KoLT4n5egNdX-W9a