Reputation: 11
I'm new to Godot and following a tutorial on building a basic inventory system for an RPG. The tutorial walks through the process of creating a custom resource, then referencing that resource in a node by exporting a number of key variables.
In my code, I'm trying to assign a texture and some text to a texture button and a rich text label node, respectively. This is my code in my texture button node:
@tool
extends TextureButton
@onready var MyLabel = $RichTextLabel
@export var MY_ITEM: Resource:
set(newItem):
MY_ITEM = newItem
self.texture_normal = newItem.getTexture()
print(newItem.getQuantity())
MyLabel.bbcode_text = newItem.getQuantity()
func addQuantity(addedQuant: int):
MY_ITEM.addQuantity(addedQuant)
And this is my code in my custom resource:
@tool
extends Resource
class_name RPG_Items
@export var ITEM_NAME: String
@export var QUANTITY: int
@export var TEXTURE: Texture
@export var HOVER_TEXT: String
func addQuantity(addedQuant: int):
QUANTITY += addedQuant
func getTexture() -> Texture:
return TEXTURE
func getQuantity() -> int:
return QUANTITY
When I run this code, the texture is assigned properly, but the text is not, displaying the error in the title.
I've tried a number of things to troubleshoot. In no particular order, they are:
Considering I'm still pretty new to all of this, it's very possible I've overlooked something very minor, but I just can't seem to figure out what's wrong. Any and all advice welcome, thanks!
Upvotes: 1
Views: 5397
Reputation: 40295
This is similar to this question: I am getting an error "Invalid call. Nonexistent function 'set' in base 'Nil'.", except that was for Godot 3, and you are trying to set a property instead of calling a method.
The error is telling you that you are trying to set a property on "on base: 'Nil'"... So the variable on which you are trying to set does not have a reference to an object.
For example, if you get the error in the following line...
MyLabel.bbcode_text = newItem.getQuantity()
It means that MyLabel
does not reference an object. Which means the line shown below either didn't run or failed (if it failed, that would have been an additional error):
@onready var MyLabel = $RichTextLabel
In your case, it would not have run yet.
This is happening because Godot is setting the properties of the Node
during initialization, before adding the Node
to the scene tree (and thus before _ready
and @onready
run). So when your property setter runs, the @onready
variables has not been initialized yet.
To reiterate: Godot sets MY_ITEM
, before running @onready
.
By the way, you'll also need to cast to String
but that is not the issue at hand. The issue at hand is that MyLabel
is still null
when the setter is running.
We are going to address this with the following pattern:
@tool
extends TextureButton
@onready var MyLabel = $RichTextLabel
@export var MY_ITEM: Resource:
set(newItem):
if MY_ITEM == newItem:
return
MY_ITEM = newItem
if is_node_ready():
_update()
func _ready() -> void:
_update()
func _exit_tree() -> void:
request_ready()
func _update() -> void:
if MY_ITEM != null:
texture_normal = MY_ITEM.getTexture()
MyLabel.bbcode_text = MY_ITEM.getQuantity()
So, we are updating the properties of the Node
s in _update
.
Which either we call:
Node
becomes ready (in _ready
), making affective any changes to its property that happened before the Node
became ready.Node
is ready and the property changes (in the property setter).Notes:
We are using in_node_ready
to know if _ready
did already run.
And we are using request_ready
when the Node
exits the scene tree so _ready
runs again the next time the Node
enters the scene tree (this support the use case of removing the Node
from the scene tree, change its properties, and adding it again).
I have added a null
check for the situation when the property was not set. You might want to set some default text and texture for that case.
In your particular case, this pattern is only necessary for MyLabel
which becomes available when the Node
is ready... So you could set texture_normal
in the property setter as usual and it would work.
In a more complex scenario, you can have multiple update functions. It might be:
And yes, you would call them all from _ready
.
To be clear, _ready
and @onready
run when the Node
enters the scene tree (either for the first time, or after we called request_ready
) and after _ready
and @onready
has run for all the children.
Upvotes: 2