Drag and Drop a scene tiles node in TileMapLayer

Godot Version

4.3 beta 1

Question

I’m using a TileMapLayer with scenes to act as a board for a card game, for various reasons.

I’ve managed to implement a drag and drop of cards on the tilemap, which works visually. Cards are snapped to relevant grid positions without issue.

However, I’m wanting to make use of the tilemap’s get_used_cells() function to perform some game logic.

On my card object, I store a “old_tilemap_coords” variable, keeps track of the tilemap coordinates of the card.

I thought I might be able to do something like this:

# Assume "coord" is the new position where the tile is to be placed.
# Assume "item" is a reference to the selected card object

    # place it in the new position
    set_cell(coord, 
        get_cell_source_id(card.old_tilemap_coords), 
        get_cell_atlas_coords(card.old_tilemap_coords), 
        get_cell_alternative_tile(card.old_tilemap_coords))
    
    # now remove the cell from the old position
    erase_cell(item.old_tilemap_coords)

This does update the get_used_cells() result, but the card is effectively replaced with a new node, which has different properties.

This is fine, set_cell() doesn’t specify that it can reference things like this.

So to solve my problem, is my only option to write some method to copy over the relevant data properties of the source node and then apply them post the erase_cell() procedure?

# Assume "coord" is the new position where the tile is to be placed.
# Assume "item" is a reference to the selected card object
   
    var some_var = item.some_data

    # place it in the new position
    set_cell(...)
    
    # now remove the cell from the old position
    erase_cell(item.old_tilemap_coords)

    # Now re-apply data from original card
    for i in range(CardRack.get_child_count()):
        if CardRack.get_child(i) is CardBase:
            if CardRack.get_child(i).coordinates == coord:
                CardRack.get_child(i).some_data = some_var

Or is there something built in that I am not aware of that might make my life easier?

Do you mean that you have placeholder card tiles that you replace with Card scenes? If yes, can you use custom data layers (TileData) to store the relevant data? Of course, this does not work if the data will change during gameplay. In that case, I don’t think there is a way to store that data with TileMap and you should try some other approach. I have not tried 4.3 but I assume it’s not that different in terms of how TileData works.

I believe this has been an issue for some time now with erase cell and scene based tilesets. I see the same issue you’re having in the bug reports since the release of version 4.0. The same issue existed in TileMap so I imagine it brought its unfixed issues with it to TileMap layer

Unfortunately not, cards have data that I expect to change during gameplay.

I implemented a data class which emits a signal when data is changed, as a means of copying the data across and changing the newly created card to have the same data as the old card. The method “set_card_data” below also causes a signal to be emitted if any of the variables are changed, which I use to update the value shown on the card.
But it causes the card to “flash”

    # place it in the new position
    set_cell(...)
    
    # Required, or else the loop below doesn't find the child
    update_internals()

    # Now re-apply the data
    for i in range(CardRack.get_child_count()):
        if CardRack.get_child(i) is CardBase:
            if CardRack.get_child(i).tilemap_coords == coord:
                CardRack.get_child(i).card_data.set_card_data(temp_data)
    # now remove the cell from the old position
    erase_cell(card.tilemap_coords)

Do you have any other suggestions?

Is it a bug, or just an unsupported feature? I’ve read that this sort of thing isn’t supported due to the internal functionality of Tilemaps(layers): Allow new Tilemap cells with scenes to return the node/scene they create · Issue #4833 · godotengine/godot-proposals · GitHub

A quick update.
The flashes in the above approach are caused because I scale the card with an “on_hover” animation, and because I’m changing instances, the card starts at default scale, detects the onhover, then scales up.

I could workaround this now, but I’m likely to change card appearances, add animations, etc, so any workaround at the moment will likely need to change in the future.

Needless to say my above approach seems fine for now - eager to hear if anyone else has any other suggestions though.

Have you considered using normal tiles and then replacing them with actual scenes (not scene tiles) and moving those around instead of creating new instances? That way you could still use the TileMap as a scene painter if that is one of the reasons you want to use it. Then create a dictionary that keeps track of cards and their coordinates (coords as keys and cards as values) and update it whenever a card is moved.

Sorry if my suggestion is not very helpful, I may have misunderstood your problem.