How to connect a signal to non existent child

Godot Version

4.4.1

Question

I have a main scene with a script conneced to it. I have another scene with an “enemy” that has some script as well. Main scene can create “enemies”, but it also needs to delete them after some time. My thought is to make some functions and connect them using signals. e. g. Enemy says to main node “My health is at 0, please delete me, unload me”, main scene does that, everyone is happy. The problem is that I didnt find a way to connect a signal from my enemy to the main scene, because enemy will only appear after some time in main, so I cant connect it using explorer and I didnt find a way to do that through code. Any help is appreciated.

var myEnemy = MyEnemy.instantiate() # or new()
myEnemy.myEnemyDeathSignal.connect(myDeathSignalHandlerFunction)

can I ask you a question, what is what here, where do I put this code?

You said you didn’t find a way to connect a signal in code. That is how you can do it.

It doesn’t really matter, technically, where you put this code. All you need is the object that has the signal and a function that will handle the signal. But for clarity of responsibilities, it makes the most sense that whoever wants the thing made will also destroy it. Based on your description, it would probably go in your main scene somewhere.

So, in his code the first line is the variable he has for creating the enemy, after he can use add_child(myEnemy) to create the enemy. He follows that up by connecting the signal from the enemy to the main.

but what if I have more enemies, not one, how do I differ them, how do I unload an enemy that has no health for example, it needs to understand who sent that signal?

What you are looking for is a signal manager.
It is an autoload wherein you register the signal (in your case when the enemy spawns) and the manager can connect to the appropriate listener.
If you google it you can find a few videos demonstrating the idea.

you have to add the child in the script first;

add_child(myEnemy)

Signals are handled in main scene for this example.
And, from now on every enemy is referred to as myEnemy.

I prefer the rust country so I use self right after add_child(myEnemy)

self.Death.connect(myEnemy._on_death.bind())

I am pretty sure this refers to your main scene, then the signal connect, and bracket to target enemy.

$“.”.Death.connect(myEnemy._on_death.bind())

So your main scene like this for example. I’m not sure which is best? You answer that.

Then you have to add a code with function to your enemy.

func _on_death():
print(“Ouch, I’m dying.”)

This will work if there’s something that will emit a signal in main.

Death.emit()

I think he wants the signal to go the other way.

The bird didn’t soar

It doesn’t actually say that.
But, for that you can use the opposite to add the code to the Enemy script.

self.Death.connect(get_parent()._on_death.bind())
print(Death.get_connections())

in my test chcks out.

[{ “signal”: MeshInstance3D(star.gd)::[signal]Death, “callable”: Node3D(map.gd)::_on_death, “flags”: 0 }]

This is a basic implementation of a loading screen. Good for you. First time I see that. This is how the engine checks for initialized instances in reverse.

This says that, or at the very least that that is the thought he had.

You can send the node (the enemy) as an argument when you emit the signal.

ahh. it makes sense.
for this example I actually used a different tutorial. a long time ago.

it works as well, to create the connection declared enemy to main.

myEnemy.Death.connect(self._on_death.bind())

In this case the self will target only a single instance. I forgot.

How will his main scene recieve this arg when the signal is not yet connected?

He can just connect the signal after

add_child(someNode)
someNode.on_death.connect(delete_enemy)

If I may suggest a simpler solution: You don’t need the main scene to delete your enemies.

In your enemy.gd script:

@export var health: int = 20:
	set(value):
		health = value
		if health <= 0:
			die()

func die():
	//do stuff
	queue_free()

:slightly_smiling_face:

To answer your spawning questions, you could do something like this in your main.gd script:

# Width of the spawn area on the x-axis
@export var width: float = 1.0
#Length of the spawn area (y-axis in 2D, z-axis in 3D)
@export var length: float = 1.0
# Number of starting enemies
@export var num_enemies: int = 5
# In the editor, drag and drop the enemy scene into this.
@export var enemy_scene: PackedScene



var rng = RandomNumberGenerator.new()


func _ready() -> void:
	propogate(num_enemies, enemy_scene)


func propogate(number: int, scene: PackedScene):
	for i in number:
		spawn(scene)


func spawn(packed_scene: PackedScene) -> void:
	var scene = packed_scene.instantiate()
	scene.position.x = rng.randf_range(-width, width)
	scene.position.z = rng.randf_range(-length, length)
	add_child(scene)

I added code for randomly placing items because I thought it might help. It assumes a square level. Your code will have to be more complex otherwise.

Now, let’s assume you want to track how much xp the player got for each enemy killed. That would be a great use for a signal. You could add to your player.gd:

var xp: int = 0


func connect_enemy_death(enemy: Node) -> void:
	enemy.death.connect(_on_enemy_killed)


func _on_enemy_killed(experience: int) -> void:
	xp += experience

You would update your enemy.gd with a signal:

signal death(experience: int)


@export var health: int = 20:
	set(value):
		health = value
		if health <= 0:
			die()
@export var xp_value: int = 50


func die():
	death.emit(xp_value)
	queue_free()

Then you would connect them in your main when you spawn the enemies:

func spawn(packed_scene: PackedScene) -> void:
	var scene = packed_scene.instantiate()
	scene.position.x = rng.randf_range(-width, width)
	scene.position.z = rng.randf_range(-length, length)
	add_child(scene)
	player.connect_enemy_death(scene)

Hope that helps! I tried to keep the code generic so you could spawn in other enemies, breakables, NPCs, etc.

1 Like

The enemy deletes only itself.
with

func _on_death():
queue_free()

This is a basic function name. It can be anything.

func plant_flowers_and_daisies():
queue_free()

as long as it’s …

(self.plant_flowers_and_daisies().bind())

The main scene is the one who creates the child.
For example:

func ready():
add_child(myEnemy)
and with the .connect anywhere in the same function and after the instance.
It’s works as long as it happens t the same time. In chronological order.

I think I checked this already with the same intention.
there is no easy way to assign myEnemy.queue_free()
I think I get problems or errors.
In any case. I don’t want to stall looks like a beginner question. At least individual medium.

1 Like

at least he missed the point of the intermediate tutorials.

because you can as well put arguments in accepted functions in the ready field for rating that function as during initializations.

I mean creating callable functions.
Watching tutorials.

You send the argument when you emit the signal.

If the death handler is not connected by the time the enemy dies, it sounds like there’d be a serious flaw in design. In any normal circumstance, this is not even worth a question, because you connect signals immediately when you have the object.

I know that, you know that. However, that was OP’s question though. How to connect signals between nodes when one doesn’t yet exist.

sticked to this cause it was easier to understand and all, thanks to everyone who answered tho

1 Like