Signals not connecting

Godot Version

4.4.1

Question

I’m developing a simple game. When the player ends a level, it emits a signal that runs through a SignalBus.gd global autoloaded script. Other signals through the script are working well, just this one script (intermission.gd) refuses to connect to any signal. I am making the connections from within the code itself. Here is the code for SignalBus.gd and intermission.gd

Intermission.gd

extends Node2D

var counter := 0
var arrayCounter := 0
var length := 0
var toDisplay = {}
var score = "None"
const SCORE_START = "Score: "
const DEATHS_START = "Deaths: "
var deaths = "None"

func _ready() -> void:
	
	#NOT WORKING
	SignalBus.displayScore.connect(_on_display_score)


	#_on_display_score(2, 2)
	var deathString = DEATHS_START + str(deaths)
	var scoreString = SCORE_START + score
	toDisplay = [
		{
			"value": deathString,
			"object": null
		},
		{
			"value": scoreString,
			"object": null
		}
	]
	length = len(toDisplay[0]["value"])

	for dict in toDisplay:
		#add labels for everthing we need to display
		var child = Label.new()	
		dict["object"] = child
		$VBoxContainer.add_child(child)

	$DisplayTimer.start()


#IS WORKING BUT NOT WHEN SIGNAL IS EMITTED
func _on_display_score(scoreIn: int, deathsIn: int):
	print('here')
	score = str(scoreIn)
	deaths = str(deathsIn)


#makes the score and deaths show up nicely
func _on_display_timer_timeout() -> void:
	counter += 1
	if arrayCounter >= len(toDisplay):
		return
	if counter == length+1:
		length = len(toDisplay[arrayCounter]["value"])
		arrayCounter += 1
		counter = 0
		return
	else:
		var dict = toDisplay[arrayCounter]
		dict["object"].text = dict["value"].substr(0, counter)
		$DisplayTimer.start()

SignalBus.gd

extends Node

enum scoreTypes {
	COIN = 1,
	ENEMY = 1,
}


@warning_ignore_start("unused_signal")

signal dashStarted()
signal dashEnded()
signal dashesUpdated(dashCount: int)
signal dashPickedUp()
signal died()
signal isClimbing()
signal stoppedClimbing()
signal healthUpdated(currentHealth: int)
signal damage(value: int)
signal scoreChange(scoreChange: int)
signal levelEnd()
signal displayScore(score: int, deaths: int)
signal testSignal()


@warning_ignore_restore("unused_signal")

Are you by any chance queue_free() the node before it has a chance to print anything?
Can we see your emit code? Are you sure it is getting to that line?

Yes, I’m certain that the signal is being called, I’ve tested it with other signals. The player emits the signal, but the intermission.gd file refuses to connect to any signal

