I'm trying to reparent a node using what seems to be the only way - removing it and then adding it to a new parent - but have been having problems. The node seems to be removed from the scene and just remains that way.

My function looks like this:

func turn_left():
	var old_position = self.global_position
	var new_parent = get_node("../../../Path2D/PathFollow2D")
	get_parent().call_deferred("remove_child", self)
	new_parent.call_deferred("add_child", self)
	self.global_position = old_position

Maybe there is a better way to reparent nodes that I'm unaware of? My aim is to freely move nodes from one PathFollow2D to another once it enters an Area2D.

Thanks

Fixed formatting.

Maybe the issue is the deferred calls? Not sure to be honest.

You probably need to set the node's owner. I found this to be an issue when I was making code for re-parenting nodes in the Godot editor.

This is the function I ended up using (C++ source):

void CanvasItemEditor::_set_owner_for_node_and_children(Node *node, Node *owner) {
	node->set_owner(owner);
	for (int i = 0; i < node->get_child_count(); i++) {
		_set_owner_for_node_and_children(node->get_child(i), owner);
	}
}

Which I call like this in the code (C++ source):

undo_redo->add_do_method(this, "_set_owner_for_node_and_children", new_bone, editor_root);

That fixed the issue for me with the nodes not appearing in the Godot editor or in the scene tree.

Haha, thanks. That seems to have fixed it :) (to Megalomaniak)

Edit: Actually, while it works it crashes and I get a repeating error:

'Can't change this state while flushing queries. User call_deferred or set_deferred to change monitoring state instead.'

Deferred calls are run on the next frame IIRC so if you have to call deferred then you also have to make sure that it's removed from a parent without it getting deleted from memory too, though this might only be an issue with 4.0?

Maybe set the monitoring state of the Area2D to false before reparenting and then set it back to true after reparenting? That said, I'm not sure why changing the parent would cause issues with the monitoring state.

I've done some tweaking but it still gets removed and doesn't come back.

I get these error messages:

E 0:00:05.339 remove_child: Cannot remove child node Car2 as it is not a child of this node. <C++ Error> Condition "idx == -1" is true. <C++ Source> scene/main/node.cpp:1256 @ remove_child()

E 0:00:05.346 add_child: Can't add child 'Car2' to 'LeftTurnN1F', already has a parent 'LeftTurnN1F'. <C++ Error> Condition "p_child->data.parent" is true. <C++ Source> scene/main/node.cpp:1176 @ add_child()

My code looks like this:

func _on_LeftTurnN1A_body_entered(body):
	if body.is_in_group("Vehicle"):
		if body.following_player == 0:
			if not body.path == $LeftTurnN1/LeftTurnN1F:
				body.path.call_deferred("remove_child", body)
				$LeftTurnN1/LeftTurnN1F.call_deferred("add_child", body)
	pass

Hmm, strange. I wonder if the order of the deferred functions is wrong or something. That might explain the errors.

I don't know if this would work, but maybe try making a helper function that performs both operations and call it deferred? Something like this:

func reparent_node(node, new_parent):
	# (optional) a quick check:
	if (node.get_parent() == new_parent):
		print ("Node already parented to passed new_parent!")
		return
	
	node.get_parent().remove_child(node)
	new_parent.add_child(node)

# then in the function:
call_deferred("reparent_node", body, $LeftTurnN1/LeftTurnN1F)

Then, in theory, the order would be preserved, which if that is causing the issue, then it should work.

Unfortunately, it still just disappears and the text prints:

Node already parented to passed new_parent! Node already parented to passed new_parent! Node already parented to passed new_parent!

Hmm, so its already parented to the node you are trying to change it to?

Thinking about it, maybe when it gets removed and added as a child of another node, it triggers the collision detection again? Either way, it’s strange that it thinks the new parent and the existing parent are the same. I would think they would be different.

