Add_sibling not working when used in a scene that is added to another scene

Godot Version

v4.2.2.stable.official [15073afe3]

Question

I made an enemy that spawns several others to act as independent turrets that attaches to it using this code in the enemy’s ready() function:

for n in hardpoints:
	print_debug("Spawn Hardpoint "+str(n))
	var spawn
	if hardpoint_types.get(n) == "Spawner":
		spawn = load("res://SpawnerTurret.tscn")
	elif hardpoint_types.get(n) == "Beam":
		spawn = load("res://BeamTurret.tscn")
	elif hardpoint_types.get(n) == "Basic":
		spawn = load("res://BasicTurret.tscn")
	elif hardpoint_types.get(n) == "Flak":
		spawn = load("res://FlakTurret.tscn")
	var ID = get_node("Hardpoint"+str(n))
	var mob = spawn.instantiate()
	if hardpoint_pos.get(n) == "Above":
		mob.z_index = z_index + 5
	var dbID = str(SelfID)+str(n)
	mob.parent = get_node(self.get_path())
	mob.ID = dbID
	mob.ishardpoint = true
	add_sibling(mob)

And for the most part this works fine. When the game spawns the enemy via add_child(), the turrets spawn alongside it as intended. However, when I place the enemy as a scene/node inside the game scene (not spawning it via code), the turrets don’t spawn at all despite the code running as it should (“Spawn Hardpoint 1” and so on shows in the output, but a print_debug in the turret’s ready() function doesn’t print anything in the output). When I change “add_sibling” to “add_child” in the above code however, the placed enemy spawns the turrets as intended, though it has other issues such as placement and orientation.

Can someone explain why “add_sibling” doesn’t work here, and how I can get it to work? Thanks!

this construction looks badly, why not just mob.parent=self

why not try to get_parent().add_child(mob) if addition of sibling doesn’t not works?

You’re right, mob.parent=self is better, thanks! But get_parent().add_child(mob) doesn’t work either, and as far as I know, add_sibling is just a shorthand for that.

not exactly. Sometimes you may get error about business of scene tree that recomends to call_deferred. <-You may try to add_sibling.call_deferred(mob)

1 Like

Adding call_deferred worked, thank you! Could you explain why it was needed when spawning the enemy via code worked just fine before? I don’t think I saw any errors come up when I was using just “add_sibling”.

because of add_sibling() I suppose. Its better to use add_child() because of scene tree nodes are being added in main thread and if something inside of child’s script deferred to call, add_sibling() may desync with main thread and produce error of even not work at all.

1 Like

I tried to figure this out in a small test based on yours, but it looks like you’ve both beaten me to a solution already!

Even so. I think this is to do with the order in which _ready() is called for Nodes in the tree.

When a scene starts, Nodes are added to the tree starting at the root and going down along each “branch” (and _enter_tree() is called). Once everything is added, _ready() is called in reverse order. The “leaves” of the branch will be ready first, and the scene root will be ready last.

It looks like, for whatever reason, children can’t be added to a Node in between that Node’s _enter_tree() and _ready() calls.

When you add a sibling, it’s the same as adding a child to the parent. And since the parent hasn’t called _ready() yet, it can’t have any new children.

The docs for Object.call_deferred (Object — Godot Engine (stable) documentation in English) say that code called using call_deferred won’t run until some kind of idle time. I’m not sure what exactly this means but I guess by the time that happens the tree is fully ready.

The solution I came up with was a little different. I’ll post it in case it helps someone in future.

func _ready() -> void:
    (create the new sibling node as you have done and then...)
    # get a reference to the parent
    var parent := get_parent();
    # connect to its ready signal, and add the child to the tree there
    parent.ready.connect(func():
      parent.add_child(mob);
    );

Seemed to work for me.

2 Likes

call_deferred called when every task is ready, not even scene tree, but all tasks and before current frame was finalized. call_deferred() if called from subthread helps to join its result with main thread, but it a theme for another discussion)

2 Likes

So it’s a backend issue of the order in which functions are called, and what can be done at which time. I admit this is still a bit over my head, but I’ll try to keep it in mind. Thank you both!

1 Like