Setting global position directly offsets the scene from where it should be

Godot Version

v4.6.2.stable.mono.official [71f334935]

Question

I need help with what appears to be a bug.

I am simply trying to instantiate a new scene at the parent’s Node2D location

If I use

using Godot;
using System;

public partial class TestSpawner : Node2D
{
	[Export] PackedScene spawnScene;
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		Node2D newObject = spawnScene.Instantiate<Node2D>();	
        CallDeferred(MethodName.AddChild, newObject);		
	}
}

This works as expected. As soon as I add in newObject.GlobalPosition = GlobalPosition; things start to go wonky.

using Godot;
using System;

public partial class TestSpawner : Node2D
{
	[Export] PackedScene spawnScene;
	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		Node2D newObject = spawnScene.Instantiate<Node2D>();
		newObject.GlobalPosition = GlobalPosition;
        CallDeferred(MethodName.AddChild, newObject);		
	}
}

I expect this to do the exact same thing is plainly instantiating, but it doesn’t appear so.

If the spawner is at 0,0 it works great, but at soon as I move the Node2D somewhere else there is this massive offset from where the Node2D actually is.

The reason I am wondering is I have made a spawner class that is more complex than simply placing it at the Node2D position. It looks at an Area2D’s CollisionShape2D and randomly spawns in entities within the given rectangle. The rectangle is scaled and rotated to fit the desired area.

The following code does this:

using Godot;
using System;

public partial class RunnerSpawner : Node2D
{
    [Export] PackedScene runnerScene;
    [Export] CollisionShape2D shape;
    [Export] uint amountToSpawn;
    Node2D newObject;
    float width, height;
    Vector2 localPoint, globalSpawnPosition;
    public override void _Ready()
    {
        // Set width and height based off of the collision shape's size
        width = shape.Shape.GetRect().Size.X;
        height = shape.Shape.GetRect().Size.Y;
        // Spawn in runners
        for (int i = 0; i < amountToSpawn; i++)
        {
            SpawnInArea(i);
        }
    }
    public void SpawnInArea(int number)
    {
        // Create a new runner instance
        newObject = runnerScene.Instantiate<Node2D>();
        newObject.Name = "Runner " + number;
        // Generate random local point within the collison shape
        localPoint = new Vector2(RNG.RandomFloatRange(-width / 2, width / 2), RNG.RandomFloatRange(-height / 2, height / 2));
        // Rotate the point to match the collison shape's tilt and move to it's location
        globalSpawnPosition = shape.GlobalTransform * localPoint;
        // Set the position and add it to the scene
        newObject.GlobalPosition = globalSpawnPosition;
        CallDeferred(MethodName.AddChild, newObject);
    }
}

Once again if it is at 0,0 everything works perfectly, but as soon as I move the Node2D away from there I end up with this wonky offset.

Any advice you can give me would be of great help.

P.S. I am only on day 3 of this project so bare with me

Before a child is added to the scenetree it cannot have a global position. You should be getting a soft error from the engine telling you as such. This is because parent transforms will ultimately dictate its final position and it isnt in the tree yet to do the inverse transforms for global positioning.

Try updating the global position after add child (a second deferred call). Or use to_local(global_position) and set the local position of the new child before the add.

If i think about it, the local position of the parants global position will always be zero.

Oh my god I forgot about that. There is unfortunately no soft error coming up. Adding CallDeffered to set the position definitely fix it.

Thank you so much, you were super helpful.