Allow me to clarify more in this answer.
Raycast inspection(or any other physical inspection) only focuses on the physical world. They can not recognize one sprite on top of another, or something like that. They only care about the geometry of the colliders.
When you edit the scene with tilemaps, all the sprites in the tilemap share a single collider (TilemapCollider2D). So raycast can not tell which sprite you actually clicked. it can only tell you at what point does the collision occur.
So then the solution is clear: Let the sprite have its own gameobject and collider.
I found two ways to achieve it:
Option1:
Use TilemapExtrasPackage, Directly use ray inspection and sort the results.
Give each sprite a physical shape, and use gameobject brush to edit them into the scene. And you will get a lot of overlapping colliders like:

Use RaycastAll to find all colliders that the current screen point passes through, and sort them. Pick the first one.
Code:
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
var hits = new List<RaycastHit2D>(Physics2D.RaycastAll(ray.origin, ray.direction));
if (hits.Count > 0)
{
hits.Sort((l, r) =>
{
var p1 = l.transform.position;
var p2 = r.transform.position;
return (int)(((p1.y - p2.y) * 10 + (p2.z - p1.z)) * 10);
});
var target = hits[0].transform.GetComponent<SpriteRenderer>();
target.color = target.color == Color.red ? Color.green : Color.red;
}
}
}

Precautions:
- Set sort axis as (0,1,-0.49)

- The tilemap of interactive objects needs to set a smaller y value to facilitate sorting.
Option2:
If there are no additional interactable objects on the tile, the tile click area can be mapped algorithmically.
Core idea: split the grid into smaller-grained areas, record which grid covers each small area, traverse the grid from back to front, and refresh the record.

Assuming the grids are "half-height", That is to say a plot of 2 heights with the bottom side overlapping with a diamond behind it.(Most of the games I've seen are like this, and so are the Unity presets). Now divide a grid into 6 areas as follows,This is the smallest partition case:

To find out which areas a grid covers, we can do this:
For the grid of integer layers(Here is a multiple of 2), we can divide it into two parts:
The plot where it is located(red)
The area covered by each layer(blue and yellow):

For non-integer grids, just add the area occupied by the extra half-tile(pink):

When click hapens, Find out which area was clicked, and find the real grid that covered it last time. That's all.
Code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class NewBehaviourScript : MonoBehaviour
{
public GridLayout gridLayout;
public Tilemap tilemap;
private Dictionary<Tuple<int, int>, Vector3Int[]> tileInfoDict = new Dictionary<Tuple<int, int>, Vector3Int[]>();
void Start()
{
var bounds = tilemap.cellBounds;
var allTilePos = new List<Vector3Int>();
for (int x = bounds.xMin; x < bounds.xMax; x++)
{
for (int y = bounds.yMin; y < bounds.yMax; y++)
{
for (int z = bounds.zMin; z < bounds.zMax; z++)
{
var pos = new Vector3Int(x, y, z);
if (tilemap.HasTile(pos))
{
allTilePos.Add(pos);
}
}
}
}
allTilePos.Sort((left, right) =>
{
return (right.x + right.y) - (left.x + left.y);
});
foreach (var item in allTilePos)
{
DealInfo(item.x, item.y, item.z);
}
}
private void DealInfo(int x, int y, int z)
{
var fullLayerCount = z / 2;
var isHalfLayer = (z % 2) == 1;
var posNow = new Vector3Int(x, y, z);
var selfInfo = EnsureGetInfo(x, y);
for (int index = 0; index < 6; index++)
{
selfInfo[index] = posNow;
}
for (int i = 1; i <= fullLayerCount; i++)
{
var midX = x + i;
var midY = y + i;
//mid
var midInfo = EnsureGetInfo(midX, midY);
for (int index = 0; index < 6; index++)
{
midInfo[index] = posNow;
}
//left
var leftInfo = EnsureGetInfo(midX - 1, midY);
for (int index = 3; index < 6; index++)
{
leftInfo[index] = posNow;
}
//right
var rightInfo = EnsureGetInfo(midX, midY - 1);
for (int index = 0; index < 3; index++)
{
rightInfo[index] = posNow;
}
}
if (isHalfLayer)
{
var midX = x + fullLayerCount + 1;
var midY = y + fullLayerCount + 1;
//mid
var midInfo = EnsureGetInfo(midX, midY);
midInfo[2] = posNow;
midInfo[5] = posNow;
//left
var leftInfo = EnsureGetInfo(midX - 1, midY);
leftInfo[4] = posNow;
leftInfo[5] = posNow;
//right
var rightInfo = EnsureGetInfo(midX, midY - 1);
rightInfo[1] = posNow;
rightInfo[2] = posNow;
}
}
private Vector3Int[] EnsureGetInfo(int x, int y)
{
var key = new Tuple<int, int>(x, y);
if (!tileInfoDict.ContainsKey(key))
{
tileInfoDict.Add(key, new Vector3Int[6]);
}
return tileInfoDict[key];
}
private int GetAreaIndex(Vector3 p1, Vector3 p2)
{
var v = p2 - p1;
var gridX = gridLayout.cellSize.x;
var gridY = gridLayout.cellSize.y;
v = new Vector3(v.x * gridY / gridY, v.y, 0);
var v1 = new Vector3(-1, 1);//left
var v2 = new Vector3(1, 1);//right
var len = Mathf.Sqrt(2) * gridY / 4;
var m1 = Vector3.Project(v, v1).magnitude;
var m2 = Vector3.Project(v, v2).magnitude;
if (m1 > len)
{
if (m2 < len)
return 1;
else if (v.x > 0)
return 3;
else
return 0;
}
else
{
if (m2 > len)
return 4;
else if (v.x > 0)
return 5;
else
return 2;
}
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);
if (hit.collider != null)
{
Vector3Int cellPosition = gridLayout.WorldToCell(hit.point);
var key = new Tuple<int, int>(cellPosition.x, cellPosition.y);
if (tileInfoDict.ContainsKey(key))
{
var center = gridLayout.CellToWorld(cellPosition);
var index = GetAreaIndex(center, hit.point);
var targetPos = tileInfoDict[key][index];
if (targetPos != null)
{
tilemap.SetTileFlags(targetPos, TileFlags.None);
if (tilemap.GetColor(targetPos) == Color.red)
{
tilemap.SetColor(targetPos, Color.green);
}
else
{
tilemap.SetColor(targetPos, Color.red);
}
}
}
}
}
}
}