Putting nodes in variables

Godot Version

4.3

So what I’m trying to do is to basically create a variable that contains nodes and edit them all through this, here’s the script

var scene = 0
var child = [$Panel1,$Panel2]

func _on_buttonscene_pressed() -> void:
	scene += 1
	print (scene)
	
	if scene == 0:
		for panel in child:
			child.visible = false
		
		$Panel2.visible = true
		
	
	elif scene == 1:
		for panel in child:
			child.visible = false
		
		$Panel2.visible = true

You have a number of issues in your code:

  1. Whether scene equals 1 or 2, you are always showing panel 2. So all those statements have no effect.
  2. You cannot use a reference like $Panel in a variable assignment at the beginning of a file without using @onready
  3. Using an @onready variable in another @onready variable assignment would be dicey.
  4. The use of child as a variable name is really confusing and should be avoided. Case in point, you are trying to operate on the array instead of the members of the array in your for/each loop.
  5. If you fix all the above issues, this code will only result in the second if statement being executed once.

Here’s how you can restructure your code. I assumed you wanted Panel1 to be visible in the first condition.

var scene = 0
var panels: Array

@onready var panel_1: Panel = $Panel1
@onready var panel_2: Panel = $Panel2

func _ready() -> void:
	panels.append(panel_1)
	panels.append(panel_2)

func _on_buttonscene_pressed() -> void:
	scene += 1
	print (scene)
	
	if scene == 0:
		for panel in panels:
			panel.visible = false
		
		panel_1.visible = true
		
	
	elif scene == 1:
		for panel in panels:
			panel.visible = false
		
		panel_2.visible = true

However, like I said in #5, this code will only work the first time you click the button. Assuming you want the button to toggle between the two:

  1. In the Inspector, turn the button’s toggle_mode to true by checking the box.
  2. Connect the `toggled(toggled_on: bool) signal of the button to your code.
  3. Disconnect the pressed() signal.
  4. Use this code:
@onready var panel_1: Panel = $Panel1
@onready var panel_2: Panel = $Panel2


func _on_button_toggled(toggle_mode: bool) -> void:
	panel_1.visible = toggle_mode
	panel_2.visible = !toggle_mode

If they’re not toggling in the order you want, change the function to this:

func _on_button_toggled(toggle_mode: bool) -> void:
	panel_1.visible = !toggle_mode
	panel_2.visible = toggle_mode

You don’t need all that other stuff.

Aren’t you doing exactly that in your code?
This:

func _ready() -> void:
	panels.append(panel_1)
	panels.append(panel_2)

is no different than this:
@onready var panels:Array[Panel] = [panel_1, panel_2]

You’re right, thanks. Neither would work. It’d have to be:

var scene = 0
var panels: Array
var panel_1: Panel
var panel_2: Panel

func _ready() -> void:
	panel_1 = $Panel1
	panel_2 = $Panel2	
	panels.append(panel_1)
	panels.append(panel_2)

func _on_buttonscene_pressed() -> void:
	scene += 1
	print (scene)
	
	if scene == 0:
		for panel in panels:
			panel.visible = false
		
		panel_1.visible = true
		
	
	elif scene == 1:
		for panel in panels:
			panel.visible = false
		
		panel_2.visible = true

Hello there,
You haven’t really mentioned what help you need regarding you code. I assume you wanted to share your code for improvements.

One improvement I would like to share is to only use the child list to change the visibility of panels and remove the conditional check.

func _on_buttonscene_pressed() -> void:
    scene += 1
    print (scene)

    for panel in child:
        child.visible = false  # Hide all the panels

    child[scene].visible = true; # Show the current panel

Further it can be improved by adding a _ready() function that already hides all the panels except the first one on startup.

func _ready() -> void:
   for panel in child:
      panel.visible = false;  # Hide all the panels at startup

   child[scene].visible = true;  # Show the default panel

func _on_buttonscene_pressed() -> void:
    scene += 1
    print (scene)

    child[scene - 1].visible = false;  # Hide the previous panel
    child[scene].visible = true;  # Show the next panel

Please note that I code in C# and haven’t used gdScript in a long time. Kindly forgive me if I had made any mistake above.

(Not trying to badger you but) I don’t understand why the other method wouldn’t work.

@onready var panel_1: Panel = $Panel1
@onready var panel_2: Panel = $Panel2

@onready var panels:Array[Panel] = [panel_1, panel_2]

I use that method often enough that I know it seems to work, however if I am doing something that isn’t recommended, I wonder if you can explain why (or point me to some docs)?
And I apologize to the OP here for this bit of sidetrack.

It will work sometimes. But it isn’t guaranteed to work. If your file is large enough, Godot will load it faster by using multiple threads. At that point you will get a race condition and you may end up with panels being constructed by a different thread and faster than panels 1 and 2. In which case it will throw a null exception at some point because panels is an array containing two nulls.

Do not trust any onready variable or variable constructed in _ready() until the ready signal has been emitted by your object. This bites you in the behind most often when you have something in _process or _physics_process that uses one of those variables. They can get called before the node is fully constructed.

If this happens you can create a variable is_ready to track it.

var is_ready = false

func _ready() -> void:
	ready.connect(_on_ready)

func _on_ready() -> void:
	is_ready = true

func _physics_process(delta: float) -> void:
	if not is_ready:
		return
	#regular stuff to execute after we are ready

I could have also used is_ready as an example above - it’s just kind of convoluted for that and in that case not good practice.