Cannot reference buttons that were made via gdscript

Godot Version

4.4.1.stable

Question

I got a "Invalid access to property or key ‘btn’ on a base object of type ‘HBoxContainer (h_box_container.gd)’ error. I don’t know how to fix this. I am trying to make a game where multiple buttons pop up and by clicking on one, you can place tiles of different sizes on the grid. Nodes are organized as follows:

Code in ‘Loading_cargo’ node:

extends Node2D

var cargo_qty: int
@onready var h_box_container: HBoxContainer = $HUD/Panel/HBoxContainer

# Called when the node enters the scene tree for the first time.
func _ready():
	cargo_qty = randi_range(3, 10)
	for a in range(cargo_qty):
		h_box_container.place_cargo_button()

Code in ‘HBoxContainer’ node:

extends HBoxContainer

@onready var cargo: TileMapLayer = $"../../../Cargo"

var cargo_selector: int = 0

func place_cargo_button():
	cargo_selector = randi_range(1, 3)
	var btn = Button.new()
	btn.text = str(str(cargo_selector) + " X " + str(cargo_selector))
	btn.pressed.connect(cargo.button_pressed)
	add_child(btn)

Code in ‘Cargo’ node:

extends TileMapLayer

@onready var h_box_container: HBoxContainer = $"../HUD/Panel/HBoxContainer"
@onready var cargo: TileMapLayer = $"."
@onready var placed_cargo: TileMapLayer = $"../Placed_cargo"

var mouse_position: Vector2i
var old_mouse_position: Vector2i

var x: int = 11
var y: int
var rotational_adder: int
var source_id: int = 0

var x_min: int
var x_max: int
var y_min: int
var y_max: int

func button_pressed():
	if h_box_container.btn.get_text() == "1 X 1":
		x = 0
		y = 0
		rotational_adder = 1
		x_min = 7
		x_max = 66
		y_min = 9
		y_max = 23
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		h_box_container.btn.queue_free()

	if h_box_container.btn.get_text() == "2 X 2":
		x = 0
		y = 1
		rotational_adder = 2
		x_min = 8
		x_max = 65
		y_min = 10
		y_max = 22
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		h_box_container.btn.queue_free()

	if h_box_container.btn.get_text() == "3 X 3":
		x = 0
		y = 3
		rotational_adder = 3
		x_min = 9
		x_max = 64
		y_min = 11
		y_max = 21
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		h_box_container.btn.queue_free()

func _input(event):
	if event.is_action_pressed("ROTATE_RIGHT"):
		x += rotational_adder
	if event.is_action_pressed("ROTATE_LEFT"):
		if x == 0:
			match rotational_adder:
				1: x = 3
				2: x = 6
				3: x = 9
		else:
			x -= rotational_adder
	if rotational_adder == 1 and x > 3:
		x = 0
	if rotational_adder == 2 and x > 6:
		x = 0
	if rotational_adder == 3 and x > 9:
		x = 0
	if event.is_action_pressed("LEFT_CLICK") and x != 11:
		placed_cargo.set_cell(local_to_map(get_local_mouse_position()), source_id, Vector2i(x, y))
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		x = 11
		y = 0
		
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float):
	set_cell(mouse_position, source_id, Vector2i(x, y))
	mouse_position = local_to_map(get_local_mouse_position())
	if old_mouse_position != mouse_position:
		erase_cell(old_mouse_position)
		old_mouse_position = mouse_position
	
	if mouse_position.x < x_min or mouse_position.x > x_max or mouse_position.y < y_min or mouse_position.y > y_max:
		source_id = -1
	else:
		source_id = 0

The btn variable you create in place_cargo_button() is a local variable to that function. You won’t be able to access it from other classes.

# ...
var btn:Button

func place_cargo_button():
    # ...
    btn = Button.new()
    # ...

But you shouldn’t need to do that. You can bind extra parameters to a signal connection with Callable.bind() like:

func place_cargo_button():
	cargo_selector = randi_range(1, 3)
	var btn = Button.new()
	btn.text = str(str(cargo_selector) + " X " + str(cargo_selector))
	btn.pressed.connect(cargo.button_pressed.bind(btn, cargo_selector))
	add_child(btn)
func button_pressed(btn:Button, cargo_selector:int):
	if cargo_selector == 1:
		x = 0
		y = 0
		rotational_adder = 1
		x_min = 7
		x_max = 66
		y_min = 9
		y_max = 23
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		btn.queue_free()

	elif cargo_selector == 2:
		x = 0
		y = 1
		rotational_adder = 2
		x_min = 8
		x_max = 65
		y_min = 10
		y_max = 22
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		btn.queue_free()

	elif cargo_selector == 3:
		x = 0
		y = 3
		rotational_adder = 3
		x_min = 9
		x_max = 64
		y_min = 11
		y_max = 21
		Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
		btn.queue_free()

That worked, thank you!

I do have a follow up question: in the code below, I try to ‘reset’ the atlas coordinates by making the x variable 11 and y variable 0, so a blank tile follows the mouse instead of a ‘cargo’ tile. However, that doesn’t happen, a cargo tile still follows the mouse. How do I make this so the tile disappears from the mouse once a cargo tile is placed?

	if event.is_action_pressed("LEFT_CLICK") and x != 11:
		placed_cargo.set_cell(local_to_map(get_local_mouse_position()), source_id, Vector2i(x, y))
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		x = 11
		y = 0