Child marker of scene returns (0,0,0) for global position, even after parent is moved.

Godot Version

4.6.3

Question

Fair warning: I’m pretty new to Godot, so there’s a non-zero chance I’ve messed up something basic.
I’m trying to attached two tiles (instantiated scenes) together by aligning their markers.
To do this, one of the steps is to calculate the offset from that marker to the centre of the scene.
However, I’m running into issues with this, because the marker is always reporting it’s position as 0,0,0, even if I move the parent scene.

func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	await get_tree().process_frame # Here just in case it matters. I'll remove it if i can and it still works.
	var marker: Marker3D = new_tile.get_tree().get_first_node_in_group("AttachMarker")
	var marker_offset = marker.global_position - new_tile.global_position
	print("START")
	print(marker.global_position)
	print(new_tile.global_position)
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset

Outputs the following:
START
(0.0, -0.0, 0.0)
(0.0, 0.0, 0.0)

Modifying the code to move the tile first:

func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	await get_tree().process_frame # Here just in case it matters. I'll remove it if i can and it still works.
	new_tile.global_position += Vector3.UP*50*PI
	var marker: Marker3D = new_tile.get_tree().get_first_node_in_group("AttachMarker")
	var marker_offset = marker.global_position - new_tile.global_position
	print("START")
	print(marker.global_position)
	print(new_tile.global_position)
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset

Has no effect on the reported marker position
Output:
START
(0.0, -0.0, 0.0)
(0.0, 157.0796, 0.0)

This tile is an inherited scene, inheriting from a blender import. These markers were attached at attachment points.

The markers have visibly different positions from the root node in the 3d viewer.

I’m genuinely stumped at this one. Here’s a larger snapshot of code, if it helps.
level() is called in _ready(), and GameState is a global variable bank.

	
func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	await get_tree().process_frame # Here just in case it matters. I'll remove it if i can and it still works.
	var marker: Marker3D = new_tile.get_tree().get_first_node_in_group("AttachMarker")
	var marker_offset = marker.global_position - new_tile.global_position
	print("START")
	print(marker.global_position)
	print(new_tile.global_position)
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset
	
	
# To return bool and number when fully implemented, not yet complete.
func level():
	if is_multiplayer_authority():
		GameState.reset_levelstate.rpc()
	GameState.reset_generationstate()
	var generate_tile = func() -> Dictionary:
		var new_tile := TEMP.instantiate()
		#new_tile.top_level = true
		new_tile.scale = Vector3.ONE * (1.0/3.0)
		add_child(new_tile)
		
		var tree := new_tile.get_tree()
		var nodes := tree.get_nodes_in_group("Nodes")
		var ground_spawns := tree.get_nodes_in_group("GroundSpawns")
		var air_spawns := tree.get_nodes_in_group("AirSpawns")
		
		var chosen_node_id := randi() % nodes.size()
		var exit_nodes = nodes.duplicate()
		exit_nodes.remove_at(chosen_node_id)
		var chosen_exit_node_id := randi() % exit_nodes.size()
		
		var chosen_node: Marker3D = nodes[chosen_node_id]
		var chosen_exit_node: Marker3D = exit_nodes[chosen_exit_node_id]
		assert(chosen_node != chosen_exit_node, "Node and Exit node are the same!")
		
		# Label marker, for attaching.
		chosen_node.add_to_group("AttachMarker")
		
		# Flip exit marker, so it can attach to other pieces properly.
		chosen_exit_node.rotate_object_local(Vector3.UP, PI)
		
		var anchor_pos = GameState.generationState["AnchorPos"]
		var anchor_dir = GameState.generationState["AnchorDir"]
		chosen_node.global_position = anchor_pos
		chosen_node.look_at(global_position + anchor_dir)
		
		if GameState.generationState["AnchorMarker"]:
			attach_tile(new_tile, GameState.generationState["AnchorMarker"])
		
		GameState.generationState["AnchorPos"] = chosen_exit_node.position
		GameState.generationState["AnchorDir"] = -chosen_exit_node.global_transform.basis.z
		GameState.generationState["AnchorMarker"] = chosen_exit_node
		
		return {
			"tile": new_tile,
			"exit_pos": chosen_exit_node.position,
			"exit_dir": -chosen_exit_node.global_transform.basis.z
		}
		
	for i in range(2):
		var tile_data = await generate_tile.call()
		

