Getting a signal twice since I introduced a global Autoload

Godot Version

4.6

Question

I’ve had my heart container HUD start to print the starting health twice since I introduced a global auto load to keep track of the player’s health between scenes. It seems to be firing the health signal twice according to my output print.

Health Component Script

extends Node

signal grant_health  (HP :int)
signal health_changed(diff: int)
signal health_depleted

@export_subgroup("Settings")
@export var max_health: int = 3 : set = set_max_health, get= get_max_health
@export var immortality : bool = false: set = set_immortality, get = get_immortality
@export var immortality_time : float 

var immortality_timer :Timer = null

@onready var health : int = max_health: set = set_health, get = get_health

#func _ready() -> void:
	#health=HealthTracker.player_HP
	#get_health()

func set_max_health( value:int)->void:
	max_health=value

func get_max_health()-> int:
	return max_health

func set_immortality(value:bool)-> void:
	immortality= value

func get_immortality() -> bool:
	return immortality

func set_temporary_immortality(_immortality_time)->void :
	if immortality_timer==null:
		immortality_timer= Timer.new()
		immortality_timer.one_shot=true
		add_child(immortality_timer)
	if immortality_timer.timeout.is_connected(set_immortality):
		immortality_timer.timeout.disconnect(set_immortality)
	
	immortality_timer.set_wait_time(_immortality_time)
	immortality_timer.timeout.connect(set_immortality.bind(false))
	immortality = true
	immortality_timer.start() 

func set_health(value:int)->void:
	if value < health and immortality:
		return
		
	var clamped_value = clampi(value, 0 , max_health)
	if clamped_value !=health:
		var difference = clamped_value - health
		health = clamped_value
		HealthTracker.player_HP=health
		health_changed.emit(difference)
		
		
		if health == 0 :
			health_depleted.emit()

func get_health()-> int:
	var HP:int = health
	grant_health.emit(HP)
	return health

I’m very confused on why its firing twice, and the HUD behaves the way I expect it to once the player gets hit, which makes it more puzzling to me, however, according to the output print , it does seem to be getting 6 to 4 signals still.

HUD Script

extends CanvasLayer
@export_subgroup("Scenes")
@export_file ("*.tscn") var heart_full : String

@export_subgroup("Nodes")
@export var health_component : HealthComponent
@export var hbox : HBoxContainer

func _ready() -> void:
	setup_hp()
	
func setup_hp():
	clean_current_representation()
	#_on_health_component_grant_health (Health_Points)

func _on_health_component_grant_health(HP: int) -> void:
		print(HP)
		draw_hearts(HP)

func _on_health_component_health_changed(_diff: int) -> void:
	clean_current_representation()

func draw_hearts(HP:int):
	for x in HP :
		var heartf = load(heart_full)
		var heart_instance = heartf.instantiate()
		hbox.add_child(heart_instance)
		#if x < HP +1:
			#break
		#if x < 4:
			#continue

func clean_current_representation():
	for child in hbox.get_children():
		child.queue_free()

and it seems to be primarily due to the introduction of the global Autoload, which by itself is quite simple, any help would be greatly appreciated.

Health Tracker Global Script

extends Node

const HEALTH_COMPONENT = preload("uid://dtkmr0o3nr2li")
var health_component=HEALTH_COMPONENT.instantiate()


var player_HP : int = health_component.max_health

Hi, where is the print statement that is printed multiple times? Is it the one in _on_health_component_grant_health?

1 Like

Yes, it is the health grant signal

Whetee did you send the signal from before you added the autoload?

Sounds like you forgot to remove it from the other script and now have double signals v

2 Likes

Post a screenshot of your remote scene tree.

1 Like

The grant_health signal gets emitted in health’s getter function get_health(), so it gets emitted every time you read the value of health. Adding a line like this:

		HealthTracker.player_HP=health

will call the getter function and thereby emit the signal one additional time, every time that line is reached.

To be honest, I don’t get the purpose of the grant_health signal. Only the setter function needs to notify the HUD.

1 Like

In your example, don’t use an autoload script if you want to update HP in the UI scene. Connect directly to the signal. Otherwise, each time you emit the signal, it will go through the autoload and then back to the scene script. Prefer a direct connection.
I used many hubs in a project and ended up removing them because they added resource usage and complexity for no real benefit.

1 Like

Thank you everyone, every post really helped me wrap my head around this.

Here is what I ended up with.

extends CanvasLayer
@export_subgroup("Scenes")
@export_file ("*.tscn") var heart_full : String

@export_subgroup("Nodes")
@export var health_component : HealthComponent
@export var hbox : HBoxContainer

func _ready() -> void:
	setup_hp()

func setup_hp():
	var SHP=HealthTracker.player_HP
	clean_current_representation()
	draw_hearts(SHP) 

func _on_health_component_health_changed(_diff: int , HP: int) -> void:
	clean_current_representation()
	print (HP)
	draw_hearts(HP)

func draw_hearts(HP:int):
	for x in HP :
		var heartf = load(heart_full)
		var heart_instance = heartf.instantiate()
		hbox.add_child(heart_instance)

func clean_current_representation():
	for child in hbox.get_children():
		child.queue_free()

I basically added the health value to my health_changed signal , and pulled my character’s starting health points “SHP” from the health tracker on ready.

1 Like