Programmatically setting a cell with an atlas tile respecting priority

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By hanke

I’m trying to place an atlas tile from a tileset, picked at random, respecting its priority. For this, I am using tilemap.set_cell:

# index == 0 (the index shown when holding alt when editing the tileset)
tilemap.set_cell(x, y, index)

But this always places the tile from position (0, 0).

Autotiles work perfectly, but not atlas tiles. I can use set_cell with the autotile_coord to select a specific subtile from the atlas tile, but that defeats the purpose of the engine selecting a random atlas subtile for me.

How can I have Godot select an atlas subtile in code?

P.S: I found this from 2019: https://forum.godotengine.org/48904/set-tileset-atlas-subtile-from-code?show=48904#q48904 (but has functionality been added?)

:bust_in_silhouette: Reply From: hanke

Shortly after asking, I decided to just write a solution. I hope this helps a future problem-haver:

# Places a randomly chosen atlas tile based on priority at x, y.
#
func place_atlas_tile(tilemap : TileMap, tileset_atlas_index : int, x : int, y : int) -> void:
    var atlas_list = generate_atlas_list(tilemap, tileset_atlas_index)
    var autotile_coord = atlas_list[randi() % atlas_list.size()]
    tilemap.set_cell(
        x,
        y,
        tileset_atlas_index,
        false,
        false,
        false,
        autotile_coord)

# Generates an array of subtile coords that can be used for fast autotile subtile selection.
# Even though it may seem wasteful, an array is most of the time more performant
# than a Dictionary solution.
#
# Note: Reuse this when placing a batch of tiles.
#
func generate_atlas_list(tilemap : TileMap, tileset_atlas_index : int) -> Array:
    var cell_size = tilemap.cell_size
    var array = Array()
    var tileset : TileSet = detail_tilemap.tile_set
    var region = tileset.tile_get_region(tileset_atlas_index)
	var start = region.position / cell_size
	var end = region.end / cell_size
	for x in range(start.x, end.x):
	    for y in range(start.y, end.y):
            var autotile_coord = Vector2(x, y)
            var priority = tileset.autotile_get_subtile_priority(detail_tileset_index, autotile_coord)
        	    for p in priority:
                    array.append(autotile_coord)
	return array
:bust_in_silhouette: Reply From: SoyKaf

ok, I’m not too sure about the solution provided by hanke, since I tried it and firstly, it had some variables that weren’t defined. and secondly, it created very weird results by grabbing tiles outside the actual atlas zone.

Here’s my version of it which is simpler and works as expected.
Kuddos to GammaGames for providing part of the solution.

func set_cell(tilemap, x, y, id):
	tilemap.set_cell(x, y, id, false, false, false, get_subtile_with_priority(id,tilemap))

func get_subtile_with_priority(id, tilemap: TileMap):
	var tiles = tilemap.tile_set
	var rect = tilemap.tile_set.tile_get_region(id)
	var size_x = rect.size.x / tiles.autotile_get_size(id).x
	var size_y = rect.size.y / tiles.autotile_get_size(id).y
	var tile_array = []
	for x in range(size_x):
		for y in range(size_y):
			var priority = tiles.autotile_get_subtile_priority(id, Vector2(x ,y))
			for p in priority:
				tile_array.append(Vector2(x,y))

	return tile_array[randi() % tile_array.size()]