Connect vs emit_signal

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By BillRogers


I was working on a level script to respawn enemies once one dies. Each enemy would have an on_death signal which should emit to the level. Since each enemy is instantiated in the level scene, I figured I’d have to connect the signal to the level script each time an enemy was instantiated. This is what it looked like:

func spawn_enemy():
var new_enemy = enemy.instantiate()
var randomizer = randi_range(0, 3)
var start_pos = start_positions[randomizer]
new_enemy.global_position = start_pos.global_position

new_enemy.connect("on_death", enemy_death())

Something like that, I don’t remember the exact wording of the “connect” part in spawn_enemy() because I’m no longer using it. Then a function called enemy_death() would simply call spawn_enemy.

Thing is, when spawn enemy was called, an infinite loop would start as though connecting the signal was simultaneously emitting the signal. Am I wrong to think connecting the signal is only to create the link between the emitted signal and the emitter, requiring emit_signal to be called before any action happens? It seems like emit_signal and connect are doing the same thing only connect specifies what the signal affects.

I think you got this right. The connect method will connect a signal of an object to a method. And the emit_signal method will emit a signal, triggering every method connected to this signal.

I don’t have much information here but for it to do an infinite loop, there would be an emit_signal called somehow from the spawn_ennemy method.

Adab | 2023-01-13 10:21

If your enemy spawn in to a dangerous area and dies, your signal handling code will spawn a new enemy. If the enemy also spawns in a dangerous area and immediatly dies, then you have an infinite loop problem. I suggest you add break points and follow the logic to examine how creating an enemy signals an enemy death.

godot_dev_ | 2023-01-13 15:24

That’s what’s weird; I put a breakpoint at the beginning of the above code and followed it. It runs normally, initializing enemy variables and executing the ready function after the “add_child” part. Then it resumes at “var randomizer…” and, once it hits “new_enemy.connect”, it immediately executes the enemy_death() function. The code never reaches where the “enemy” scene emits its death signal. So as far as I can tell, “connect” is both connecting and emitting the signal.

I ended up having to use “await” in the above code to wait for the death signal instead of connecting it. It works but seems like it shouldn’t have to be that way.

BillRogers | 2023-01-13 23:29

:bust_in_silhouette: Reply From: Adab

I think the problem comes from the way you wrote the method parameter of the connect method. By writing enemy_death() I suppose godot thinks it is the result of the method enemy_death that should be taken as parameter and not the method itself.

Try this: new_enemy.connect("on_death", new_enemy, "enemy_death")

Edit after discussion in comments:
new_enemy.connect("on_death", Callable(self, "enemy_death")) seems to be the solution.

That still doesn’t work. For one thing, Godot 4 only accepts 2 parameters for connect, the signal and the callable. I should have specified that enemy_death() is in the level script, not the enemy. The level is to connect the “on death” signal when an enemy is spawned. The enemy dies, emits the “on death” signal, the level receives that then executes enemy_death() which just spawns a new enemy. Hopefully that clarifies rather than confuses!

I tried new_enemy.connect(new_enemy.on_death, enemy_death())

I tried connect(new_enemy.on_death, enemy_death())

I tried new_enemy.connect("on_death", enemy_death())

It seems like one of those should work since, as I understand it, “connect” will connect the signal (first parameter) to the function (second parameter). I’m very confussed!

BillRogers | 2023-01-14 04:25

I created a small test project trying to reproduce your issue and I couldn’t even start it when using new_enemy.connect("on_death", enemy_death()).

However I tried this way and it worked fine: new_enemy.connect("on_death", Callable(self, "test_method"))
Depending on where the method connected to the signal is, you might want to change self to the correct object but in your case it should be the same.

Adab | 2023-01-15 20:30

How friggin’ weird, using Callable worked! Thanks a lot. I don’t understand why that should be necessary but it’s good to know.

BillRogers | 2023-01-20 05:58