Can't get animation to stop if player character leaves area

Godot Version

4.4

Question

Trying to make interactable animated objects and have been mimicking a few different methods from YouTube tutorials.

Currently trying https://www.youtube.com/watch?v=pQINWFKc9_k , as it seems like a nice way to jump start every interactable object.

The gist is that there is an interaction component scene that is a child node of the player character. It is a 2D node with an Area2D child and the following script:

extends Node2D

@onready var interact_label: Label = $interact_label
var current_interactions := []
var can_interact := true


func _input(event: InputEvent) -> void:
	if event.is_action_pressed("e") and can_interact:
		if current_interactions:
			can_interact = false
			interact_label.hide()
			
			await current_interactions[0].interact.call()
			
			can_interact = true

func _process(_delta: float) -> void:
	if current_interactions and can_interact:
		current_interactions.sort_custom(_sort_by_nearest)
		if current_interactions[0].is_interactable:
			interact_label.text = current_interactions[0].interact_name
			interact_label.show()
	else:
		interact_label.hide()

func _sort_by_nearest(area1, area2):
	var area1_dist = global_position.distance_to(area1.global_position)
	var area2_dist = global_position.distance_to(area2.global_position)
	return area1_dist < area2_dist

func _on_interact_range_area_entered(area: Area2D) -> void:
	current_interactions.push_back(area)

func _on_interact_range_area_exited(area: Area2D) -> void:
	current_interactions.erase(area)

The second component is an Area2D with a script that you attach to your interactable scenes.

extends Area2D

@export var interact_name: String = ""
@export var is_interactable: bool = true

var interact: Callable = func():
	pass

So I make a StaticBody2D scene called tiny_stone, give it an animated sprite 2d, a collision shape, a couple of timers to trigger functions, a marker to instantiate a collectable item when it is destroyed, and the above Area2D component. And it works as expected so far with one exception.

Expected behavior: When the player character leaves the interaction Area2D before the tiny_stone is finished mining, it stops mining and resets the tiny_stone back to the default state.

Actual behavior: Once the player character initiates mining by pressing “e” in the correct area, no force on Earth will stop it from finishing.

Attempted solution: I’m trying to have the child Area2D signal tiny_stone (its parent) when the player character leaves. This solution worked in a previous iteration but not this one. The signal doesn’t seem to go through, as print() doesn’t log anything when I would expect it to.

The code for tiny_stone is currently as follows:

extends StaticBody2D

@onready var interact_object: Area2D = $interact_object
@onready var mining_time: Timer = $mining_time
@onready var destroyed_time: Timer = $destroyed_time

var state = "default"
var collectable = preload("res://Scenes/Objects/Collectable/tiny_stone_collectable.tscn")
var player_in_area = false	

func _ready() -> void:
	interact_object.interact = _on_interact

func _process(delta: float) -> void:
	if state == "default":
		$mining_time.stop()
		$AnimatedSprite2D.play("default")
		if !player_in_area:
			state = "default"

func _on_interact():
	if state == "default":
		state = "mining"
		$AnimatedSprite2D.play("mining")
		$mining_time.start()

func _on_mining_time_timeout() -> void:
	if state == "mining":
		state = "destroyed"
		$destroyed_time.start()
		$AnimatedSprite2D.play("destroyed")
		interact_object.is_interactable = false
		var collectable_instance = collectable.instantiate()
		collectable_instance.global_position = $collectable_spawner.global_position
		get_parent().add_child(collectable_instance)

func _on_destroyed_time_timeout() -> void:
	#function to delete this scene, but not the collectable it spawned
	pass # Replace with function body.


func _on_interact_object_body_exited(body: Node2D) -> void:
	if body.has_method("player"):
		player_in_area = false
		print("player left area")

func _on_interact_object_body_entered(body: Node2D) -> void:
	if body.has_method("player"):
		player_in_area = true
		print("player now in area")

Sorry for the long post, it just seems missing a tiny detail means missing the cause of the problem. I’m assuming that the method I’m mimicking wasn’t made with animated sprites in mind, and I thought about trying to fix it in the player character’s code instead, but I couldn’t think of how. I appreciate any time you can spare, thank you.

EDIT: Got the enter and leave signals to trigger, was a problem with collision layers not being selected. Still haven’t managed to get the stone to change animations on exit, but it’s a hopeful step.

Sleeping on it and typing it out helped a lot. I’ve fixed most of it.

The problems fixed so far were:

  1. The collision mask layer for exiting and entering was set to interact with the player character. Fixing this allowed the enter and exit signals to trigger.

  2. I removed the var player_in_area, it was needed in the previous version but not in this one.

  3. For the exit body function, I changed it to:

func _on_interact_object_body_exited(body: Node2D) -> void:
	if body.has_method("player"):
		state = "default"
		$AnimatedSprite2D.play("default")
		$mining_time.stop()
		print("player left area")

and that seems to work fine.

  1. The only problem left is that the player is able to mine the stone outside of interact_object which means that if I trigger mining without entering the zone, I don’t exit the zone if I then turn around and walk away, which means the animation isn’t canceled. I’m looking through all the collision layers to figure out why that is. I suspect the player character’s zone for interacting is too big, but we’ll see.

Edit: Ok, so the issue I now face is that the enter and exit body functions are using the stone’s collision2d area meant to stop the player from walking through it, instead of the interactable zone. I don’t know why this is, since the interactable zone is the one sending the enter and exit signals.

Solved the last problem by shrinking the size of the player scene’s interactable radius and increasing the size of the stone node’s interactable radius. Makes sense thinking about it, the stone node needs to be the one determining what state it is in. Going forward, I’ll make the resources have large interactable areas and keep the player area small so the resources can handle the interactions.