Recently I got the same problem. I have found some solution and might want to share it with the world because, Google Search for this problem becomes unfruitful.
Yes, you could change individual TileData, without it affecting all the TileMap. So you could randomly modulate color for every Tile without creating special Alternative Tile to do it.
For it to works, you need to extends TileMap Node, there’s 2 function that you need override there:
bool _use_tile_data_runtime_update ( int layer, Vector2i coords )
void _tile_data_runtime_update ( int layer, Vector2i coords, TileData tile_data )
In _use_tile_data_runtime_update
, you should return true if the appearance of the tile need to be updated, for example the color will change. By default it’s returning false
. You could force it to return true
all the time, but I guess it will impact the performance.
The second one is the one that’s the main part. _tile_data_runtime_update
will be called if _use_tile_data_runtime_update
returning true
. It will supply you with the layer and coordinates of the tile that is changing, and also the TileData of that tile. In here, you could change the TileData without it affecting all the other Tile. It’s only changing that specific tile in that layer and coordinates.
But sometimes you need to change the tile from the external code, and handling this in TileMap seems counterintuitive. I’ve made some helpers function to accomplish this.
extends TileMap
class_name UpdateTileMap
var _update_fn: Dictionary = {} # of Dictionary of Callable
func update_tile(layer: int, coords: Vector2i, fn: Callable):
if not _update_fn.has(layer): _update_fn[layer] = {}
_update_fn[layer][coords] = fn
notify_runtime_tile_data_update(layer)
func _use_tile_data_runtime_update(layer: int, coords: Vector2i):
if not _update_fn.has(layer): return false
if not _update_fn[layer].has(coords): return false
return true
func _tile_data_runtime_update(layer: int, coords: Vector2i, tile_data: TileData):
if not _update_fn.has(layer): return false
if not _update_fn[layer].has(coords): return false
var fn: Callable = _update_fn[layer][coords]
fn.call(tile_data)
_update_fn[layer].erase(coords)
With this new extension, you could update the Tile in Tilemap with:
tilemap.update_tile(0, Vector2i(4,4), func (tile_data:TileData): tile_data.modulate = Color.RED)
Basically all the change will be noted in the Dictionary, and we will notify the tilemap that something has changed with notify_runtime_tile_data_update(layer)
.
Then after it updates the change, it will remove the note, so it won’t be updated again in the next frame if it doesn’t change.
Hope it helps.