godot4

How to Highlight a Tile Under Cursor without Removing it in Godot 4.3?


Hi everyone I am working with Godot 4.3 and struggling with the new tile system while trying to "highlight" a tile when hovered over with the cursor. I want to replace a cell's visual appearance without actually removing or replacing the cell data.

I've used the following function to manipulate tiles:

void set_cell(int layer, Vector2i coords, int source_id = -1, Vector2i atlas_coords = Vector2i(-1, -1), int alternative_tile = 0)

However, my implementation seems to remove the cell instead of just highlighting it. Here's the relevant part of my code:

extends TileMap

var highlighted_tile_position: Vector2i = Vector2i(-1, -1)
var highlight_tile_id:Vector2i = get_cell_atlas_coords(layer, Vector2i(0, 1))  # Ensure this is a valid and visible tile ID

func _process(delta: float):
    var mouse_pos = to_local(get_global_mouse_position())
    var tile_pos = local_to_map(mouse_pos)

    if tile_pos != highlighted_tile_position:
        # Reset the previously highlighted tile if valid
        if highlighted_tile_position != Vector2i(-1, -1) and get_cell_source_id(layer, highlighted_tile_position) != -1:
            set_cell(layer, highlighted_tile_position, get_cell_source_id(layer, highlighted_tile_position))  # Reset to its original ID

        # Highlight the new tile if valid
        if get_cell_source_id(layer, tile_pos) != -1:
            highlighted_tile_position = tile_pos
            set_cell(layer, tile_pos, 0, highlight_tile_id)  # Attempt to highlight new tile

The issue seems to be in the way I'm using set_cell as it erases the tile instead of highlighting it. I suspect my source_id, atlas_coords, and alternative_tile parameters might be incorrect.

Questions:

How do I correctly set these parameters to change the tile's appearance without removing the underlying data? Is there a more appropriate way to temporarily alter a tile's appearance on hover? Any insights or examples would be greatly appreciated.


Solution

  • Setting cells via set_cell() works best when you want your terrain to change dynamically at runtime in response to in-game events (such as explosions altering the game field) because the engine uses the data stored for each tile for auto-tiling, collisions, and physics interaction.

    On the other hand, a marker that highlights tiles where the mouse cursor is only serves visual purposes; it doesn't belong to the tilemap entity but to the user interface domain.

    Since you want to preserve underlying tile data, I strongly recommend treating such marker as a separate, independent Node. This way, you can control its behaviour, change its appearance, animate it, and much more.

    I propose a solution where your TileMap (or TileMapLayer, the actual "new tile system") controls a child Node acting as a highlight marker and following the mouse cursor while snapping to the tilemap grid:

    extends TileMap
    
    @onready var marker: Node2D = get_node("Marker") # Child Node2D
    
    
    func _process(delta: float):
        var mouse_pos = get_local_mouse_position()
        var cell_pos = local_to_map(mouse_pos)
        highlight_cell(cell_pos)
    
    
    func highlight_cell(cell_position: Vector2i):
        marker.position = map_to_local(cell_position)
    
    

    The marker Node can be anything you want: Sprite2D, AnimatedSprite2D, or even a plain Node2D with some custom drawing such as:

    extends Node2D
    
    @export var size := Vector2.ONE * 14.0
    @export var color := Color(1.0, 1.0, 0.0, 0.5)
    
    
    func _draw():
        draw_rect(Rect2(-size / 2.0, size), color)
    

    Here's the result (tileset credits: Happyland by Buch on OGA):

    Showcase of proposed solution