Hitboxes work on manually placed enemies but not instantiated

Godot Version

4.6.2

Question

My hitboxes work when the player targets an editor placed enemy, but does not work when targeting an enemy that has been instantiated. It seems to be a signal issue as the debug print inside enemy script signal triggers on editor placed enemies

enemy.gd:

extends Node2D

@export var stats_component: StatsComponent
@onready var hurtbox_component: HurtboxComponent = $HurtboxComponent as HurtboxComponent

func _ready() -> void:
	hurtbox_component.area_entered.connect(_on_hurtbox_entered)
	
	hurtbox_component.hurt.connect(func(hitbox_component: HitboxComponent):
		stats_component.health -= hitbox_component.damage
		print("!!")
		)
	EventBus.no_health.connect(enemy_death)

func _process(delta: float) -> void:
	position = position + Vector2(0, stats_component.speed)

func enemy_death():
	EventBus.enemy_death.emit()
	queue_free()

func _on_hurtbox_entered(hitbox: HitboxComponent):
	if not hitbox is HitboxComponent: return
	hurtbox_component.hurt.emit(hitbox)

enemy_generator.gd:

extends Node2D

@export var BasicEnemyScene: PackedScene

@onready var screen_width = get_viewport().get_visible_rect().size.x

@onready var spawner_component: = $SpawnerComponent as SpawnerComponent
@onready var basic_enemy_spawn_timer: Timer = $BasicEnemySpawnTimer

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	basic_enemy_spawn_timer.timeout.connect(handle_spawn.bind(BasicEnemyScene, basic_enemy_spawn_timer))

func handle_spawn(enemy_scene: PackedScene, timer: Timer) -> void:
	spawner_component.scene = enemy_scene
	spawner_component.spawn(Vector2(randf_range(0, screen_width), -16))
	timer.start()

spawner_component.gd

class_name SpawnerComponent
extends Node2D

@export var scene: PackedScene
 
# Spawn an instance of the scene at a specific global position on a parent
func spawn(global_spawn_position: Vector2 = global_position, parent: Node = get_tree().current_scene) -> Node:
	assert(scene is PackedScene, "Error: The scene export was never set on this spawner component.")
	# Instance the scene
	var instance = scene.instantiate()
	# Add it as a child of the parent
	parent.add_child(instance)
	# Update the global position of the instance.
	# (This must be done after adding it as a child)
	instance.global_position = global_spawn_position
	# Return the instance in case we want to perform any other operations
	# on it after instancing it.
	return instance

I have tried adding the line

instance.hurtbox_component.area_entered.connect(instance._on_hurtbox_entered)

after the enemy is added as a child in the spawner component but no change

Try this:

parent.add_child(instance)
await get_tree().process_frame

That forces the script to wait until after the frame where the child is added.

Took many hours of trying absolutely anything i could think of and it now all works as intended, I think the problem ended up coming down to my signals not being connected correctly and maybe some scene hierarchy issue

Code is an absolute mess do not try to learn anything from it

enemy.gd

extends Node2D

@export var stats_component: StatsComponent
@onready var hurtbox_component: HurtboxComponent = $HurtboxComponent as HurtboxComponent

func _ready() -> void:
	var hitbox: HitboxComponent
	hurtbox_component.hurt.connect(func(hitbox_component: HitboxComponent, entity):
		entity.stats_component.health -= hitbox_component.damage
		)
	stats_component.no_health.connect(enemy_death)

func _process(delta: float) -> void:
	position = position + Vector2(0, stats_component.speed)
	pass

func enemy_death():
	EventBus.enemy_death.emit()
	queue_free()
	pass

spawner_component.gd

class_name SpawnerComponent
extends Node2D

@export var scene: PackedScene

@onready var parent: SubViewport = $"../.."

@onready var new_instance
var instance_list : Array[Node2D] = []

# Spawn an instance of the scene at a specific global position on a parent
func spawn(global_spawn_position: Vector2 = global_position, parent: Node = parent) -> Node:
	assert(scene is PackedScene, "Error: The scene export was never set on this spawner component.")
	# Instance the scene
	new_instance = scene.instantiate()
	new_instance.get_child(1).area_entered.connect(_on_hurtbox_entered.bind(new_instance))
	instance_list.append(new_instance)
	parent.add_child(new_instance)
	# Update the global position of the instance.
	new_instance.global_position = global_spawn_position
	return new_instance

func _on_hurtbox_entered(hitbox: HitboxComponent, entity):
	print(new_instance.name)
	# Ensure the area we are overlapping is a HitboxComponent
	if not hitbox is HitboxComponent: return
	new_instance.hurtbox_component.hurt.emit(hitbox, entity)