Search Unity

Clicking On Tiles With An Isometric Z As Y Tilemap?

Discussion in '2D' started by genowhirl9999, Apr 12, 2019.

  1. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    I can't seem to register what tile is being clicked on with a z as y tilemap. Using WorldToCell and GetTile gives me the tile located at the cell on the grid but if the tile you're clicking on has a significant z value it will appear over other cells and you end up registering the tiles behind the one you're actually clicking on.

    Is there any easy way around this? It seems like a relatively simple task.

    edit: Gave a more thorough description a few posts down.
     
    Last edited: Apr 15, 2019
  2. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    Any ideas? I'm really struggling to figure out a solution. I can try to explain it better if needed.
     
  3. Green11001

    Green11001

    Joined:
    Apr 14, 2018
    Posts:
    397
    not sure what your trying to say. Show pictures?
     
    genowhirl9999 likes this.
  4. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    Ah, sure thing, I'll post some when I get a chance.
     
    Last edited: Apr 14, 2019
  5. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11


    So all the tiles are on the grid shown here. Some are offset using a "z" value on the grid. With the "z as y" type of grid the "z" value on the tile is translated to the y-axis in world space instead because this is a completely 2D image despite appearing 3D.

    So the WorldToCell function takes a position in world space and gives you the cell on the grid that position is over. So it'll give me the cell my mouse is hovering over. The problem here is that this artificial z value adjusting the y of the tile OFF of the grid. So if I click on the now adjusted tile the WorldToCell doesn't take into account this y movement and will give me the position on the grid where I clicked, not the position the tile actually belongs to, which is what I need to know which tile was actually clicked.

    Does that help?
     
  6. Green11001

    Green11001

    Joined:
    Apr 14, 2018
    Posts:
    397
    I somewhat understand your problem. Maybe you can offset the click a bit before putting it into worldtocell?
     
  7. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    How do I know how much to offset it by and in which direction if I can't tell what is being clicked on?

    I tried some raycasting as well and it just gives me the collider for the whole tilemap and the point of collision not the specific tile that was collided with, seemingly. So that puts me back to not being able to get the correct tile based solely on a point in space.

    I've been researching this and I can't find an answer that works for this kind of tilemap, they all seem to rely on a combination of WorldToCell and GetTile. Any way I can ask the tilemap devs what they had in mind for this kind of thing? It seems impossible.
     
  8. MrMagister

    MrMagister

    Joined:
    Jan 8, 2019
    Posts:
    9
    Oh yeah.. I got the same problem) And you know, it's real problem. Because as I understand tile it just an object for rendering, and that all. For example, you can't even get some uniq instance of tile in tilemap, because all tiles it just scriptable objects, so it just settings for rendering. When you call GetTile from tilemap, you got ScriptableObject, that will be the same for all tiles with this type.

    So, to solve this problem I made some complex solution. Hope you will understand it))

    I've created MonoBehaviour, which finds in TileMap all Positions of tiles, that I need, than creates gameobjects for each tile and adds PolygonCollider to it. And for this PolygonCollider we take points from sprite of tile. And put this gameobjects to TileMap object and give them positions from GetCellCenterLocal method So, you have many gameobjects with colliders for each tile. But now you need to handle click on top collider if they crossed. And for this not enough to use default IPointerClickHandler, because this interface sorts colliders by Z as I understand and I don't know how to change it to sort by Y (if somebody will find it, I will glad to see solution). So, to solve it, my MonoBehaviour in Update method finds all Raycast hits and give you click for object, that has highest Z or if equal, then lowest Y.

    So, this is like general idea) Hope it will be helpful and maybe someone have some easiest solution or will be great to start discussion how to find this easiest solution.
     
  9. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    I don't doubt that works but that gives a similar problem to just using gameobjects instead of tiles. You're drastically increasing the overhead which kind of defeats the point of using tiles in the first place. There has to be a better way.
     
  10. Green11001

    Green11001

    Joined:
    Apr 14, 2018
    Posts:
    397
    I dont really have a better solution and if you cant find anything else perhaps you can try to code in some objects that are over the tilemap. (like clickboxes or whatever) that the player clicks and each of them correspond to a certain tile or a certain area of tiles.
     
  11. MrMagister

    MrMagister

    Joined:
    Jan 8, 2019
    Posts:
    9
    I don't say that you need to use gameobjects instead of tiles. In this case gameobjects will be just only contains collider and that all. Yeah, it's absolutely not an ideal solution, but I don't think, that this solution make really big overhead and defeats the point of using tiles in the first place. It's just a variant of "tile clicking handler" and no more. But if you find better solution, please share) I will be grateful.
     
  12. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    Yeah I might end up doing something like that but considering how basic this is (just click a tile) I was expecting there to be some kind of unity solution. Like did they not anticipate this issue when they designed the z as y tilemap? Or are they working on it? Am I using it wrong? I'd really love to hear from a dev on this, it's super confusing that something like this wouldn't have a easy solution.
     
  13. Salexey

    Salexey

    Joined:
    Jul 22, 2013
    Posts:
    2
    Each new height layer assumes that you will use a new layer of tilemaps(or sublayer). You should not have any offsets, or if you have them, they should be equal for their layer.
     
  14. genowhirl9999

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    11
    If that's the case why is there a built-in height system? Tutorials even show using the height offset like that. New tilemap might work as a workaround but I don't think that's the intended use case.
     
  15. dchapel1

    dchapel1

    Joined:
    Sep 11, 2019
    Posts:
    2
    I've figured out a solution to this, a bit late I know. You need to know the bounds of your isometric map, and for simplicity your tiles should all be above the z = 0 offset. Now, when you click, the world to cell function gives you a tile at the base of map with a z of 0. Create a traversal vector to check all the tile positions that could be in front of such a position;

    Code (CSharp):
    1. Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    2.             Vector3Int gridPos = terrain.WorldToCell(mousePos);
    3.             Vector3 travPos = gridPos;
    4.             Vector3Int checkPos = gridPos;
    5.             while (travPos.z < DataManager.instance.GetMapSize().z)
    6.             {
    7.                 travPos += new Vector3(-0.5f, -0.5f, 1);
    8.                 checkPos = new Vector3Int(Mathf.FloorToInt(travPos.x), Mathf.FloorToInt(travPos.y), Mathf.FloorToInt(travPos.z));
    9.                 if(terrain.HasTile(checkPos))
    10.                 {
    11.                     gridPos = checkPos;
    12.                 }
    13.             }
    The key is traversing by half a tile in the x and y direction for every z increment, and rounding to an integer value in order to check the tilemap for a tile.

    There's still a slight problem if you click on a tile (x,y,z) only partially occluded by another tile in a position (x+1, y+1, z+1) from it. I am going to see if I can figure something out for that.
     
  16. BTippen

    BTippen

    Joined:
    Jul 9, 2019
    Posts:
    6
    dchapel this is very good. Can I ask what is DataManager?
     
  17. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    just points to the current maximum z height of the map
     
  18. dchapel1

    dchapel1

    Joined:
    Sep 11, 2019
    Posts:
    2
    Since I made this post a while back I actually found a seemingly better solution, as the solution I posted above was pretty wonky in a lot of cases. Specifically if your map isn't just like a sort of deformed mesh with only 1 tile at the top. It's detailed here, in the top answer, but the other answers also detail some good solutions (including one like mine):
    https://stackoverflow.com/questions/21842814/mouse-position-to-isometric-tile-including-height

    And DataManager was just a place where I was keeping track of the maximum z value in the map. I never ended up doing an isometric game because of this. If I give it a go again I'll probably make it with a 3d terrain and just billboard sprites on it or something.
     
  19. Liktoria

    Liktoria

    Joined:
    Aug 6, 2020
    Posts:
    1
    This post is quite old already, but I struggeled with the same issue. In my case I wanted to change a tile once a character walked on it, but had to take the different height of the tiles into consideration. I solved this by creating a tilemap for each height level or z-value. Then I iterated over my tilemaps and adjusted the z value in each iteration. Afterwards I returned the correct z-value. This is what I came up with:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Tilemaps;
    5.  
    6. public class Whatever : MonoBehaviour
    7. {
    8.     public List<Tilemap> levelTilemapsAscending = new List<Tilemap>();
    9.     public Tilemap groundTilemap;
    10.     private Vector3 characterPosition;
    11.  
    12.         characterPosition = transform.position;
    13.         characterPosition.z = 0;
    14.         Vector3Int currentCell = groundTilemap.WorldToCell(characterPosition);
    15.         currentCell.z = calculateCorrectZ(currentCell);
    16.  
    17.     private int calculateCorrectZ(Vector3Int cellToCheck)
    18.     {
    19.         int z = 0;
    20.         for (int i = 0; i < levelTilemapsAscending.Count; i++)
    21.         {
    22.             cellToCheck.z = i;
    23.             if (levelTilemapsAscending[i].HasTile(cellToCheck))
    24.             {
    25.                 z = i;
    26.             }
    27.         }
    28.  
    29.         return z;
    30.     }
    31.  
    32. }
    With this I can determine the tilemap, that the highest tile is in and its position. The list of tilemaps is ordered by the z-value used on each tilemap. So levelTilemapsAscending[0] holds the tilemap where tiles are always painted with z=0 and so on.
     
  20. berber_hunter

    berber_hunter

    Joined:
    Apr 30, 2021
    Posts:
    14
    OP is using a single tilemap
     
  21. Ausmo

    Ausmo

    Joined:
    Aug 28, 2021
    Posts:
    1
    Hey, I just worked through this. Here is a solution with a single Tilemap that uses the z values for elevation. Tiles below z=0 are ignored so keep that in mind. Uses mouse input

    Code (CSharp):
    1. public class CursorTile : MonoBehaviour
    2. {
    3.  
    4.     //Tweak based on you tile origin; constant offset applied to mouse coordinates
    5.     public Vector3 mouseOffset = new Vector3(0, -.5f, 0);
    6.  
    7.     //y difference for each 1 unit of z; My tiles were .25 units tall
    8.     public float ydelta = -0.25f;
    9.     //0 to zMax are iterated through when selecting a tile
    10.     private int zMax;
    11.     private Tilemap t;
    12.  
    13.     void Start()
    14.     {
    15.         t = GameObject.FindObjectOfType<Tilemap>();
    16.         zMax = t.cellBounds.zMax;
    17.     }
    18.  
    19.     void Update()
    20.     {
    21.         if (Input.GetMouseButtonDown(0))
    22.             GetTopCell();
    23.     }
    24.  
    25.     public Vector3Int GetTopCell()
    26.     {
    27.  
    28.         Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    29.         Vector3Int cell = t.WorldToCell(mousePos);
    30.  
    31.         //Iterate through tile layers starting at the top
    32.         for (int i = zMax; i >= 0; i--)
    33.         {
    34.             //Adjust mouse world coordinate to include z (i)
    35.             Vector3 v = new Vector3(mousePos.x, mousePos.y + i * ydelta, i);
    36.             v += mouseOffset;
    37.             //check cell
    38.             cell = t.WorldToCell(v);
    39.             if (t.HasTile(cell))
    40.             {
    41.                 cell = getTopCell(cell);
    42.                 Debug.Log(i + " : " + cell);
    43.                 this.transform.position = t.CellToWorld(cell);
    44.                 break;
    45.             }
    46.         }
    47.  
    48.         return cell;
    49.     }
    50.  
    51.     //Get a cell that is not covered by any tiles
    52.     private Vector3Int getTopCell(Vector3Int cell)
    53.     {
    54.         //default to given cell
    55.         Vector3Int newCell = cell;
    56.  
    57.         //iterate from given cell -> upwards
    58.         for (int i = cell.z; i <= zMax; i++)
    59.         {
    60.             Vector3Int _cell = new Vector3Int(cell.x, cell.y, i);
    61.             if (t.HasTile(_cell))
    62.             {
    63.                 newCell = _cell;
    64.             }
    65.             else
    66.             {
    67.                 //break on empty and return last tile found
    68.                 break;
    69.             }
    70.         }
    71.         return newCell;
    72.     }
    73. }
     
  22. MacFelon

    MacFelon

    Joined:
    Dec 6, 2021
    Posts:
    1
    Wow, these are very interesting solutions. I think I can make this work for mine even. I fill a single tilemap from my server (massive world (1,000,0000 x 1,000,000 +) based mmorpg mutable world). I have to increment x and y one for each Z I go up (I move the rendering and tiles to be the Z level as the character as they move actually up and down in the game) but this should work. I wonder if I can take opacity into account as they go behind walls and blocking features... Hmmm.