Label returning as Null

Godot Version

Godot Engine v4.2.2.stable.official.15073afe3

Question

Why is my label returning as null? It’s properly referenced from the scene. I can’t solve this.

Here is the code:

extends Node2D

var cur_dialogue := 0
@onready var label = $NinePatchRect/Label
@export var dialogue_lines = [
	"Default Line",
]

func _ready():
	visible = false
	print(dialogue_lines[cur_dialogue])

func dialogue():
	if not is_visible():
		visible = true
	
	if cur_dialogue < dialogue_lines.size():
		if label:  # Check if 'label' is not null
			label.text = dialogue_lines[cur_dialogue]
		else:
			print("Label node not found or is null.")
		print(cur_dialogue)
		cur_dialogue += 1
	else:
		print("End of dialogue reached.")
		
	if Input.is_action_just_pressed("Advance Dialogue"):
		visible = false
		cur_dialogue = 0

and the Hierarchy:
image

Any help is greatly appreciated, I have been trying to solve this for a while.

I think you have to enclose the path in quotations like this

 $"NinePatchRect/Label"

or try a unique name or export var

1 Like

I tried out this code in a new project (Godot 4.2.2 also), and added a call to dialogue() at the end of _ready(), and it is properly setting the non-null label’s text… Can’t seem to reproduce this.

Can you try this 3-node hierarchy in a new project? Where is dialogue() being called from?

2 Likes

Okay, I was able to reproduce this, by calling dialogue() in a different node’s _ready() method, which was run prior to node’s _ready() method above (before the @onready label was set).

2 Likes

I think that doesn’t matter, and it would be bad code styling for a var to come after _ready(). It could be easier for the person to delete the line and ctrl drag it again.

For the first question, I tried it in a new product and it works. For the second, dialogue() is being called from the player’s script listed here:

extends CharacterBody2D

@export var speed := 50
@export var run_multiplier := 2
@export var dialogue_box = Node2D

@onready var myscript = load("res://ui/dialoguebox.gd").new()

@onready var all_interactions = []
@onready var interact_label = $InteractionComponents/InteractLabel

func _ready():
	update_interactions()

func _input(_event):
	var input_direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	if Input.is_action_pressed("Run"):
		velocity = input_direction * speed * run_multiplier
	else:
		velocity = input_direction * speed
	if Input.is_action_just_pressed("Advance Dialogue"):
		execute_interaction()

func _physics_process(_delta):
	move_and_slide()

func _on_interaction_area_entered(area):
	all_interactions.insert(0, area)
	update_interactions()

func _on_interaction_area_exited(area):
	all_interactions.erase(area)
	update_interactions()

func update_interactions():
	if all_interactions:
		interact_label.text = all_interactions[0].interact_label
	else:
		interact_label.text = " "

func execute_interaction():
	# Basicly, this says 'If interact_type matches "print_text", then do etc." You could add other 
	# parameters to see if interact_type matches things like "take_item" or "start_battle".
	if all_interactions:
		var cur_interaction = all_interactions[0]
		match cur_interaction.interact_type:
			"print_text" : print(cur_interaction.interact_value)
			"start_dialogue_1" : myscript.dialogue()

when you get null for a node in a variable with @onready and you call it via _ready()
you should be aware that the @onready’s var may not be added to child yet when this node is _ready. so what you can do is
to add

await get_tree().physics_frame

on the first line of _ready()

I did this, but the node still returns as null. I also tried adding it into the player’s _ready() and it still didn’t work.

What does the stack trace look like? If you set a breakpoint at the line with the “label is null” print statement, what do you see? Is there a _ready() call in it somewhere? That would indicate things aren’t all fully initialized.

you dont load script like this, it will always print null because of this

what you can do is, to actually instantiate the scene, not the script

just replace the characterbody2d code with this:

extends CharacterBody2D

@export var speed := 50
@export var run_multiplier := 2
@export var dialogue_box = Node2D

var myscript = load("res://ui/dialoguebox.tscn").instantiate()

@onready var all_interactions = []
@onready var interact_label = $InteractionComponents/InteractLabel

func _ready():
	update_interactions()
	add_child(myscript)

func _input(_event):
	var input_direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	if Input.is_action_pressed("Run"):
		velocity = input_direction * speed * run_multiplier
	else:
		velocity = input_direction * speed
	if Input.is_action_just_pressed("Advance Dialogue"):
		execute_interaction()

func _physics_process(_delta):
	move_and_slide()

func _on_interaction_area_entered(area):
	all_interactions.insert(0, area)
	update_interactions()

func _on_interaction_area_exited(area):
	all_interactions.erase(area)
	update_interactions()

func update_interactions():
	if all_interactions:
		interact_label.text = all_interactions[0].interact_label
	else:
		interact_label.text = " "

func execute_interaction():
	# Basicly, this says 'If interact_type matches "print_text", then do etc." You could add other 
	# parameters to see if interact_type matches things like "take_item" or "start_battle".
	if all_interactions:
		var cur_interaction = all_interactions[0]
		match cur_interaction.interact_type:
			"print_text" : print(cur_interaction.interact_value)
			"start_dialogue_1" : myscript.dialogue()
1 Like

it will always null because he tries to spawn just script, not the scene. because it’s a script object, it will never run @onready variable line
hence it will never find label node because DialogueBox scene is never spawned

2 Likes

I think that did it. It dosen’t return a null label node anymore. I’ll still need to tweak it a bit, but thank you for your help!

1 Like