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:
    10
    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:
    10
    Any ideas? I'm really struggling to figure out a solution. I can try to explain it better if needed.
     
  3. A_Person_Named_Dart

    A_Person_Named_Dart

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

    genowhirl9999

    Joined:
    Jun 24, 2016
    Posts:
    10
    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:
    10


    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. A_Person_Named_Dart

    A_Person_Named_Dart

    Joined:
    Apr 14, 2018
    Posts:
    367
    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:
    10
    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. KilatiF

    KilatiF

    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:
    10
    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. A_Person_Named_Dart

    A_Person_Named_Dart

    Joined:
    Apr 14, 2018
    Posts:
    367
    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. KilatiF

    KilatiF

    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:
    10
    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:
    10
    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. m_craig36

    m_craig36

    Joined:
    Apr 30, 2021
    Posts:
    1
    OP is using a single tilemap
     
unityunity