How do you pass a variable down to a grandchild node?

Godot Version

4.5.1 Linux

Question

I know this is probably stupid simple, but I’m a beginner and can’t get a straight answer anywhere. All I want to know is how you pass down a variable from one node to a grandchild node, with the entire process being confined within the scene. If there’s multiple ways, I’d like to know them too.

edit: I should probably clarify further. There is a root node in a scene with a child, and that child node also has a child node. A variable is going to be updated every frame in the root node, and the grandchild node needs to know every frame what that variable currently is. The entire scene is completely static and there will only ever be one of it.

Say your variable in the grandchild node is strength.

$child_node/grandchild_node.strength = 5

There’s lots of ways. While @fireside’s option will work, it’s not recommended because you’re hard-coding a node path. Instead you should use an @onready variable. (See below.)

Let’s say you have 3 nodes:

NodeA
|____NodeB
  |____NodeC

let’s say that they all have a variable named foo.

Use an @onready Variable

# node_a.gd
extends Node

var foo: int = 1

@onready var node_c: Node = "$NodeB/NodeC"


func pass_down() -> void:
	node_c.foo = foo

Use an @onready Variable with a Unique Name

Right-click on NodeC in the Scene Tree and select % Access as Unique Name.

# node_a.gd
extends Node

var foo: int = 1

@onready var node_c: Node = "%NodeC"


func pass_down() -> void:
	node_c.foo = foo

Use a Signal

# node_a.gd
extends Node

signal change_foo(value: int)

var foo: int = 1


func pass_down() -> void:
	change_foo.emit(foo)
# node_c.gd
extends Node

var foo: int = 3

@onready var node_a: Node = "$../NodeA"


func _ready() -> void:
	node_a.change_foo.connect(_on_change_foo)

func _on_change_foo(value: int) -> void:
	foo = value

Use a Signal with a Unique Name

Right-click on NodeA in the Scene Tree and select % Access as Unique Name.

# node_a.gd
extends Node

signal change_foo(value: int)

var foo: int = 1


func pass_down() -> void:
	change_foo.emit(foo)
# node_c.gd
extends Node

var foo: int = 3

@onready var node_a: Node = "%NodeA"


func _ready() -> void:
	node_a.change_foo.connect(_on_change_foo)

func _on_change_foo(value: int) -> void:
	foo = value

Use a Signal Bus

  1. Create a new scene.
  2. Add a node to it.
  3. Rename the node SignalBus. (Not strictly necessary but helps with clarity.)
  4. Save the scene as signal_bus.tscn.
  5. Attach a script to it. (See below for contents.)
  6. Save the script.
  7. Add the scene as an Autoload named SignalBus (not the script).
# signal_bus.gd
extends Node

@warning_ignore("unused_signal")
signal change_foo(value: int)
# node_a.gd
extends Node

var foo: int = 1


func pass_down() -> void:
	SignalBus.change_foo.emit(foo)
# node_c.gd
extends Node

var foo: int = 3


func _ready() -> void:
	SignalBus.change_foo.connect(_on_change_foo)

func _on_change_foo(value: int) -> void:
	foo = value
2 Likes

If your scene tree is static (the number and position of grandchildren don’t change), you can also use

self.get_child(temp1).get_child(temp2).foo = 

# temp 1 and 2 should be an int value or an int var
# temp1 is the child node and temp2 is the grandchild, these values are determined by node order in the scene tree, node orders are indexed (not counted), so the first child in order should be 0

You can set the variable directly this way without a signal

That’s not how get_child() works. It uses indexes. Which means you have to hardcode it.

Here’s a few more possible ways.

Use get_child()

# node_a.gd
extends Node

var foo: int = 1

func pass_down() -> void:
	get_child(0).get_child(0).foo = foo

Use get_children(), set(), and Node Name

# node_a.gd
extends Node

var foo: int = 1:
	set(value):
		foo = value
		pass_down(self)

func pass_down(node: Node) -> void:
	for node: Node in get_children():
		if node.name == "NodeC":
			node.foo = foo
		pass_down(node)

Use get_children(), set(), and class_name

# node_a.gd
extends Node

var foo: int = 1:
	set(value):
		foo = value
		pass_down(self)

func pass_down(node: Node) -> void:
	for node: Node in get_children():
		if node is NodeC:
			node.foo = foo
		pass_down(node)
# node_c.gd
class_name NodeC extends Node

var foo: int = 3

Respectfully, this is 100% congruent with my post, you said I did it wrong, but besides the placeholder temp1 and temp2, this is the same as what I suggested.

I even mentioned the indexing thing

1 Like

Apologies, I missed your comments in your code. You are correct.

It is worth noting a technicality here.
None of the examples given actually “pass a variable down”. They all pass a value down that is then set in a variable that must already exist in the child script.
This is because GDScript passes by value and not reference.
So if we use dragonforges first method as an example:

extends Node

var foo: int = 1

@onready var node_c: Node = "$NodeB/NodeC"


func pass_down() -> void:
	node_c.foo = foo

and inside NodeC script we change the value of foo to 12, in the parent script foo will retain the original value of 1.

1 Like