One of the prints makes it seem you expect the marker position to be changing, but you never do anything with marker position. You just update the offset.

Also, it looks like youuse the new tiles position to calculate the offset before you have set the new tiles position?

The marker position and offset are maybe used in a way that could cause them to negate eachother. If you have a marker (25,25,25) and first node in group (0,0,0) and then subtract the marker the offset will be -25. If the new marker is 25 + offset you will end up with 0.

It seems a bit sketchy to use a get_first_node_in_group like this. I guess you only have one node in the group? Otherwise i would have a closer look at the group. For node in group print node just to make sure it contains what you expect.

Try checking a few of these things one at a time. Difficult to say what is wrong just by reading.

In order here:

I don’t expect the marker position to be changing, I expect it to already be changed, since it’s offset from the root node of the scene (so I expect the global positions to be different).
I did try changing the local position of the marker though (within the inherited scene), and that actually did change the print to what i set the transform position to! not sure why global_position would be returning a local position though…

I am calculating position offset before setting the position of the tile. I want to find the difference in position from the root node of the scene to a marker contained within that scene. This value shouldn’t change if the tile is translated positionally (although it is, since the marker isn’t giving me expected global position values right now, but that’s not relevant here).

I’m not sure what you mean by this? I do intend to negate the offset of the marker to the root (which is why I’m calculating it in the first place, so I can move the tile to the correct place).

I know for a fact there’s only one node in that group, it’s the node picked by chosen_node.add_to_group("AttachMarker").

How do you know that? You call generate_tile twice, each time adding a randomly picked node to the group. Is your printout from the first or from the second call? Print all nodes in the group in attach_tile.

Also get rid of all awaits.

Looks like this was half of the bug, there were two nodes in the group!
Removing the groups entirely and replacing them with another variable in the GameState now makes the marker position follow the root node (so it’s got the right node now).
However it’s still not offset from the root node.

(This fix is also not perfect, I’ll probably try and find a better way)

P.s. forgot to include the fourth print! It’s just an empty table though, since I removed the groups.

image

func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	await get_tree().process_frame
	new_tile.global_position += Vector3.UP*50*PI
	var marker: Marker3D = GameState.generationState["AttachMarker"]
	var marker_offset = marker.global_position - new_tile.global_position
	print("START")
	print(marker.global_position)
	print(new_tile.global_position)
	print(new_tile.get_tree().get_nodes_in_group("AttachMarker"))
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset
	
	
# To return bool, not yet complete.
func level():
	if is_multiplayer_authority():
		GameState.reset_levelstate.rpc()
	GameState.reset_generationstate()
	var generate_tile = func() -> Dictionary:
		var new_tile := TEMP.instantiate()
		#new_tile.top_level = true
		new_tile.scale = Vector3.ONE * (1.0/3.0)
		add_child(new_tile)
		
		var tree := new_tile.get_tree()
		var nodes := tree.get_nodes_in_group("Nodes")
		var ground_spawns := tree.get_nodes_in_group("GroundSpawns")
		var air_spawns := tree.get_nodes_in_group("AirSpawns")
		
		var chosen_node_id := randi() % nodes.size()
		var exit_nodes := nodes.duplicate()
		exit_nodes.remove_at(chosen_node_id)
		var chosen_exit_node_id := randi() % exit_nodes.size()
		
		var chosen_node: Marker3D = nodes[chosen_node_id]
		var chosen_exit_node: Marker3D = exit_nodes[chosen_exit_node_id]
		assert(chosen_node != chosen_exit_node, "Node and Exit node are the same!")
		
		# Label marker, for attaching.
		GameState.generationState["AttachMarker"] = chosen_node
		
		# Flip exit marker, so it can attach to other pieces.
		chosen_exit_node.rotate_object_local(Vector3.UP, PI)
		
		var anchor_pos = GameState.generationState["AnchorPos"]
		var anchor_dir = GameState.generationState["AnchorDir"]
		chosen_node.global_position = anchor_pos
		chosen_node.look_at(global_position + anchor_dir)
		
		if GameState.generationState["AnchorMarker"]:
			attach_tile(new_tile, GameState.generationState["AnchorMarker"])
		
		GameState.generationState["AnchorPos"] = chosen_exit_node.position
		GameState.generationState["AnchorDir"] = -chosen_exit_node.global_transform.basis.z
		GameState.generationState["AnchorMarker"] = chosen_exit_node
		
		return {
			"tile": new_tile,
			"exit_pos": chosen_exit_node.position,
			"exit_dir": -chosen_exit_node.global_transform.basis.z
		}
		
	for i in range(2):
		var tile_data = await generate_tile.call()
