+2 votes

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://godotengine.org/qa/48904/set-tileset-atlas-subtile-from-code?show=48904#q48904 (but has functionality been added?)

Godot version 3.3
in Engine by (52 points)
reshown by

2 Answers

+2 votes
Best answer

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
by (52 points)
selected by
+1 vote

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()]
by (16 points)
edited by
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.