How to Highlight a Tile on Click (Troubleshooting SetTile)

Hi,

I followed a tutorial to try to get one of my tiles to highlight on click (or mouseover), but it doesn’t seem to be working. This is my first time seeking help with Unity, so please bear with me.

In my scene I have a grid with two tilemaps as children: Interactive and Terrain. My intent is for Interactive to show the highlight using a HighlightTile sprite, while Terrain shows the default sprite.

The grid has the following script attached to it:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEngine.UIElements;


public class GridController : MonoBehaviour {

    private Grid grid;

    [SerializeField] private Tilemap interactiveMap = null;
    [SerializeField] private TileBase hoverTile = null;

    private Vector3Int previousMousePos = new Vector3Int();

    void Start() {
        grid = gameObject.GetComponent<Grid>();
    }

    void Update()
    {

        Vector3Int mousePos = GetMousePosition();

        if (!mousePos.Equals(previousMousePos)) {
            interactiveMap.SetTile(previousMousePos, null); // Remove old hoverTile
            interactiveMap.SetTile(mousePos, hoverTile);
            previousMousePos = mousePos;
        }

        if (Input.GetMouseButton(0))  {
            interactiveMap.SetTile(mousePos, hoverTile);
        }

        if (Input.GetMouseButton(1)) {
            interactiveMap.SetTile(mousePos, null);
        }

    }

    Vector3Int GetMousePosition() {
        Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        return grid.WorldToCell(mouseWorldPos);
    }

}

The code is currently redundant (using both mouse position and click) as I’m still just trying to get something to work. Also, I had to change the hoverTile type from Tile to TileBase, as I got a type conversion error when using SetTile.

And for reference, here’s what my setup looks like:

Right now, if I run the game, no matter whether I hover over the tiles or click on them, it does nothing. Any help or suggestions is appreciated. Also, let me know what other information might be needed to solve this problem.

Thanks in advance.

the mouse position isn’t the same as the tilemap ‘grid’ position. the mouse position is in screen coordinates.
Here’s an example of how to do it from my own code

using TilePlus;
using UnityEngine;
using UnityEngine.Tilemaps;

namespace TilePlusDemo
{
    /// <summary>
    /// An example of how to control a tile from a mouse-click.
    /// </summary>
    public class TpPickTile : MonoBehaviour
    {
        /// <summary>
        ///The tilemap
        /// </summary>
        [Tooltip("The tilemap")]
        public Tilemap m_Tilemap;

        /// <summary>
        /// The camera
        /// </summary>
        [Tooltip("The camera")]
        public Camera m_Camera;

        /// <summary>
        /// Delay between moves used for debouncing
        /// </summary>
        [Tooltip("Delay between moves for debouncing, min=0.2")]
        public float m_Delay = 0.2f;

        //used for debouncing
        private float lastTime;

        //used for debouncing
        private float timeAccum;

        private void Update()
        {
            // +debounce
            timeAccum += Time.deltaTime;

            if (!Input.GetMouseButton(0))
                return;

            if (m_Delay < 0.2f)
                m_Delay = 0.2f;
            if (timeAccum < (lastTime + m_Delay))
                return;
            lastTime = timeAccum;

            // -debounce
           
           
            //get the mouse position
            var screenPos = Input.mousePosition;
            //test to ensure that it's within the visible area
            if (screenPos.x < 0 ||
                screenPos.y < 0 ||
                screenPos.x > Screen.width ||
                screenPos.y > Screen.height)
                return;
           
            //get the tilemap grid position
            var worldPos = m_Camera.ScreenToWorldPoint(screenPos);
            var gridPos = m_Tilemap.WorldToCell(worldPos);
            
            /*
            Your code goes here - gridpos is the tile coordinate

            */

        }
    }
}

Hope this helps.

Thanks. This worked, although for some reason I had to add +10 to the Z coordinate of each grid position. Any ideas as to why this was needed? Not really necessary to know, but it’d be helpful for future reference.

Here’s my (now working) code:

using UnityEngine;
using UnityEngine.Tilemaps;

public class GridController : MonoBehaviour {

    [SerializeField] private Tilemap m_Tilemap = null;
    [SerializeField] private Camera m_Camera = null;
    [SerializeField] private TileBase selectedTile = null;

    private Vector3Int previousGridPos = new Vector3Int();

    /// <summary>
    /// Delay between moves used for debouncing
    /// </summary>
    public float m_Delay = 0.2f;

    //used for debouncing
    private float lastTime;

    //used for debouncing
    private float timeAccum;

    void Update()
    {

        // +debounce
        timeAccum += Time.deltaTime;

        if (!Input.GetMouseButton(0))
            return;

        if (m_Delay < 0.2f)
            m_Delay = 0.2f;
        if (timeAccum < (lastTime + m_Delay))
            return;
        lastTime = timeAccum;

        // -debounce

        //get the mouse position
        var screenPos = Input.mousePosition;
        //test to ensure that it's within the visible area
        if (screenPos.x < 0 ||
            screenPos.y < 0 ||
            screenPos.x > Screen.width ||
            screenPos.y > Screen.height) {
            return;
        }

        var worldPos = m_Camera.ScreenToWorldPoint(screenPos);
        var gridPos = m_Tilemap.WorldToCell(worldPos);

        //test to ensure that it's within the visible area
        if (screenPos.x < 0 ||
            screenPos.y < 0 ||
            screenPos.x > Screen.width ||
            screenPos.y > Screen.height)
            return;

        if (!gridPos.Equals(previousGridPos))
        {
            var adjPos = gridPos + new Vector3Int(0, 0, 10);
            var adjPrev = previousGridPos + new Vector3Int(0, 0, 10);
            Debug.Log("gridPos: " + adjPos);
            Debug.Log("previousGridPos: " + adjPrev);
            m_Tilemap.SetTile(adjPrev, null); // Remove old tile
            m_Tilemap.SetTile(adjPos, selectedTile);
            previousGridPos = gridPos;
        }
    }
}

One thing - you don’t need this twice, although it shouldn’t hurt anything


//test to ensure that it's within the visible area
if (screenPos.x < 0 ||
screenPos.y < 0 ||
screenPos.x > Screen.width ||
screenPos.y > Screen.height)
return;

the only reason I can think of for requiring the offset is that you’re using a custom orientation or Tile Anchor. If a normal XY tilemap’s Z position is 0 and there’s no custom orientation or tile anchor then the Z parameter should be zero.

If the cells are in zero Z then reset the Z i.e. “worldPos.z = 0” before converting to tile-space.

I should have thought of that one… the way I’m using it the Z doesn’t matter (not using Tilemap.GetTile). My bad :frowning:

It’s easily overlooked just as are things like finding the distance between two Vector3 then normalising and putting in a Vector2 and wondering why it’s wrong.

It’s probably worth always using a Vector2Int if all the cells exist in the logical position Z=0. There’s an implicit conversion operator from Vector3Int I believe.