Unable to prevent signal input if a var is set to true

Godot Version

4.2.2.stable

Question

Hi,

I have picked upt Godot recently and am quite new to game development and coding, so I am still learning a lot.

I have recently run into a problem with input and a Node “SelectionArea” I have created to select buildings or items in my scene, which can also be placed. Now I want to differentiate between placing items and selecting items. For that I added a variable that triggers a “preview_mode” true or false depending on whether my I am in “placing” mode or not. The code for the selection area looks as follows:

class_name SelectionArea

extends Area2D

signal selection_toggled(selection)

@export var exclusive = true
@export var selection_action = "map_left_click"

var selected = false: set = set_selected
var is_mouse_over: bool
var preview_mode := false

func _ready():
	SignalManager.connect("preview_active", _on_preview_active)

func set_selected(selection):
	if selection:
		_make_exclusive()
		add_to_group("selected")
	else:
		remove_from_group("selected")
	selected = selection
	selection_toggled.emit(selected)
	print("Selection toggled: ", selected)

func _make_exclusive():
	if not exclusive:
		return
	get_tree().call_group("selected", "set_selected", false)
	
	
func _mouse_enter(): 
	is_mouse_over = true

func _mouse_exit():
	is_mouse_over = false
	
func _input(event):	
	if event.is_action_pressed(selection_action) and is_mouse_over:
		if preview_mode:
			return
		else:
			set_selected(not selected)
			get_viewport().set_input_as_handled()
			print("Click: Selection")
	return
	
func _on_preview_active(status):
	preview_mode = status
	print("Preview mode status changed: ", status)

The actual problem right now is, if I want to place another building on a tile (I am using a tilemap) behind the selecetion area, it still triggers the “selection” even though “preview_mode” is set to true. (I was able to confirm that, as once I enter the “placing mode” the signal come through and prints:

Preview mode status changed: true

If I then place an item in the world I have a print in another script that correctly prints:

Click: Place Building and also correctly places the item

However if I want to place an item now, for example, on the tile above the now occcupied tile that is covered by the selection are I recieve:

Selection toggled: true
Click: Selection

even though the preview_mode = true

Is my syntax wrong or am I not understanding something about the _input() function?

Any help is appreciated,
Thanks

Could you post a video showing your problem in game? It would make it easier to visualize your problem. But seeing your explanation, if you place a building in “preview_mode”, it will stop working right away and change the Click behavior from “placing mode” to “selection”, right? If so, looking at your code, I need to know how the signal “preview_active” is emitted. If you could share how it is emmitted and also share your script that prints Click: Place Building, it would helps a lot. And don’t worry about the _input() function in this script. If was something wrong with it, it wouldn’t even print Click: Selection.

Obviously preview_mode cannot be true if you are getting Click: Selection.
Is it possible the output is coming from an instance of this class you didn’t expect?

You can try to reduce the number of SelectionArea instances to 1 and see if the problem persists.

Here is a link to a Youtube video of it in action, if that helps

Video showing the issue

If the preview_status is changing it should usually print the change in the console as well, unless it is changed outside of the signal function.

I’ll post the code for the BuildingPlacer in a second as well, where the signal is coming from.

class_name Building_Placer

extends Node2D

@onready var tilemap = $"../TileMap"
@onready var world = get_parent()

@export var preview_building: Building
var preview_instance


func _process(delta):
	if preview_instance != null:
		var mouse_pos = get_local_mouse_position()
		var grid_pos = tilemap.local_to_map(mouse_pos)
		var world_pos = tilemap.map_to_local(grid_pos)
		preview_instance.position = world_pos
		preview_instance.z_index = 2
		preview_instance.previewing = true
		if is_tile_occupied(grid_pos):
			preview_instance.can_place = false
		else:
			preview_instance.can_place = true
			

func create_placement_preview():
	preview_instance = preview_building.scene.instantiate()
	add_child(preview_instance)
	SignalManager.emit_signal("preview_active", true)
		
func clear_preview():
	SignalManager.emit_signal("preview_active", false)
	if preview_instance == null:
		return
		
	preview_instance.queue_free()
	
func _input(event):
	if preview_instance != null:
		if event.is_action_pressed("right_click"):
			print("Click: End Preview")
			clear_preview()
	
		if event.is_action_pressed("map_left_click"):
			var mouse_pos = get_local_mouse_position()
			var tile_pos = tilemap.local_to_map(mouse_pos)
	
			if not is_tile_occupied(tile_pos):
				place_building(tile_pos)
				print("Click: Place Building")
			else:
				print("Occupied")
	
func is_tile_occupied(tile_position):
	var placed_buildings = get_parent().get_node("BuildingContainer").get_children()
	for child in placed_buildings:
		if tilemap.local_to_map(child.position) == tile_position:
			return true
	return false
	
func place_building(tile_pos):
	var building = preview_building.scene.instantiate()
	var world_position = tilemap.map_to_local(tile_pos)
	building.position = world_position
	get_parent().get_node("BuildingContainer").add_child(building)
	
func _on_region_map_ui_build_pressed():
	create_placement_preview()

The signal is also running through a Signal Manager singleton:

extends Node

signal formation_selected

signal building_selected

signal preview_active(status)

signal mouse_coords(mouse_position, hexgrid_position)

I am not sure, where it would reset itself to false though… Besides the signal function nothing is changing the value and the signal should give me a print about the change, which it doesn’t.

Unfortunately even if I only have one building it gives me the same problem, so I don’t think multiple instances of the “SelectionArea” are to blame…

I can’t see the entire project so I say this based on little evidence.
The fact that you only have one building does necessarily negate the idea that you may have more than one instance of SelectionArea.
Try changing this line:
var preview_mode := false
to

var preview_mode:bool = false: 
    set(value): 
        preview_mode = value
        printt("preview mode changed to:", value)

If have a single instance and something is changing the value to false you will see the line printed.
If you have more than one instance then you should not see that line printed.

Did that. Here is the console output from trying to place a building behind another building:

preview mode changed to: true
Preview mode status changed: true
Click: Place Building
Click: Place Building
Selection toggled: true
Click: Selection
Selection toggled: false
Selection toggled: true
Click: Selection
Selection toggled: false
Click: Selection

I does not seem to change in the process. Is the way I set up the _input() in general correct and should work in theory?

I think I know the problem now!

Because I am instantiating the SelectionArea when placing the “building” it does not recieve the value change from entering building mode I think…

I wonder what triggers the prints then in the first place though, because at the start the selection area is not instantiated yet… Does Godot still send the signal to the script even though it is not in the scene yet? And after that I suppose it get’s reset once it loads with the “building-placement”?

I’ll try to make some changes to fix that.

Thanks for the help up to this point! The comment with the multiple instances put me on the right track if this turns out to be true.

Yes that did the trick.

I set var preview_mode: bool = true: now, since the preview_mode is active when placing the building. I can now place buildings even behind it and once I exit preview mode, the status changes as intended!