How to reference child nodes from a base class

Godot Version

v4.3-stable

Question

Hi, im trying to reference child nodes from a Base class called Plant:

image
image

See that i use onready to reference the nodes

My class Grape inherits from Plant
image
image

When i try to call the variables “child nodes” from base class on Grape, they get null.
I tryed to use _ready() function on base class to reference nodes too:
image

On debug i can see the “get_node()” returning null. Its like base class cannot see him own child nodes. I now that i can create the nodes programatically on base class, but i dont want to configure all their properties mannualy etc.

Theres another way to do that? Am i doing something wrong?

I am not certain, but I think that the ready function is not automatically called on a custom base class when a child is instantiated. You can use the super function to call it though.

Here is an example for a simpler situation:

extends ParentClass

func _ready():
    super() # Calls the ready function in ParentClass
    # You can then add whatever specific ready stuff you need here

Your situation with a GrandParentClass and a ParentClass is a bit more complicated and I am sorry I don’t have time to do testing to see when the ready is called on the GrandParent, but hopefully this points you in the right direction (if I am right about when the ready function is called).

You can see an example of super() in the docs here:

Well, maybe not. I was intrigued so I set up this:

Grandparent class with a reference onready to a node

class_name GrandParentClass
extends Node2D

@onready var MySprite = $Sprite2D

func _ready():
	print("this is the grand parent class")

A parent class:

class_name ParentClass
extends GrandParentClass

func _ready():
	print("this is the parent class")

Then a scene that extends the parent with a child Sprite2D node

extends ParentClass

func _ready():
	print("child is ready")
	print(MySprite)

The output was as follows:

child is ready
Sprite2D:<Sprite2D#29326574871>

So the grandparent variable was still found without the use of super.

When super() is added to the child and parent ready functions the output is:

this is the grand parent class
this is the parent class
child is ready
Sprite2D:<Sprite2D#29326574871>

So I don’t think this helps you at all. Sorry.

Edit: Looking again at your code, you have an _ready function in your Grape class, so the Plant _ready is never called. Adding super() to call the parent _ready first might solve your issue after all.

Sorry, but doesnt work.

I tryed:

#BASE CLASS
extends Node2D
class_name Plant

@onready var timer: Timer = $Timer
@onready var target_finder: TargetFinder= $TargetFinder
@onready var life: Node2D = $Life
@onready var destroyable: Destroyable = $Destroyable

func _ready() -> void:
	print("BLASE CLASS PLANT READY")
	print("Timer: ", timer)
	print("Target: ", target_finder)
	print("Life: ", life)
	print("Destroyable: ", destroyable)

Child:

#CHILD CLASS
extends Plant
class_name  Grape

func _ready() -> void:
	super()
	print("CHILD CLASS GRAPE READY")

The output:

BLASE CLASS PLANT READY
Timer: <Object#null>
Target: <Object#null>
Life: <Object#null>
Destroyable: <Object#null>
CHILD CLASS GRAPE READY

See that base class child are null yet :sob:

I simplified your examples to just one element. I set up a script called base_class.gd with the following:

extends Node2D
class_name Plant

@onready var timer: Timer = $Timer

func _ready() -> void:
	print("Timer: ", timer)

Then I created a new scene and added a single child Timer node to the scene. I then attached a script I called grape.gd with this code:

extends Plant
class_name  Grape

func _ready() -> void:
	super()
	print("CHILD CLASS GRAPE READY")

The output when I played the grape scene is the following:

Timer: Timer:<Timer#29041362199>
CHILD CLASS GRAPE READY

So it appears to work fine.

The only thing I can suggest is to check your node names and paths are correct.

If you follow what I did above with a completely new scene, does it not work as mine did? If not, I honestly do not know what the problem is, sorry.

In the game I am making at the moment my ready functions simply connect to my signal manager. The root node, my “Game” node, in it’s ready function calls an initialise() function. In that function I initialise all my children, passing shared node refrences like Player, Game etc, calling the player settings singletons to set up the player and levels if a game has been loaded etc. I did this as I was running into complications of when ready() was called as well, but by moving it into initialisation functions called when the root was ready, I averted all those problems. Perhaps something like that would help.

I must admit that when I use a base class I tend to not use the _ready() function, as it can be somewhat confusing about when it runs. Instead I again use initialisation functions to do set-up requests.

Anyway, I hope you get your problem solved. Perhaps stepping through your code with the debugger one line at a time will help you see what the program is doing and when.

The difference between your code and mine is that the my Timer is added to the base class “Plant”, not to the child class “Grape”.

image

I came to the conclusion that Godot does not have this feature: From the moment a class is inherited, the “get_node()” functions will search in the child class tree, and not in the parent tree.

I decided to add the nodes to the base class programmatically, unfortunately :face_with_diagonal_mouth:

1 Like

Yes, that is correct. Inheritance only inherits the script, not the scene structure.

My custom base classes are just stand alone scripts. I didn’t realise your timer was on your base class - sorry.

The child nodes are ready before the parent. You won’t be able to access them until the parent is ready. You can set up an init function in Grape and call it from the parent Plant._ready(). However…
I don’t understand why you are duplicating the parent variables in the child class.
For example in Grape._ready() you have:
timer = get_node("Timer")
But Grape already has access to its parent variable timer.
(Grape.timer will be null until the parent is ready)
If you need access to them prior to the parent being ready then it might be a design issue.

Your nodes don’t have the same children. Plant has “Timer”, “TargetFinder”, “Life”, and “Destroyable”, but Grape has none of those. You need to also inherit the scene or add those nodes. A inherited script does not implicitly inherit a scene.