Godot Version
v4.4.1.stable.official [49a5bc7b6]
Question
I’m having a hard time believing that ther’s no easy way to get the instance of a scene at any given coordinates from a TileMapLayer. I have a piece of code that generates a random maze using recursive backtracking, and puts the result into a 2D array, and then I use that to generate the maze on a TileMapLayer, where I use two different scenes, one scene for the walls, and one for the actual tile the player can pass over and change.
func DrawMazeFromArray(maze: Array, tilemap: TileMapLayer) -> void:
if not is_instance_valid(tilemap):
push_error("Invalid TileMap reference")
return
var height = maze.size()
if height == 0:
push_error("Empty maze array")
return
var width = maze[0].size()
if width == 0:
push_error("Empty maze row")
return
tilemap.clear()
for y in range(height):
for x in range(width):
var cell_value = maze[y][x]
match cell_value:
WALL:
tilemap.set_cell(Vector2i(x, y), 2, Vector2i.ZERO, 1)
UNVISITED:
push_warning("This tile should not be here!
Something went wrong during generation!")
VISITED:
tilemap.set_cell(Vector2i(x, y), 2, Vector2i.ZERO, 2)
_:
push_warning("Unknown maze cell value: " + str(cell_value))
However, as I find, there’s absolutely no way to get an instance back, even if I know it’s exact coordinates?
You can get the packedscene for a given cord but not the specific instance that I can see using the tilemap layer node and tileset.
If you do want the specific instances I think what you could do is loop through the children of the tilemap layer (which is where the tiled scenes are placed) and check if it matches the packed scene and then check if its cords match.
Here’s an example that I got working
extends Node2D
@onready var tile_map_layer: TileMapLayer = $TileMapLayer
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if !event.is_pressed():
return
var clicked_cell = tile_map_layer.local_to_map(tile_map_layer.get_local_mouse_position())
var sourced_id = tile_map_layer.get_cell_source_id(clicked_cell)
if sourced_id > -1:
var scene_source = tile_map_layer.tile_set.get_source(sourced_id)
if scene_source is TileSetScenesCollectionSource:
var alt_id = tile_map_layer.get_cell_alternative_tile(clicked_cell)
var scene = scene_source.get_scene_tile_scene(alt_id)
var pos = tile_map_layer.map_to_local(clicked_cell)
for child in tile_map_layer.get_children():
if child.scene_file_path == scene.resource_path:
if child.global_position == pos:
print(child)
I tried something like this before, but for some reason, no matter what I do, get_children for my TileMapLayer ALWAYS returns an empty collection, even when I obviously have the entire maze drawn out with my tiles.
Here’s anexmple project of what I got working…
wonder what could be different with your project…
So as it turns out, if I do it like this, get_children is always empty. If I call get_children at the bottom of the script I showed in my original post, it’s also empty, but I got around it by using call_deferred
at the bottom of my script I showed that actually draws / makes the TileMapLayer. I’m not sure why this is a thing?
1 Like
That makes sense if you are trying to get the children on ready.
"For performance reasons, all TileMap updates are batched at the end of a frame. Notably, this means that scene tiles from a TileSetScenesCollectionSource may be initialized after their parent. This is only queued when inside the scene tree.
To force an update earlier on, call update_internals()."
So I guess you could also use update_internals for tilemap layer as an alt to using a deferred call.
But I’m not trying to get the children on ready. The generation of this maze happens minutes into the gameplay.
Either way, I’ve decided to go with a different approach, and kinda just gave up on trying to GET that instance, because doing it this way feels incredibly hacky, and I honestly think it’s a massive issue that Godot doesn’t include this function by default, so I just use signals to do what I must. I got it working by calling the following function with call_deferred
right after I generate and draw the maze:
func ConnectSignals():
var children = TMapLayer.get_children()
for child in children:
if child is NormalTile:
child.Completed.connect(HandlePlayerMoved)