Var declared with onready works sometimes, not others - returns null

Godot Version

Godot v4.6

Question

Summary:

Need to figure out why I’m getting the Null error when one function runs but not another within the same script.

Context:

I’ve declared an onready var “slot_grid” in a script (inventory_grid.gd) attached to a PanelContainer.

In the script I set class_name to “InventoryGrid”

Within the script, I use the var in a function, specifically calling slot_grid.get_children() and slot_grid.get_child(0) - which works fine in two separate functions called within the script. This all happens

However, when I call the function from a different script (pickup.gd), I get a null value error:

E 0:00:02:435 InventoryGrid.add_item_to_slot: Cannot call method ‘get_children’ on a null value.
inventory_grid.gd:130 @ InventoryGrid.add_item_to_slot()
inventory_grid.gd:130 @ add_item_to_slot()
pickup.gd:27 @ _on_body_entered()

I’ve tried changing the way I reference the node but get the same crash either way:

var inventory = InventoryGrid.new()

@export var inventory: InventoryGrid

Here’s some screenshots:

InventoryGrid.new() only instantiates a single node, not the whole scene, and since this node is not even added to the scene tree, its _ready() and @onready won’t run at all.

Sorry, I’m confused.

Is that the behavior when using InventoryGrid.new() or @onreadyonready?

I thought once the scene has loaded, that’s it. Local and remote calls should work.

If not, what’s the fix?

Who and where loads the scene? If you want to refer to the scene you placed in the editor then what’s the point of using InventoryGrid.new()? Get the reference via the correct node path to the scene in the scene tree.

I’m really confused now -

Which script are you referring to?

Everything works in the inventory_grid.gd script EXCEPT when add_item_to_slot is called from another script.

Then, ‘add_item…’ fails.

What he’s asking is if the slot grid is in your invtentory grid because you just showed the component inside your test level, so we cannot see if the sub components actually exist.

Can u show us your inventory grid component scene hierarchy?

Sure:

Ok, can print the slot_grid and slot_grid .get_chilrdren() before the for loop by adding a str(slot_grid) to see if there are any errors in console?

Ah Ok, I read the whole thing, is there any reason why u cannot also use @onready in the pickup.gd also?

If that has failed or is inconsistent, what I suggest you do, is either put the inventory grid in the Global variables where it is 100% accessible since they load first, or just put it in the player character so it gets initialized with it, then when the player enters the area2d, it will definitely be ready.

Yes - the print call always works.

print(“add_item_to_slot”)

This works regardless of how/when ‘add_item…’ is called

I editted the post. Put grid in global variable is my best bet. Or inside the CharacterBody2D going to collide with the area2d.

I read your edit.

I think inventoryMain should be a child of TestLevel and not a child of Player.

Also, it seems odd that I would need to make the InventoryGrid global - Everything is in the same Project Scene - it should all be accessible.

I will test with a Global, but I think there should be a solution without it.

In pickup.gd you initialize inventory with InventoryGrid.new(). What’s the purpose of doing this?

Using a Global still causes the Null error (see bottom)

This is my Global.gd:

extends Node

@onready var inventory_grid: InventoryGrid = $InventoryMain/InventoryGrid

And it is set to load via Project Settings

In pickup.gd I have this:

#Original - yields Null error
#var success: bool = inventory.add_item_to_slot(pick_up, amount)
#Global attempt 
var success: bool = Global.inventory_grid.add_item_to_slot(pick_up, amount)

The Global call to ‘add_item_to_slot’ still errors:
image

The path is wrong in your global. Look into remote tree at runtime so see where the autoloaded node is actually placed and what’s the path from it to the inventory grid node, or alternatively us the absolute path.

ok -

Here’s the path at runtime:

So I guess it should be this?

@onready var inventory_grid: InventoryGrid = $Root/TestLevel/InventoryMain/InventoryGrid

That’s still a relative path. Absolute paths need to start with /

Also it’s root, not Root. Case matters.

So:

$"/root/TestLevel/InventoryMain/InventoryGrid"

WORKS!

Thank you both!

(I still think this should work without being a Global, so if anything pops into your head, please let me know. Maybe I built the Scene wrong?)

It can work without a global. You just need a correct path to get the node reference. Since global path is the same no matter to which node the script is attached, you can try to use the global path from the node you were intialy accessing the inventory.

Yes, that works!

This seems better than using a Global - thanks again!

Hmmm… interesting.

Debugging without being on a call or with access to the screen is kinda insane.

Totally forgot to ask about the path u mapped and that the Area2Ds were nested and not on the same level.

Assumed it would be correct cause your assignments from the main code were correct after checking them XD.

Another thing is global variables can be accessed without the money sign by their names so that gives a certain amount of convenience.

It seems silly to put the entire inventory grid in as a Global since it is a UI component, so another way is to put the Player Data in a global component, and then use a signal to update when a property eg. Lives or coins is changed that the Inventory Grid component will listen for and then refresh itself based on that data.