Home > Mobile >  Godot: Cannot get path of node as it is not in a scene tree
Godot: Cannot get path of node as it is not in a scene tree

Time:06-17

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:

enter image description here

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 Nodes 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 Nodes, 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 serialized Node 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:
  • Related