player.gd (i’ve omitted large parts cuz its a very large script

func _ready() -> void:
	SignalBus.levelEnd.connect(_on_level_end)


func _on_level_end(_currentLevel):
	#this works
	print_debug("here")
	SignalBus.displayScore.emit(score, deaths)

level_end.gd

extends Area2D


func _on_body_entered(_body:Node2D) -> void:
	var currentLevel = get_tree().current_scene.scene_file_path.to_int()
	SignalBus.levelEnd.emit(currentLevel)
	var intermissionPath = "res://levels/intermission.tscn"
	Transition.change_scene(intermissionPath)   

Transition.gd (since its referenced in level_end.gd)

extends CanvasLayer


func change_scene(target: String) -> void:
	$TextureRect.visible = true
	$AnimationPlayer.play("fadeToBlack")
	await $AnimationPlayer.animation_finished
	if ResourceLoader.exists(target):
		get_tree().call_deferred("change_scene_to_file", target)
	else:
		print("Error in changing scene")
	$AnimationPlayer.play_backwards("fadeToBlack")
	await $AnimationPlayer.animation_finished
	$TextureRect.visible = false
	SignalBus.deaths = 0

func _ready() -> void:
	$TextureRect.visible = false

I haven’t used Godot 4, but there are two potential issues I see. One is that you use print("here") in multiple places. Try using something like print("here1") and print("here2"). The other thing is that signal levelEnd() has no arguments, but you pass an argument when you call SignalBus.levelEnd.emit(currentLevel).

Edit: try replacing signal levelEnd() with signal levelEnd(value: int) in SignalBus.gd.

1 Like

I updated SignalBus to contain signal levelEnd(currentLevel: int) at some after the question, but no, that wasn’t the issue

I updated player to

func _on_level_end(_currentLevel):
	print_debug("here1")
	SignalBus.displayScore.emit(score, deaths)

and intermission to

func _on_display_score(scoreIn: int, deathsIn: int):
	print_debug("here2")
	score = str(scoreIn)
	deaths = str(deathsIn)

but in the console i only get

here1
   At: res://scripts/player.gd:223:_on_level_end()

Is Intermission.gd also an autoload? If so, it must come after SignalBus.gd in order to call SignalBus.displayScore.connect(_on_display_score) in _ready().

1 Like

No, intermission is not an autoload

Try adding print_debug("here3") and print_debug("here4") before and after SignalBus.displayScore.connect(_on_display_score) in intermission’s _ready() function to see if it even gets there. And also, do you get any errors when you run the project?

1 Like

No errors when I run the project, just that the score and deaths on the intermission screen are empty.

I changed intermission.gd to

func _ready() -> void:
	print_debug("here 3")
	SignalBus.displayScore.connect(_on_display_score)
	print_debug("here 4")
	print_debug("Score: ", score, " Deaths: ", deaths)

Console log is

here1
   At: res://scripts/player.gd:223:_on_level_end()
here 3
   At: res://scripts/intermission.gd:17:_ready()
here 4
   At: res://scripts/intermission.gd:19:_ready()
Score: None Deaths: None
   At: res://scripts/intermission.gd:20:_ready()

Ok, there are a few possibilities left. First, make sure that score and deaths in player.gd are of type int. You could also try calling it with some explicit values, for instance:

func _on_level_end(_currentLevel):
	print_debug("here1")
	SignalBus.displayScore.emit(1, 2)

Next, if that doesn’t work, try putting the same SignalBus.displayScore.emit(1, 2) in intermission right after you connect the signal. If that doesn’t work, then print the return value of the connect method:

var return_value = SignalBus.displayScore.connect(_on_display_score)
print(return_value)
1 Like

doing

func _on_level_end(_currentLevel):
	print_debug("here1")
	SignalBus.displayScore.emit(1, 2)

did nothing, still no change

but

func _ready() -> void:
	SignalBus.displayScore.connect(_on_display_score)
	SignalBus.displayScore.emit(1, 2)
	print_debug("Score: ", score, " Deaths: ", deaths)

Did return

Score: 1 Deaths: 2
   At: res://scripts/intermission.gd:20:_ready()

(i did change the original call in player back to using the variables instead after the first check)
And displayed correctly in the scene

That probably means that intermission is not instantiated when you call

func _on_level_end(_currentLevel):
	print_debug("here1")
	SignalBus.displayScore.emit(score, deaths)

So, either it has not been instantiated yet, or it has been removed. You may try to create a button that will call the levelEnd signal when you click it, to see if it works then.

1 Like

I’m guessing its the former, because the intermission screen stays indefinitely until the player chooses to change it. So how would I go around ensuring that the signal is only sent after the screen has been instantiated?

I am not sure how exactly that works in your project. As for the signal, try changing the order to:

func _on_body_entered(_body:Node2D) -> void:
	var currentLevel = get_tree().current_scene.scene_file_path.to_int()
	var intermissionPath = "res://levels/intermission.tscn"
	Transition.change_scene(intermissionPath)
	SignalBus.levelEnd.emit(currentLevel)

Also, you may need to change get_tree().call_deferred("change_scene_to_file", target) to get_tree().change_scene_to_file(target).

I made both the changes, but it still has the same issue.
I think I’m gonna try another way, namely make the SignalBus store both variables and make intermission access them directly. Thank you for your help though

1 Like

No problem. I would suggest creating a separate autoload to store all ‘global’ variables.

1 Like

Okay, will do.

1 Like