print(new_tile.get_path())
print(marker.get_path())
print(attach_point.get_path())

Again, get rid of all awaits. They are bug factories.

Output and code:

func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	new_tile.global_position += Vector3.UP*50*PI
	var marker: Marker3D = GameState.generationState["AttachMarker"]
	var marker_offset = marker.global_position - new_tile.global_position
	print("START")
	print(marker.global_position)
	print(new_tile.global_position)
	print(new_tile.get_tree().get_nodes_in_group("AttachMarker"))
	print(new_tile.get_path())
	print(marker.get_path())
	print(attach_point.get_path())
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset
	
	
# To return bool, not yet complete.
func level():
	if is_multiplayer_authority():
		GameState.reset_levelstate.rpc()
	GameState.reset_generationstate()
	var generate_tile = func() -> Dictionary:
		var new_tile := TEMP.instantiate()
		#new_tile.top_level = true
		new_tile.scale = Vector3.ONE * (1.0/3.0)
		add_child(new_tile)
		
		var tree := new_tile.get_tree()
		var nodes := tree.get_nodes_in_group("Nodes")
		var ground_spawns := tree.get_nodes_in_group("GroundSpawns")
		var air_spawns := tree.get_nodes_in_group("AirSpawns")
		
		var chosen_node_id := randi() % nodes.size()
		var exit_nodes := nodes.duplicate()
		exit_nodes.remove_at(chosen_node_id)
		var chosen_exit_node_id := randi() % exit_nodes.size()
		
		var chosen_node: Marker3D = nodes[chosen_node_id]
		var chosen_exit_node: Marker3D = exit_nodes[chosen_exit_node_id]
		assert(chosen_node != chosen_exit_node, "Node and Exit node are the same!")
		
		# Label marker, for attaching.
		GameState.generationState["AttachMarker"] = chosen_node
		
		# Flip exit marker, so it can attach to other pieces.
		chosen_exit_node.rotate_object_local(Vector3.UP, PI)
		
		var anchor_pos = GameState.generationState["AnchorPos"]
		var anchor_dir = GameState.generationState["AnchorDir"]
		chosen_node.global_position = anchor_pos
		chosen_node.look_at(global_position + anchor_dir)
		
		if GameState.generationState["AnchorMarker"]:
			attach_tile(new_tile, GameState.generationState["AnchorMarker"])
		
		GameState.generationState["AnchorPos"] = chosen_exit_node.position
		GameState.generationState["AnchorDir"] = -chosen_exit_node.global_transform.basis.z
		GameState.generationState["AnchorMarker"] = chosen_exit_node
		
		return {
			"tile": new_tile,
			"exit_pos": chosen_exit_node.position,
			"exit_dir": -chosen_exit_node.global_transform.basis.z
		}
		
	for i in range(2):
		var tile_data = generate_tile.call()
		

Well does this match what you expect those nodes to be?

It does, but not always, rerunning the code seems to produce different results, likely depending on the random node picker.

I’m currently trying to solve this seperately, but in the other case, yes it’s what I expect.

Now print it in a way that’s more useful for debugging:

print("new_tile = " new_tile.get_path ", global_position = ", new_tile.global_position)

ditto for other two relevant nodes.

Code and output:
Note: I changed the tiles to be named a random number while trying to solve the other bug

image


