Differentiating objects that are sharing same script

Godot Version

4.2

Question

I have two object on my scene, that are sharing same code. When I am instantiating those on my scene, I am assigning them a unique ID in _ready func:

func _ready():
    
    # Setting values. 
    houseid = randi() % 50

I also have a function that creates an interface whenever I am clicking on a building:

func _on_building_static_input_event(viewport, event, shape_idx):
    click += 1
    if event.is_pressed() and click > 1:
        var func_button = interfacePanel.get_child(4)
        func_button.set_text("Cut" + ' ' + str(houseid))
        func_button.visible = true
        func_button.pressed.connect(self.CutArea)

So I am creating an interface button (it works okay) and connecting it to a function that makes a simple print:

func CutArea():
    print('Test works from building ', houseid) 

This function indeed prints a text, but if I have 2 or more buildings, all the IDs of those buildings and same amount of line (like if I have 2 buildings > I have 2 lines of output) output.

I clearly can see that interface is different, here are examples:

image
image
So I expected that if I will click ā€˜Cut 3ā€™ I will have an output of ā€˜Test works from building 3ā€™ but instead I am getting this:

image

I assume that something is wrong with way of connection button to a script, but canā€™t figure out the correct way. Godot 4.2!

Thanks in advance for any advice :slight_smile:

With the information youā€™ve provided, this is pretty hard to explain. Sharing the full script (or even entire project) might help here. That being said, your code for _on_building_static_input_event strikes me as a bit odd: What is the initial value of click? And what is that second condition click > 1 for? Also, what is happening with the previous func_button when you click again?

Anyway, the behavior youā€™re seeing should only occur if clicking on ā€œCut 3ā€ somehow also is registered as a click on ā€œCut 20ā€, but I donā€™t see any reason it would.

Basically, each click overwrites the pre-created interface panel values. Here is the full code of the script:

extends Node2D

# Export variables

@export var resourceToProduce : ResourceType
@export var buildingName : String
@export var jobType : Job_Type

# Managers 
@onready var resourceManager
@onready var peasantManager
@onready var workPlacementManager
@onready var interfacePanel
@onready var peasantSlots
@onready var tilemap = $"../TileMap"
@onready var spot1 = $Spot1
@onready var spot2 = $Spot2

# Debug variables
var click = 0

# Local variables
var houseid
var maxCapacity = 2
var currentCapacity = 0
var currentWorkers = []

# Area variables
var isDrawing = false


# Called when the node enters the scene tree for the first time.
func _ready():
	
	# Setting values. 
	houseid = randi() % 50
	resourceManager = get_node("../ResourceManager")
	peasantManager = get_node("../PeasantManager")
	interfacePanel = get_node("../TileMap/CanvasLayer/BuildingPanel")
	workPlacementManager = get_node("../WorkPlacementManager")
	peasantSlots = ["../TileMap/CanvasLayer/BuildingPanel/peasant 1", "../TileMap/CanvasLayer/BuildingPanel/peasant 2"]
	$Timer.start()
	workPlacementManager.add_work_placement(self, jobType)



func _on_building_static_input_event(viewport, event, shape_idx):
	click += 1
	if event.is_pressed() and click > 1:
		for i in peasantSlots.size():
			get_node(peasantSlots[i]).visible = false
		interfacePanel.visible = true
		interfacePanel.get_child(0).set_text(buildingName + ' ' + str(houseid))
		interfacePanel.get_child(1).set_text(resourceToProduce.name)
		interfacePanel.get_child(2).set_text(str(currentCapacity) + "/" + str(maxCapacity))
		interfacePanel.get_child(3).get_child(0).texture = $Sprite2D.texture
		interfacePanel.get_child(4).visible = false
	        var func_button = interfacePanel.get_child(4)
                func_button.set_text("Cut" + ' ' + str(houseid))
                func_button.visible = true
                func_button.pressed.connect(self.CutArea)
		
		
		if currentWorkers.size() > 0:	
			for i in currentWorkers.size():
				get_node(peasantSlots[i]).set_text(currentWorkers[i].firstname + " " + currentWorkers[i].lastname + " (" + str(currentWorkers[i].energy) + "/100" + ")")
				get_node(peasantSlots[i]).visible = true
		elif currentWorkers.size() > 0 and currentWorkers.size() < maxCapacity:
			var counter = 0
			for i in currentWorkers.size():
				get_node(peasantSlots[i]).set_text(currentWorkers[i].firstname + " " + currentWorkers[i].lastname + " (" + str(currentWorkers[i].energy) + "/100" + ")")
				get_node(peasantSlots[i]).visible = true
				counter += 1
			for x in peasantSlots.size():
				x = counter
				get_node(peasantSlots[x]).set_text("")
				counter += 1
		else:
			for i in peasantSlots.size():
				get_node(peasantSlots[i]).set_text("")


func _on_timer_timeout():
	resourceManager.updateResourceAmount(resourceToProduce)

func assignWorker(worker):
	currentWorkers.append(worker)
	currentCapacity += 1
	
	if currentCapacity == 1:
		moveWorker(worker, spot1.global_position)
	else:
		moveWorker(worker, spot2.global_position)
	
