Issue with Clickable Tiles in TileMapLayer

Godot Version

4.3

Question

Hello, I’m trying to set up clickable tiles in a TileMapLayer, but I’m having trouble getting them to respond to clicks. The TileMapLayer is a child of another scene, which is being instantiated in the main scene. The scenes seem to load fine, but nothing happens when I click the tiles.

Here’s what I’ve done so far:

  • I’ve implemented _input_event in the TileMapLayer to detect mouse clicks.
  • I’ve connected the tile_clicked signal to the parent scene to handle clicks.
  • I’ve added a physics layer to the TileMapLayer and painted tiles with the physics layer.

But when I click the tiles, I get no output, and nothing happens. Am I missing something here?

Thanks for any help!

Main Scene
Main

Matrix Scene
Matrix

TileMapLayer

extends TileMapLayer

# 4x4 tile state array
var tile_states: Array = []

# Signal to notify parent when a tile is clicked
signal tile_clicked(coords: Vector2i, power: int)

func _ready():
	# Initialize the tile states array to null
	tile_states = []
	for y in range(4):
		var row = []
		for x in range(4):
			row.append(null)
		tile_states.append(row)

	# Debugging to confirm readiness
	print("TileMapLayer is ready.")

# Called when an input event occurs on this node (TileMapLayer)
func _input_event(viewport, event, shape_idx):
	print("DEBUG: _input_event triggered!")  # Debugging line
	if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
		var local_mouse_pos = to_local(event.position)
		var clicked_coords = local_to_map(local_mouse_pos)
		
		print("Mouse clicked at local position:", local_mouse_pos)
		print("Tile coordinates:", clicked_coords)
		
		# Emit signal when tile is clicked
		emit_signal("tile_clicked", clicked_coords, 1)  # Example power value: 1

# Return the tile states for external access
func get_tile_states() -> Array:
	return tile_states

Node2D (Parent node for Matrix scene)

extends Node2D

@export var tilemap_layer: TileMapLayer
var tile_states: Array = []

func _ready():
	# Ensure tilemap_layer is assigned
	if not tilemap_layer:
		tilemap_layer = $TileMapLayer  # Adjust this if the child node name is different

	if tilemap_layer:
		print("TileMapLayer found, connecting signal.")
		# Connect tilemap_layer's signal to the parent grid manager
		tilemap_layer.connect("tile_clicked", Callable(self, "_on_tile_clicked"))
	else:
		print("Error: TileMapLayer not found.")

# Called when a tile is clicked in the TileMapLayer
func _on_tile_map_layer_tile_clicked(coords: Vector2i, power: int):
	print("Tile clicked at:", coords, "with power:", power)
	# You can add more logic here if needed

GridManager (Main Scene)

extends Node2D

@export var matrix4x4_scene: PackedScene  # Reference to the Matrix4x4 scene
var grid_nodes: Array = []  # Array to hold the instantiated Matrix4x4 nodes

# This will hold the combined grid data
var grid_state: Array = []

# Screen resolution and tile sizes
const SCREEN_WIDTH = 1920
const SCREEN_HEIGHT = 1080
const TILE_SIZE = 16  # Adjust to match your tile size
const MATRIX_SCALE = 4  # Scale of the matrix

func _ready():
	# Initialize grid_state as a 2D array for the 8x8 grid
	grid_state = []
	for y in range(8):
		var row = []
		for x in range(8):
			row.append(null)
		grid_state.append(row)

	# Calculate the total size of the 2x2 grid
	var matrix_size = TILE_SIZE * 4 * MATRIX_SCALE  # Each matrix is 4x4 tiles
	var total_grid_width = 2 * matrix_size  # 2 matrices wide
	var total_grid_height = 2 * matrix_size  # 2 matrices high

	# Calculate offsets to center the grid
	var offset_x = (SCREEN_WIDTH - total_grid_width) / 2
	var offset_y = (SCREEN_HEIGHT - total_grid_height) / 2

	# Instantiate 4 matrix4x4 nodes and organize them in a 2x2 grid
	for i in range(4):
		var matrix = matrix4x4_scene.instantiate()  # Instantiate a new Matrix4x4 node
		add_child(matrix)

		# Determine the position for each matrix in the 2x2 layout
		var row = int(i / 2)  # 0 or 1 (top or bottom row)
		var col = i % 2  # 0 or 1 (left or right column)
		var grid_x = col * matrix_size
		var grid_y = row * matrix_size
		matrix.position = Vector2(offset_x + grid_x, offset_y + grid_y)

		# Scale up the matrix
		matrix.scale = Vector2(MATRIX_SCALE, MATRIX_SCALE)

		# Store the node and update its grid index
		grid_nodes.append(matrix)

		# Connect signals from matrix4x4 to parent grid manager
		var tilemap_layer = matrix.get_node("TileMapLayer")  # TileMapLayer is a child of Matrix4x4
		if tilemap_layer:
			print("TileMapLayer found in Matrix4x4, connecting signal.")
			tilemap_layer.connect("tile_clicked", Callable(self, "_on_tile_clicked"))
		else:
			print("Error: TileMapLayer not found in Matrix4x4!")

		# Update grid_state with the initial tile states
		update_grid_state(i, tilemap_layer.get_tile_states())  # Now using tilemap_layer.get_tile_states()

