I have a problem with Godot (I'm learning it). I have a project with 3 scenes, in each there are nodes with attached scripts, shown bellow.
Main.gd
extends Node2D
var maquina
func _enter_tree():
maquina = Maquina.new()
add_child(maquina)
Maquina.gd
extends Node
class_name Maquina
func _init():
var baldosa : Baldosa = Baldosa.new()
add_child(baldosa)
baldosa.set_texture(load("res://sprites/TileIcons/bat.png"))
Baldosa.gd
extends Node2D
class_name Baldosa
var imagen : Sprite
func _init():
print(imagen) # prints Null
imagen = get_node('Sprite') # <---- THE ERROR IS HERE...?
print(imagen) # prints [Object: null]
func set_texture(paramTextura: Texture):
imagen.texture = paramTextura
When I run it, it log the following error:
ERROR
get_path: Cannot get path of node as it is not in a scene tree.
<C Error> Condition "!is_inside_tree()" is true. Returned: NodePath()
<C Source> scene/main/node.cpp:1587 @ get_path()
<Stack Trace> Baldosa.gd:8 @ _init()
Maquina.gd:5 @ _init()
Main.gd:5 @ _enter_tree()
To my understanding: Main
starts instantiating Maquina
correctly, since it's done when Main
is in the Scene tree, but when Maquina
instantiates Baldosa
it isn't on the tree, so _ready
is not called and on _init
, Sprite
doesn't exist yet.
I can't figure out how to put the node in the tree before trying to access its properties. Any help, please. :(
UPDATE
I missed to mention that Baldosa.tscn already have an Sprite
node:
CodePudding user response:
The evident problem
You say this:
I can't figure out how to put the node in the tree before trying to access its properties. Any help, please. :(
But the problem is not accessing the properties. The problem is accessing the scene tree. What I'm saying is that this line:
imagen = get_node('Sprite')
Is trying to get a child Node
from the scene tree. Not a property of the current Node
.
When you do this:
var baldosa : Baldosa = Baldosa.new()
Godot will allocate the object, initialize the variables of the object (to their default value), and run _init
.
Thus, here:
var imagen : Sprite
func _init():
print(imagen) # prints Null
imagen = get_node('Sprite') # <---- THE ERROR IS HERE...?
print(imagen) # prints [Object: null]
Your baldosa
is not yet in the scene tree (The line add_child(baldosa)
has not executed yet). And thus, it is unable to get the Node
called "Sprite"
from the scene tree.
Where to put the code?
The general advice is to put the code on _ready
(or use onready
).
With that said, I want to point out that - as you already know - you could use _enter_tree
.
The main difference is that _enter_tree
will run every time the Node
enters the scene tree (which could be multiple times, because you can remove a Node
from the scene tree with remove_child
and add it again with add_child
), but _ready
will only run once.
So if you use _enter_tree
you should either:
- Check if you have already instantiated the children. And thus, you are not making duplicates.
- Use
_exit_tree
to free them. Ensuring that the next time_enter_tree
executes they are not instantiated.
I'd use _ready
unless I don't know how the code would be used (e.g. I'm making a plugin), in which case I'd use the _enter_tree
and _exit_tree
approach.
So, using _ready
on Baldosa
we have:
var imagen : Sprite
func _ready():
imagen = get_node('Sprite')
print(imagen)
Yet, that won't work on its own without changing Maquina
too:
func _ready():
var baldosa : Baldosa = Baldosa.new()
add_child(baldosa)
baldosa.set_texture(load("res://sprites/TileIcons/bat.png"))
The hidden problem: Where is Sprite
?
When _ready
executes the Node
is already in the scene tree. But, as it turns out, it does not have this Sprite
child.
I don't see any code that adds this Sprite
child anywhere in your question.
Here I need to remind you that a script is not a scene. The name Baldosa
refers to a script, as you see here:
class_name Baldosa
And when you initialize it, like this:
var baldosa : Baldosa = Baldosa.new()
Godot will create an object of the build-in type the script ultimately inherits from (the script can say to extend a build-in type, another script, or not say at all… In which case it extends Reference
... In the case at hand you get a Node2D
), and attach the script to it.
That process does not involve figuring out in which scene you use the script (there could be one, multiple, or none), nor instantiating any scene.
Thus baldosa
will not have children. So there would not be a Sprite
child, and thus this still fails:
imagen = get_node('Sprite') # <---- THE ERROR IS HERE...?
Instantiate a scene (a.k.a. Where children come from)
The solution is instantiate a scene, not a script. Something like this:
extends Node
class_name Maquina
const Baldosa_Scene := preload("res://path/to/scene/baldosa.tscn")
func _ready():
var baldosa : Baldosa = Baldosa_Scene.instance()
add_child(baldosa)
baldosa.set_texture(load("res://sprites/TileIcons/bat.png"))
Replace "res://path/to/scene/baldosa.tscn"
with the path to the scene you want to use (which has the Baldosa
script on the root, and a child Sprite
). If you don't have it, then go ahead and create said scene.
And with that the code on Baldosa
should work.
Just in case, let me tell you: Godot does not have a prefab/scene distinction. They are all scenes. You can instantiate scenes inside of scenes. There are no prefabs, this is not Unity. If you are thinking "I want a prefab here", you want a scene.
I went over the process Godot uses to instantiate a scene elsewhere. Including when _init
, _enter_tree
and _ready
execute. You can look there for more details.
If you REALLY don't want to use a scene…
And just to be thorough, if you really don't want to use a scene, you can always have the Baldosa
script create the child from code:
imagen = Sprite.new()
add_child(imagen)
I notice, this is similar to what you have been doing. So you probably just set up the scene the way you want and not have no code at all.
On the flip side - if you want to do it all from code - you could build it all from the root, I mean Main
:
extends Node2D
var maquina:Node
var baldosa:Node2D
var imagen:Sprite
func _ready() -> void:
maquina = Node.new()
add_child(maquina)
baldosa = Node2D.new()
maquina.add_child(baldosa)
imagen = Sprite.new()
imagen.texture = load("res://sprites/TileIcons/bat.png")
baldosa.add_child(imagen)
Similarly you could add Node
s on demand from code.
What is the ultimate goal here? I can think of a few reasons why you might want to do things from code:
- Good old curiosity of how to do it. Hopefully this answer satisfies that.
- You want to instance these
Node
s, but you only when or know how many in runtime. For this use case, you would have code that instantiates a scene, as shown previously. Consider a scene a serializedNode
with properties and children. - You are trying to reduce the load time of your scene. For this use case, aside from using code to instantiate scenes, you could:
- Use
InstancePlaceholder
s - Use Background loading.
- Use