func removeWorker(worker):
	currentWorkers.erase(worker)
	currentCapacity -= 1
	moveWorker(worker, worker.SpawnPoint.global_position)

func moveWorker(worker, toPos):
	if tilemap.is_point_walkable(toPos):
		worker.current_path = tilemap.astar.get_id_path(
		tilemap.local_to_map(worker.global_position),
		tilemap.local_to_map(toPos)
		).slice(1)
		
func CutArea():
	print(houseid)
	print('Test works from building ', houseid)

ā€˜clickā€™ condition just there for testing purposes (iā€™m currently instantiating buildings on left click, and also interface is being opened on left click, so there was a bug when I was just instantiating a building an interface was immediately opened, this temporary fix allows me to not open the interface immediately after instantiating).

So basically yeah, I have some placeholders created in my canvas:

image

And Iā€™m overwriting them each time I click on building. I thought that it will overwrite the funcition as well in a sense.

So, essentially, youā€™re doing this, right?

extends Node2D

var click = 0

@onready var button := get_node("Button")
@onready var id := randi() % 50

func _on_area_2d_input_event(viewport: Node, event: InputEvent, shape_idx: int) -> void:
	click += 1
	if event.is_pressed() and click > 1:
		var func_button = button
		func_button.set_text("Cut" + ' ' + str(id))
		func_button.visible = true
		func_button.pressed.connect(self.CutArea)

func CutArea():
	print(id)

So clicking the Area2D (or whatever else youā€™re using that sends an input_event signal, Iā€™ll assume that part works properly) will show a button, and clicking it will call CutArea(), which prints the id.

But if you instantiate that same scene multiple times, clicking a button will not only print the id associated with it, but also the id of the other buttons? Because if so, I canā€™t reproduce that with the code above!

1 Like

func _on_building_static_input_event(viewport, event, shape_idx):

Basically this is an area2d, yes. As far as I understand, this function handles all events that are happening in this area (it could be click or just mouseover, for example). But yeah, this part is working as expected anyway.

I have one single instance of the interface panel (structure is in the my previous comment) with this code I am overwriting values in the interface panel with the data from the object I clicked on.

And yes, it prints the IDs of all the buttons, itā€™s correct, and I need to print only the ID of the object where the button was clicked.

UPD.: As far as I understand all the buttons are being connected to the script, and it doesnā€™t matter which button triggers the script, all connected buttons will be triggered as well.

Ah, got it! Youā€™re re-using the same button, interfacePanel.get_child(4), in each building script. So when a building is clicked, you only change the text of the button, but the actual instance remains intact and all connected signals as well.

1 Like

Yes, I think your description is correct. This is the problem.

And, letā€™s imagine I have two instances of the same building (House ID1, house ID2) and if clicking the button I want to print output ā€œID1ā€ or ā€œID2ā€, not both.

I was trying to pass values to the function with .bind option, but it didnā€™t work. So I was thinking if it is possible to trigger the script and pass the attribute to the script without any .connect or .bind, but didnā€™t find a solution yet.

Well, your problem is not that the right function isnā€™t called. And your problem is not that the function isnā€™t printing the right thing, either. Your problem is that thereā€™s more than one function called on each click! Both buildings have connected their CutArea function to the pressed signal of the same button. Using bind wonā€™t change that.

Fixing this isnā€™t trivial, though. You could disconnect the old signal, but only if you knew the callback it was connected to previously ā€“ which you donā€™t. Or you would actually create an entirely new instance of the button, but you might have reasons not to. Or you could pass CONNECT_ONE_SHOT = 8 as a flag to connect, but then clicking on the same button repeatedly wouldnā€™t work, obviously.

1 Like

Yep, I was thinking about creating a new button. The problem here is that I have to store array of those temporary buttons somewhere else, since Iā€™m not sure how to interconnect everything. But yeah, it seems to me now that this is the only option.

Idea was to create a button, save it in array, then, upon next click, check the array, somehow delete the button from array and create a new one instead.

I prefer using two (or more) buttons and according to the scenario, hide/show the proper one(s).

Itā€™s also easier to work with, debug, etc. At least for me it is.

1 Like

Yeah, itā€™s very reasonable, but in my ideas different building might have different amount of buttons, so I want to find a better approach for solving this issue. Manually creating different sets of buttons might be confusing.

So here is the solution, thanks to @njamster. I decided to try again an approach of creating a separate button each time.

Each time Iā€™m clicking on building, this happens:

var func_button = Button.new() 
func_button.set_text(Cut + ' ' + str(houseid))|
func_button.position.x = interfacePanel.get_child(4).position.x|
func_button.position.y = interfacePanel.get_child(4).position.y|
interfacePanel.add_child(func_button)|
interfacePanel.temp_func_buttons.append(func_button)|
func_button.visible = true|
func_button.pressed.connect(CutArea)|

And I have an array of temporary buttons stored in a manager (separate object).


		if interfacePanel.temp_func_buttons.size() > 0:
			for i in interfacePanel.temp_func_buttons.size():
				interfacePanel.remove_child(interfacePanel.temp_func_buttons[i])

Before instantiating a new button I am checking this array and if itā€™s not empty Iā€™m deleting all the buttons from it.