func update_grid_state(grid_index: int, tile_states: Array):
	# Map the 4x4 grid to the correct position in the 8x8 grid
	var start_x = (grid_index % 2) * 4  # Left-to-right
	var start_y = int(grid_index / 2) * 4  # Top-to-bottom

	for local_y in range(4):
		for local_x in range(4):
			grid_state[start_y + local_y][start_x + local_x] = tile_states[local_y][local_x]

func _on_tile_clicked(coords: Vector2i, power: int):
	print("Tile clicked at:", coords, "with power:", power)

	# You can add further logic for handling the tile click here

func clear_full_rows_and_columns():
	var rows_to_clear = []
	var columns_to_clear = []

	# Check rows
	for y in range(8):
		var full_row = true
		for x in range(8):
			if grid_state[y][x] == null:
				full_row = false
				break
		if full_row:
			rows_to_clear.append(y)

	# Check columns
	for x in range(8):
		var full_column = true
		for y in range(8):
			if grid_state[y][x] == null:
				full_column = false
				break
		if full_column:
			columns_to_clear.append(x)

	# Clear rows and columns
	for y in rows_to_clear:
		for x in range(8):
			clear_tile(x, y)

	for x in columns_to_clear:
		for y in range(8):
			clear_tile(x, y)

	# Notify the grids to refill
	for grid in grid_nodes:
		grid.refill_tiles()

func clear_tile(x: int, y: int):
	# Find which 4x4 grid the tile belongs to
	var grid_index = int(y / 4) * 2 + int(x / 4)
	var local_x = x % 4
	var local_y = y % 4

	var grid = grid_nodes[grid_index]
	grid.clear_tile(local_x, local_y)

	# Update the grid state
	grid_state[y][x] = null

Output

TileMapLayer is ready.
TileMapLayer found, connecting signal.
TileMapLayer found in Matrix4x4, connecting signal.
TileMapLayer is ready.
TileMapLayer found, connecting signal.
TileMapLayer found in Matrix4x4, connecting signal.
TileMapLayer is ready.
TileMapLayer found, connecting signal.
TileMapLayer found in Matrix4x4, connecting signal.
TileMapLayer is ready.
TileMapLayer found, connecting signal.
TileMapLayer found in Matrix4x4, connecting signal.

Okay, so I have updated my TileMapLayer code and now this works when running the matrix scene. But this still does not work when I load the matrix into my main scene and try clicking the tiles in my main scene.

Updated TileMapLayer

extends TileMapLayer

signal tile_clicked(coords: Vector2i, power: int)

var tile_states: Array = []

func _ready():
	# Initialize tile states to null
	tile_states = []
	for y in range(4):
		var row = []
		for x in range(4):
			row.append(null)
		tile_states.append(row)

	print("TileMapLayer is ready.")
	
	# Make sure this node processes input events
	set_process_input(true)

# Return the tile states for external access
func get_tile_states() -> Array:
	return tile_states

# Use _unhandled_input for input events
func _unhandled_input(event):
	# Debugging to check if the event is received
	if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
		# Get the global mouse position
		var global_mouse_pos = get_global_mouse_position()
		
		# Convert the global mouse position to the local coordinate system of the TileMapLayer
		var local_mouse_pos = to_local(global_mouse_pos)
		
		# Get the tile coordinates in the grid
		var clicked_coords = local_to_map(local_mouse_pos)

		print("Global mouse position:", global_mouse_pos)
		print("Local mouse position:", local_mouse_pos)
		print("Tile coordinates:", clicked_coords)

		# Emit the signal when a tile is clicked
		emit_signal("tile_clicked", clicked_coords, 1)  # Example power value: 1