How to reference nodes robustly?

Godot Version

Latest 4.2.2, GDScript

Question

My question is simple. How to get a reference in code to a node, and not have that reference break if the node is moved in the scene tree, and/or if the node is renamed.

The unique name identifier (%) is not useful. This depends on the node name. Using the scene reference (…/nodex/nodey) is not useful. This depends on the node position in the scene tree.

Is this possible? Does every node (or scene) added to the tree have some unique ID or something I can reference?

Because its tiring to be making a new game which obviously is not finished nor correctly ordered, and have all my code break when I move and/or rename a node.

Thank you in advance for your help.

get the node and store the result in a variable. below is an example where i rename/orphan/re-parent node B. after all that m_b_node == get_node_or_null("/root/Test/A/NewNameForNodeB") still works.

extends Node2D

@onready var m_test_node : Node2D = $"/root/Test"
@onready var m_a_node : Node = $"/root/Test/A"
@onready var m_b_node : Node = $"/root/Test/B"
func BigReport():
	print(m_test_node, ", ", m_a_node, ", ", m_b_node)

func FinalReport():
	print ("-------------------")
	print ("Final report")
	print ("m_b_node == /root/Test/A/B:", m_b_node == get_node_or_null("/root/Test/A/B"))
	print ("m_b_node == /root/Test/A/NewNameForNodeB:", m_b_node == get_node_or_null("/root/Test/A/NewNameForNodeB"))

func AfterAddChild():
	BigReport()
	FinalReport.call_deferred()


func AfterRemoveChild():
	BigReport()
	print ("add child B")
	m_a_node.add_child(m_b_node)
	AfterAddChild.call_deferred()

func _ready() -> void:
	BigReport()
	
	m_b_node.name = "NewNameForNodeB"
	BigReport()
	
	print ("remove child B")
	remove_child(m_b_node)
	
	AfterRemoveChild.call_deferred()


I mostly use groups to grab any particular node. My player character has his own group. My game controller is in the “game” group. Buttons are in multiple groups, by function (mostly assigned based on their name). No matter where I move them, they’re still in their groups.

1 Like

The simplest way to get nodes no matter where they are or they name is use groups, in the node you want to get the reference use add_to_group("group_name") and in the script you want to get the reference you can use either get_tree().get_nodes_in_group("group_name") (will return an array with all nodes inside this group) or get_tree().get_first_node_in_group("group_name") (if you have only one node in the group you can use this to get the node directly)

1 Like

godot is still new to me and i didn’t know about groups. thank you for the additional information. you just made my day!

I see my question may have been misunderstood.

I want to have a reference that doesnt break when moving or renaming the referenced node while in editor mode (during design). Not while playing the game.

So if I, while developing and not playing, move a node permanently on the scene tree or rename it. Then, I want this reference to not break and have to update it manually.

In this case you can use the @export keyword, unless your change the variable name in the script, the editor will keep track automaticly of your node

@export var your_variable: NodeType (the “: NodeType” is optional but is good to filter what nodes you can assign for this variable)

After create the @export var, select the node that holds the script and assign the node you want to track

1 Like

This is the best solution of the ones I read.

@onready var player: Node2D = get_tree().get_first_node_in_group(“player”)

Now, as long as I dont rename the group, all the code that references stuff from that group will not break.

Also, I will probably add a singleton class with all the available group names, so if I decide to rename the group, I only have to change code in 1 place.

Thanks!

This solution also sounds intresting. But how do I use it?

In my case my specific problem is:
I have a Game root scene with a Player scene inside it. The Player scene has a Gun scene inside. The Gun scene emits a signal bulletsChanged(bulletsAmount).

I want the BulletsLabel to listen for the Gun signal.

Here is the hierarchy:

Here is the BulletsLabel script right now as I have it using groups (this works perfectly). Could I instead reference an exported variable from the Player scene instead? I think that would be a cleaner solution for me.

extends Label

func _ready():
	var gun = get_tree().get_first_node_in_group("gun")
	gun.bulletsChanged.connect(_gun_bullets_changed)

func _gun_bullets_changed(newBullets):
	text = "Balas: " + str(newBullets) + "/10"