How do I read what scene a tile has instanced on a specific grid position on a tilemap?

Godot Version

4.2.1

Question

Hi, I’m fairly new to Godot and working on my first actual project, a 2d platformer. I have been working on a mechanic for a while that recursively swaps tiles on a tilemap between an active and reference layer when you interact with a switch. This has mostly been successful, however I am running into an issue with scenes that I have added to my tileset. The way I found to set the tile in the active layer to the cell in the reference layer is this:

tilemap.set_cell(main_layer, cell_pos, ref_source_id, ref_atlas_coords)

This method relies on the atlas coordinates of the tile in the reference layer, but scene tiles seem to be stored differently. I am to the point where the game can read if the next tile to be swapped is a scene tile, but it just swaps it for an empty tile because it isn’t reading where the tile is stored properly. My best guess from what I’ve researched so far is that I may need to access the scene directly through the scenetree, but I’m not sure how I could make it specific to the tile that needs to be replaced.

Here’s the full script for the switch scene if it helps. It’s kind of Frankensteined together from various tutorials and the small bit of knowledge I’ve picked up thus far.

extends Node2D

@export var is_wire := true
@onready var animation = $AnimatedSprite2D
@export var main_layer := 7 # Layer with interactive wires
@export var reference_layer := 8 # Layer with opposite states
var checked_cells :=
@onready var tilemap = get_node(“/root/Level/tilemap”)
var hitcell
var proxy_atlas = Vector2i(31, 2)
var processed =
var first_neighbor_processed = false
var is_scene = false
func _ready():
hitcell = tilemap.local_to_map(position)
tilemap.set_cell(main_layer, hitcell, 1, proxy_atlas)
animation.play(“default”)
pass # Replace with function body.

func _process(delta):
pass

func _on_area_2d_body_entered(body):
animation.play(“break”)
hitcell = tilemap.local_to_map(position)
tile_swap(hitcell)
pass # Replace with function body.

func _on_animated_sprite_2d_animation_finished():
queue_free()
pass # Replace with function body.

func tile_swap(cell_pos: Vector2i):
if cell_pos in processed:
return
processed.append(cell_pos)

var tile_data = tilemap.get_cell_tile_data(main_layer, cell_pos)
var ref_source_id = tilemap.get_cell_source_id(reference_layer, cell_pos)
var ref_atlas_coords = tilemap.get_cell_atlas_coords(reference_layer, cell_pos)
var can_swap = false
is_scene = false

if tile_data and tile_data.get_custom_data("is_wire"):
	can_swap = true
elif tilemap.get_cell_source_id(main_layer, cell_pos) != -1:
	can_swap = true
var source_id = tilemap.get_cell_source_id(reference_layer, cell_pos)
if source_id != -1 and not tile_data:
	is_scene = true
	var source = tilemap.tile_set.get_source(source_id)

if can_swap:
	if is_scene == false:
		tilemap.set_cell(main_layer, cell_pos, ref_source_id, ref_atlas_coords)
	else:
		if ref_source_id != -1:
			print("Swapping scene tile at ", cell_pos, " source ID: ", ref_source_id)
			tilemap.set_cell(main_layer, cell_pos, ref_source_id)
	for dir in [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]:
		var neighbor = cell_pos + dir
		if tilemap.get_cell_source_id(main_layer, neighbor) != -1:
			if not first_neighbor_processed:
				tile_swap(neighbor)
				first_neighbor_processed = true
			else:
				call_deferred("tile_swap", neighbor)

Hello,

In the documentation it says:

The atlas coordinate identifier atlas_coords identifies a tile coordinates in the atlas (if the source is a TileSetAtlasSource). For TileSetScenesCollectionSource it should always be Vector2i(0, 0),

The alternative tile identifier alternative_tile identifies a tile alternative in the atlas (if the source is a TileSetAtlasSource), and the scene for a TileSetScenesCollectionSource.

So I think you need to do something like this:

tilemap.set_cell(main_layer_cell, cell_pos, Vector2i(0, 0), scene)

I never tried this and I didn’t understand your problem 100% but I hope this will give you some sort of idea to how to solve it.

And a small note for you: It’d be nice if you use preformatted text when you share a code. It’s easier for everyone to understand. Example:

@export var is_wire := true
@onready var animation = $AnimatedSprite2D
@export var main_layer := 7 # Layer with interactive wires
@export var reference_layer := 8 # Layer with opposite states
var checked_cells := 
@onready var tilemap = get_node(“/root/Level/tilemap”)
var hitcell
var proxy_atlas = Vector2i(31, 2)
var processed = 
var first_neighbor_processed = false
var is_scene = false
func _ready():
hitcell = tilemap.local_to_map(position)
tilemap.set_cell(main_layer, hitcell, 1, proxy_atlas)
animation.play(“default”)
pass # Replace with function body.
1 Like

Hey, thanks for the help, I was able to solve the problem! Turns out it was actually two issues, the set_cell and the is_scene detection. I had just accidentally been reading the source id for the tile on my main layer instead of reference layer. As for the set_cell, your solution worked perfectly!

Here’s the updated script, hopefully formatted correctly this time

extends Node2D

@export var is_wire := true
@onready var animation = $AnimatedSprite2D
@export var main_layer := 7 # Layer with interactive wires
@export var reference_layer := 8 # Layer with opposite states
var checked_cells := []
@onready var tilemap = get_node("/root/Level/tilemap")
var hitcell
var proxy_atlas = Vector2i(31, 2) # obsolete fix now that scene tiles properly swap
var processed = []
var first_neighbor_processed = false
var is_scene = false

# Called when the node enters the scene tree for the first time.
func _ready():
	hitcell = tilemap.local_to_map(position)
	tilemap.set_cell(main_layer, hitcell, 1, proxy_atlas)
	animation.play("default")
	pass


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass

func _on_area_2d_body_entered(body):
	animation.play("break")
	hitcell = tilemap.local_to_map(position)
	tile_swap(hitcell)
	pass
	
func _on_animated_sprite_2d_animation_finished():
	queue_free()
	pass

func tile_swap(cell_pos: Vector2i):
	if cell_pos in processed: 
		return
	processed.append(cell_pos)

	var tile_data = tilemap.get_cell_tile_data(main_layer, cell_pos)
	var ref_source_id = tilemap.get_cell_source_id(reference_layer, cell_pos)
	var ref_atlas_coords = tilemap.get_cell_atlas_coords(reference_layer, cell_pos)
	var scene_id = tilemap.get_cell_alternative_tile(reference_layer, cell_pos)
	is_scene = false

	# Determine if this can be swapped
	var can_swap = tilemap.get_cell_source_id(main_layer, cell_pos) != -1

	# Check if ref tile is a scene
	if ref_source_id != -1 and not tile_data:
		var ref_source = tilemap.tile_set.get_source(ref_source_id)
		is_scene = ref_source is TileSetScenesCollectionSource

	# Do the actual swap
	if can_swap:
		if is_scene:
			tilemap.set_cell(main_layer, cell_pos, ref_source_id, Vector2i(0, 0), scene_id)
		else:
			tilemap.set_cell(main_layer, cell_pos, ref_source_id, ref_atlas_coords, scene_id)

		# check adjacent and recurse
		for dir in [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]:
			var neighbor = cell_pos + dir
			if tilemap.get_cell_source_id(main_layer, neighbor) != -1:
				if not first_neighbor_processed:
					tile_swap(neighbor)
					first_neighbor_processed = true
				else:
					call_deferred("tile_swap", neighbor)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.