Godot Version
4.2
Question
As I understand it, this is the correct way to instantiate a scene, then add it to the tree and set it’s position:
var powOb = kapow.instantiate()
get_tree().root.add_child(powOb)
powOb.global_position = global_position
The problem is. if I have a script in the instantiated object, and I do anything inside the _ready function, then the global_position is Vector3.ZERO, ie the world origin. This is wrong. I want the position of the object to be where I place it, in this case, the same location as the object that spawns it.
You can also call the functions in this order:
var powOb = kapow.instantiate()
powOb.global_position = global_position
get_tree().root.add_child(powOb)
Doing it this way, the code works as I would like it to, with global_position being set to the correct value in the _ready function. However, every time you do this, Godot spits out an error:
E 0:00:05:0770 Projectile.gd:92 @ ProjectileUpdate(): Condition “!is_inside_tree()” is true. Returning: Transform3D()
This error sounds bad, but the actual behavior of the program is better this way.
What am I meant to do?
1 Like
This error is basically telling to you’re trying to set the node position before put him inside the tree (add as child of another node), so basically you just need to inverse the order of your code, use get_tree().root.add_child(powOb)
first and powOb.global_position = global_position
after
I’m really sorry, I am new here and I don’t want to create any conflict.
But, did you read my post in full? I went over your solution first and stated the problem that it causes.
I want to be able to read the global_position of my object in the _ready function. I have two options, one causes an error, the other does not work.
1 Like
Move the code inside _ready
function to a new function, do the add child first and set position later and after that call the function you created
This is a pretty good suggestion! I have considered doing this, as I have done similar things elsewhere in my code.
The reason I’d rather not do that here, is that this code is meant to be generic. I have all sorts of different projectiles and they might spawn all sorts of different things when they hit things. Some might have splash damage that needs to be calculated right away, and others do not. Some might spawn additional projectiles that go out in different directions like a cluster bomb.
I’d like to just be able to instantiate an object and have it run it’s own code. I really don’t think I’m doing anything unusual here.
Interesting! In 2D, both ways work. In 3D, the second way results in an error, indeed. Might be a bug? But maybe there’s a reason why this is handled differently in 3D.
Anyway, in the meantime, I guess your only option would be to work around this:
var powOb = kapow.instantiate()
powOb.g_pos = global_position
get_tree().root.add_child(powOb)
And then change your _ready
function in kapow
:
var g_pos : Vector3
func _ready() -> void:
global_position = g_pos
# ... <- your original code
Yeah, maybe. That is extremely ugly code though. I want to be able to instantiate any kind of class, and the _ready function that matters might not been in the root node of the newly instantiated scene.
This method also requires me to add this extra line of code in every place that I instantiate an object. If I forget in one place then I get a bug.
I don’t like it at all.
I’ve also noticed that some particles manage to spawn at the world origin, before the thing gets moved to it’s proper position… I don’t even know how I can work around that.
Well, you can always spawn stuff with visible = false
(or in the case of particles: emitting
) and only make it show up after it’s in the right spot.
But yeah, it’s a bit ugly and cumbersome, won’t argue that!
If nobody chimes in with a better solution, I’d advise you to open a GitHub issue.
Alright I’ve come up with a work around. I’ve made a global function:
func InstantiateAtPosition(object: PackedScene, position: Vector3) -> Node3D:
var obj = object.instantiate()
var node = Node3D.new()
get_tree().current_scene.add_child(node)
node.global_position = position
node.add_child(obj)
node.remove_child(obj)
get_tree().current_scene.add_child(obj)
obj.global_position = position
node.queue_free()
return obj
I can use this to instantiate any object I want at any position without worrying about particles appearing in the wrong place or reading the wrong position in a _ready function.
I have an alternative version here:
func InstantiateAtPosition(object: PackedScene, position: Vector3) -> Node3D:
var obj = object.instantiate()
var node = Node3D.new()
get_tree().current_scene.add_child(node)
node.global_position = position
node.add_child(obj)
return obj
Which version do you think is more sensible? The first has some fiddly back and forth, while the second creates deeper branches in the scene tree.
Do you think this is a reasonable approach?
Well, it solves your problem. So… Yes!
That being said, it’s still quite a bit of extra work to solve a (seemingly) trivial problem. So if I were you, I’d still open up an issue on GitHub in the hopes of this being avoidable altogether.
The second one is (imho) easier to understand at a glance, but if you plan on using this more often, then I’d definitely go with the second option, since the parent Node3D
becomes obsolete after the initialization.