As a test/thought, what happens if you do not remove the child from the scene but still add it as a child of a different node? I doubt it will change the child, but I’m curious if the node will still be in the scene or not. It should, since it wasn’t removed, but it may not hurt to double check to make sure the remove_child is not causing the node to be removed.

As mentioned in this Q&A post, one thing you could try is to cache/save the nodes you want to reparent, and then in the next _process call, perform the reparenting. That may also you to skip using call_deferred and may help fix the issue. The Q&A post also has code for reparenting nodes, but it looks basically like what you’ve already tried, so...

Another thing that may work, is instead of reparenting the node you duplicate it, parent it to the new parent, and destroy the original, as described briefly here. The only kicker is that duplicated nodes may not correctly carry script variables along with them, in which case you may need to use something like described in this post in addition to the duplication (I think. I haven’t tried runtime duplication in Godot).

Might your parenting function get recalled again on next frames? might explain why you are getting multiple of those errors and why the node is already parented to the new parent.

er, I see Twisted is already saying the same thing.

edit: I'd add a couple of extra checks in and make sure that the new_parent variable gets set to Null after parenting reassignment so that one of the checks won't let the reparenting happen if the new_parent == Null or such.

If I don't remove the child, it stays in the scene as expected.

The link you posted doesn't mention caching, just call_deferred, unless I missed something?

I'll look into duplicating and hopefully try to make sense of it... thanks for the help :)

Also, the if new parent == null didn't work unfortunately...

Ok so there's another error if this might help to discover the problem? It appeared after I removed the parent node from autoload.

E 0:00:04.101 emit_signal: Error calling method from signal 'area_entered': 'Area2D(LeftTurnN1A.gd)::_on_LeftTurnN1A_area_entered': Method not found.. <C++ Source> core/object.cpp:1228 @ emit_signal()

@Freeman_Reigns said: If I don't remove the child, it stays in the scene as expected.

Okay, so at least we know it's not something else that is removing the child.

The link you posted doesn't mention caching, just call_deferred, unless I missed something?

Looking at it again, it doesn't directly mention caching and is just a single sentence in the only answer: "Or maybe it's better to just add the node to a var or array and do it in next _process."

This is super rough, but something like this is kinda what I was thinking of in terms of caching:

var reparent_array = []

func execute_reparent_array():
	for reparent_data in reparent_array:
		reparent_data[0].get_parent().remove_child(reparent_data[0])
		reparent_data[1].add_child(reparent_data[0])
	reparent_array.clear()

func add_to_reparent_array(node, new_parent):
	reparent_array.append([node, new_parent])

func _process(_delta):
	execute_reparent_array()

I have no idea if that'd work though.

Ok so there's another error if this might help to discover the problem? It appeared after I removed the parent node from autoload. E 0:00:04.101 emit_signal: Error calling method from signal 'area_entered': 'Area2D(LeftTurnN1A.gd)::_on_LeftTurnN1A_area_entered': Method not found.. <C++ Source> core/object.cpp:1228 @ emit_signal()

Looks like the signal connections are not adjusted when reparenting, so the area_enter signal doesn't know which method to call after it's parent has changed, or at least that's what I am gathering from the error.

The caching doesn't seem to work, unfortunately. I may just have to use areas to turn the car at certain points instead of having it follow paths. Thank you for all of the help as always, regardless.

What happens if you don't use deferred? I've tried just removing and adding objects immediately and it seems to work fine.

Just tried and it crashes with this error:

E 0:00:01.279 emit_signal: Error calling method from signal 'area_entered': 'Area2D(LeftTurnN1A.gd)::_on_LeftTurnN1A_area_entered': Method not found.. <C++ Source> core/object.cpp:1228 @ emit_signal()

My code:

func _on_LeftTurnN1A_body_entered(body): body.get_parent().remove_child(body) $LeftTurnN1/LeftTurnN1F.add_child(body) pass

I'm not familiar with PathFollow, but I put together a demo without using parenting. Maybe it will give you some ideas.