func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	new_tile.global_position += Vector3.UP*50*PI
	var marker: Marker3D = GameState.generationState["AttachMarker"]
	var marker_offset = marker.global_position - new_tile.global_position
	print("START")
	print("new_tile = ", new_tile.get_path(), ", global_position = ", new_tile.global_position)
	print("marker = ", marker.get_path(), ", global_position = ", marker.global_position)
	print("attach_point = ", attach_point.get_path(), ", global_position = ", attach_point.global_position)
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset
	
	
# To return bool, not yet complete.
func level():
	if is_multiplayer_authority():
		GameState.reset_levelstate.rpc()
	GameState.reset_generationstate()
	var generate_tile = func() -> Dictionary:
		var new_tile := TEMP.instantiate()
		#new_tile.top_level = true
		new_tile.scale = Vector3.ONE * (1.0/3.0)
		add_child(new_tile)
		
		var tree := new_tile.get_tree()
		var nodes := tree.get_nodes_in_group("Nodes")
		var ground_spawns := tree.get_nodes_in_group("GroundSpawns")
		var air_spawns := tree.get_nodes_in_group("AirSpawns")
		
		var chosen_node_id := randi() % nodes.size()
		var exit_nodes := nodes.duplicate()
		exit_nodes.remove_at(chosen_node_id)
		var chosen_exit_node_id := randi() % exit_nodes.size()
		
		var chosen_node: Marker3D = nodes[chosen_node_id]
		var chosen_exit_node: Marker3D = exit_nodes[chosen_exit_node_id]
		assert(chosen_node != chosen_exit_node, "Node and Exit node are the same!")
		new_tile.name = str(randi())
		
		# Label marker, for attaching.
		GameState.generationState["AttachMarker"] = chosen_node
		
		# Flip exit marker, so it can attach to other pieces.
		chosen_exit_node.rotate_object_local(Vector3.UP, PI)
		
		var anchor_pos = GameState.generationState["AnchorPos"]
		var anchor_dir = GameState.generationState["AnchorDir"]
		chosen_node.global_position = anchor_pos
		chosen_node.look_at(global_position + anchor_dir)
		
		if GameState.generationState["AnchorMarker"]:
			attach_tile(new_tile, GameState.generationState["AnchorMarker"])
		
		GameState.generationState["AnchorPos"] = chosen_exit_node.position
		GameState.generationState["AnchorDir"] = -chosen_exit_node.global_transform.basis.z
		GameState.generationState["AnchorMarker"] = chosen_exit_node
		
		return {
			"tile": new_tile,
			"exit_pos": chosen_exit_node.position,
			"exit_dir": -chosen_exit_node.global_transform.basis.z
		}
		
	for i in range(2):
		var tile_data = generate_tile.call()
		

Side note: figured out the other bug. More group issues! That’ll take a bit to refactor.

So you’re expecting marker to not be at its scene origin? How did you move the marker? Are you instantiating the correct iherited scene? What’s the value of TEMP. Debugging 101: Print everything. Even if you’re “sure” you know its value.

