Dimitri Halkidis
Dimitri Halkidis

Reputation: 11

Godot 4 error - Invalid set index 'text' (on base: 'Nil') with value of type 'int'

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

Answers (1)

Theraot
Theraot

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 Nodes in _update.

Which either we call:

  • When the Node becomes ready (in _ready), making affective any changes to its property that happened before the Node became ready.
  • When the 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:

    • A single update function that all properties use
    • An update function per property (although that is rarely necessary)
    • Often you will find that it makes sense to have multiple groups of related properties each using a different update function
    • And sometimes a property needs to call multiple update functions.

    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

Related Questions