Making a slime bite me without errors (enemy attacks)

4.3 Godot Version

Hey there,

I want enemies in my game to always follow the player and attack him. My hierarchy is

CharacterBody2D for the enemy

  • collisionshape2d
  • animatedsprite2d
  • Attackarea (area2d)
    – collisionshape2d
    -timer

What im trying to do is enemy always run to player. When player enters attackarea they play the attack animation and then deals damage to player. After a quick delay the enemy attacks again.

Whatever i try, the enemy wont enter the player area by itself.
If the player moves into the enemy area, the animation plays but damage is never done.

Can you point me out where the issue is? Im new. Thank you


extends CharacterBody2D

@export var speed: int = 50       # Movement speed of the slime
@export var damage: int = 10      # Damage dealt by the slime
@export var attack_delay: float = 1.0  # Time between attacks in seconds
@export var attack_range: float = 50.0  # Range at which the slime can attack

var player = null  # Reference to the player
var can_attack = true  # Controls whether the slime can attack

@onready var attack_timer = Timer.new()
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D  # Reference to the sprite node
@onready var attack_area: Area2D = $AttackArea  # Reference to the AttackArea node (Area2D)

func _ready():
	add_child(attack_timer)
	attack_timer.wait_time = attack_delay
	attack_timer.one_shot = true
	attack_timer.timeout.connect(Callable(self, "_on_attack_timer_timeout"))  # Correct connect()

	# Ensure player is found in the "Player" group
	var players = get_tree().get_nodes_in_group("Player")
	if players.size() > 0:
		player = players[0]

	# Set initial animation
	if sprite:
		sprite.play("idle")  # Play idle animation by default

	# Connect the signal for collision detection from the attack Area2D
	if attack_area:
		attack_area.connect("body_entered", Callable(self, "_on_attack_area_entered"))  # Correct connection
	else:
		print("Error: AttackArea node not found!")

	# Connect animation finished signal once during initialization
	if sprite:
		sprite.connect("animation_finished", Callable(self, "_on_attack_animation_finished"))  # Correct connection

# Slime movement and animation handling
func _physics_process(_delta):
	if player and is_instance_valid(player):
		var direction = (player.global_position - global_position).normalized()

		# Check the distance to the player and stop when too close
		var distance_to_player = global_position.distance_to(player.global_position)

		# If too close, stop the movement or slow it down
		if distance_to_player > attack_range:
			velocity = direction * speed
		else:
			velocity = Vector2.ZERO  # Stop moving if the slime is within attack range

		move_and_slide()

		# Update the animation based on whether the slime is attacking or idle
		if sprite:
			if not can_attack:
				sprite.play("attack")  # Attack animation is played while attacking
			else:
				sprite.play("idle")  # Idle animation when not attacking

# This function is triggered when the player enters the attack area
func _on_attack_area_entered(body):
	print("Player entered attack area")  # Debugging
	if body.is_in_group("Player") and can_attack:
		# Trigger attack animation first, then apply damage
		if sprite:
			sprite.play("attack")
			print("Attack animation started")  # Debugging
		can_attack = false  # Prevent further attacks until the timer expires
		attack_timer.start()  # Start the attack cooldown timer

# After the attack animation finishes, damage is applied
func _on_attack_animation_finished(anim_name):
	print("Animation finished: ", anim_name)  # Debugging
	if anim_name == "attack":
		# Only apply damage after the attack animation has finished
		if player and player.is_instance_valid():
			player.take_damage(damage, (player.global_position - global_position).normalized())  # Apply damage to player
			print("Damage applied to player")  # Debugging

		# Reset attack animation logic
		sprite.play("idle")

# Timer handling to allow the slime to attack again after a delay
func _on_attack_timer_timeout():
	print("Attack timer finished")  # Debugging
	can_attack = true  # Reactivate attacking after the attack delay
	if sprite:
		sprite.play("idle")  # Return to idle animation after attack delay

First off, you have a timer in the scene and then you create another in code, remove one of them, if you dont use them both.

This seems incorrect, shouldn’t your sprite play the opposite animation?
Also, why do you constantly check if sprite exists. You don’t need that, that just gets in the way. Same for the other onready and player variables.

Start with the no damage, if player is in the hitbox:
Does this output anything? print("Animation finished: ", anim_name)
Same for the following: print("Damage applied to player")

If they both dont print, the _on_attack_animation_finished method doesn’t get called, so find out why that is. If only the first one prints, then your animation name is wrong ( if anim_name == "attack": )

As for the slime not going to the player, what did you set your attack_range to in the editor. Is that also at 50.0, because that seems way too much.
Try to print the distance to the player, and then see if the attack_range needs to be cut down a bit