Printing TEMP comes out as (res://assets/tiles/tile_straight.tscn):<PackedScene#-9223371989543025139>, which is expected.

Marker shouldn’t be at the scene origin because it was moved from the scene origin in the scene.

It’s worth noting that the marker has no local transform, it’s parented to an object which does. I expect this to show up in global_position, though.

Umm, if you moved the marker how does it not have local transforms? Which node did you actually move in the inherited scene. Print Node_006’s global position. Is it where you expect it to be? Print, print, print… :smiley:

By that I meant that the marker shares position with Node_006, it’s transform relative to Node_006 is zero.
Althrough, the target marker could be child to either Node_006 or Node_007, depending on which one the RNG picks.
Anyway, code and output:

P.s. There are other changes in here related to fixing the other group bugs.

image


func attach_tile(new_tile: Node3D, attach_point: Marker3D) -> void:
	new_tile.global_position += Vector3.UP*50*PI
	var marker: Marker3D = GameState.generationState["AttachMarker"]
	var marker_offset = marker.global_position - new_tile.global_position
	var TEMP_Node = marker.get_parent()
	print("START")
	print("new_tile = ", new_tile.get_path(), ", global_position = ", new_tile.global_position)
	print("Node_00X (marker parent) = ", TEMP_Node.get_path(), ", global_position = ", TEMP_Node.global_position)
	print("marker = ", marker.get_path(), ", global_position = ", marker.global_position, ", position = ", marker.position)
	print("attach_point = ", attach_point.get_path(), ", global_position = ", attach_point.global_position)
	#print(marker_offset)
	#print(attach_point.global_position)
	new_tile.global_position = attach_point.global_position - marker_offset
	
	
# To return bool, not yet complete.
func level():
	print(TEMP)
	if is_multiplayer_authority():
		GameState.reset_levelstate.rpc()
	GameState.reset_generationstate()
	var generate_tile = func() -> Dictionary:
		var new_tile := TEMP.instantiate()
		#new_tile.top_level = true
		new_tile.scale = Vector3.ONE * (1.0/3.0)
		add_child(new_tile)
		
		var tree := new_tile.get_tree()
		var nodes := get_all_children_in_group(new_tile, "Nodes")
		var ground_spawns := get_all_children_in_group(new_tile, "GroundSpawns")
		var air_spawns := get_all_children_in_group(new_tile, "AirSpawns")
		
		var chosen_node_id := randi() % nodes.size()
		var exit_nodes := nodes.duplicate()
		exit_nodes.remove_at(chosen_node_id)
		var chosen_exit_node_id := randi() % exit_nodes.size()
		
		var chosen_node: Marker3D = nodes[chosen_node_id]
		var chosen_exit_node: Marker3D = exit_nodes[chosen_exit_node_id]
		assert(chosen_node != chosen_exit_node, "Node and Exit node are the same!")
		new_tile.name = str(randi())
		
		# Label marker, for attaching.
		GameState.generationState["AttachMarker"] = chosen_node
		
		# Flip exit marker, so it can attach to other pieces.
		chosen_exit_node.rotate_object_local(Vector3.UP, PI)
		
		var anchor_pos = GameState.generationState["AnchorPos"]
		var anchor_dir = GameState.generationState["AnchorDir"]
		chosen_node.global_position = anchor_pos
		chosen_node.look_at(global_position + anchor_dir)
		
		if GameState.generationState["AnchorMarker"]:
			attach_tile(new_tile, GameState.generationState["AnchorMarker"])
		
		GameState.generationState["AnchorPos"] = chosen_exit_node.position
		GameState.generationState["AnchorDir"] = -chosen_exit_node.global_transform.basis.z
		GameState.generationState["AnchorMarker"] = chosen_exit_node
		
		return {
			"tile": new_tile,
			"exit_pos": chosen_exit_node.position,
			"exit_dir": -chosen_exit_node.global_transform.basis.z
		}
		
	for i in range(2):
		var tile_data = generate_tile.call()
		

This is curious. It seems like the marker somehow give itself a local position offset to make itself appear at the scene root position? The parent node (Node_006 in this case) is in the right position, yet the marker has moved to the scene root via local transform.

I printed the marker’s position immedately after adding the tile to the scene, and it hadn’t moved yet. Something in between is making it move.

image

(...)
var generate_tile = func() -> Dictionary:
		var new_tile := TEMP.instantiate()
		#new_tile.top_level = true
		new_tile.scale = Vector3.ONE * (1.0/3.0)
		add_child(new_tile)
		var marker_temp = new_tile.find_child("Marker3D")
		print("marker = ", marker_temp.get_path(), ", global_position = ", marker_temp.global_position, ", position = ", marker_temp.position)
    (...)
		

Yeah, that something is called - a bug.

So using Node_006’s global position instead of marker’s global position results in expected positioning?

Yes it does, the tiles connect smoothly

Though i’d hoped to be able to rely on markers since adding invisible parts to blender models feels wrong in some way :stuck_out_tongue:

Not wrong whatsoever. You can just use Blender’s empty transforms.

The only place you mess with positions between instantiation and calling attach_tile is here:

chosen_node.global_position = anchor_pos

Which again could have something to do with groups disorganization. Try to comment this out and see what happens with the marker.

oh my god that was it i feel so dumb now :sob: