Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Use Tilemaps to get a 2D isometric grid

Discussion in 'Scripting' started by stefaxel, Aug 28, 2023.

  1. stefaxel

    stefaxel

    Joined:
    Oct 8, 2021
    Posts:
    43
    I thought a good way to implement a grid of nodes would be to use an existing tilemap. I have designed a 2D isometric tile, that is 128x64 pixels, and a block which is 128x128 pixels. The grid displays but is offset to where it should be, I'm not sure if I have something wrong in the settings for tilemaps or if I have made a mistake with the script.

    Code (CSharp):
    1. public class Grid : MonoBehaviour
    2. {
    3.    
    4.     public Tilemap tilemap; // Reference to the isometric tilemap
    5.     public LayerMask walkableLayerMask; // For obstacle detection
    6.  
    7.     Node[,] grid;
    8.  
    9.     int gridSizeX, gridSizeY;
    10.  
    11.     void Start()
    12.     {
    13.         gridSizeX = tilemap.cellBounds.size.x; // x-axis grid size from tilemap
    14.         gridSizeY = tilemap.cellBounds.size.y; // y-axis grid size from tilemap
    15.         CreateGrid();
    16.     }
    17.  
    18.     void CreateGrid()
    19.     {
    20.         grid = new Node[gridSizeX, gridSizeY];
    21.  
    22.         BoundsInt bounds = tilemap.cellBounds; // boundaries of tilemap in cell co-ordinates
    23.         TileBase[] allTiles = tilemap.GetTilesBlock(bounds); // Retrieves all tiles in the given bounds
    24.  
    25.         for (int x = 0; x < gridSizeX; x++) // For x-axis tiles
    26.         {
    27.             for (int y = 0; y < gridSizeY; y++) // For y-axis tiles
    28.             {
    29.                 TileBase tile = allTiles[x + y * bounds.size.x];
    30.  
    31.                 Vector3 cellWorldPos = tilemap.GetCellCenterWorld(new Vector3Int(x, y, 0)); // Gets tilemap centre
    32.                 bool walkable = !(Physics2D.OverlapCircle(cellWorldPos, 0.1f, walkableLayerMask));
    33.                 grid[x, y] = new Node(walkable, cellWorldPos); // Create a Node instance and store it
    34.             }
    35.         }
    36.     }
    37.  
    38.     public Node NodeFromWorldPoint(Vector3 worldPosition)
    39.     {
    40.         // Calculate the percentage of the x and y co-ordinates within the tilemap
    41.         float percentX = Mathf.Clamp01((worldPosition.x + tilemap.cellSize.x * 0.5f - tilemap.origin.x) / tilemap.size.x);
    42.         float percentY = Mathf.Clamp01((worldPosition.y + tilemap.cellSize.y * 0.5f - tilemap.origin.y) / tilemap.size.y);
    43.  
    44.         // Calculate the array indices based on the percentages
    45.         int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
    46.         int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
    47.         return grid[x, y];
    48.     }
    49.  
    50.     void OnDrawGizmos()
    51.     {
    52.         Gizmos.DrawWireCube(transform.position, new Vector3(gridSizeX, gridSizeY, 1));
    53.  
    54.         if (grid != null)
    55.         {
    56.             foreach (Node n in grid)
    57.             {
    58.                 Gizmos.color = n.isWalkable ? Color.white : Color.red;
    59.                 Gizmos.DrawCube(n.position, Vector3.one * (Mathf.Min(tilemap.cellSize.x, tilemap.cellSize.y) - 0.1f));
    60.             }
    61.         }
    62.     }
    63. }
    I think the grid is of by about 31,22, from quickly counting it.
     

    Attached Files:

  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Start digging into the scene when it's running, see what all the positions of Transforms and Grids are. Anything that isn't at (0,0,0) with identity rotation and scaling would cause offset.
     
  3. stefaxel

    stefaxel

    Joined:
    Oct 8, 2021
    Posts:
    43
    The position of the tilemap grid is at 0,0,0 the scale is 1,1,1. The cell size is 1,0.5,1 the cell gap is 0,0,0 it is set to Isometric and Cell Swizzle XYZ. The transform for the ground, child of the grid is at 0,0,0, there is no rotation and scale is 1,1,1. The Tilemap component has Tile Anchor of 0,0,0 and orientation of XY, which were the default when setting up the tilemap. For the tile, its set to 128 pixels per unit, the pivot is set to bottom.

    I have placed the Grid script to be component of the Grid, I have set the layer mask for the unwalkable parts and the tilemap to the ground tilemap.
     
  4. stefaxel

    stefaxel

    Joined:
    Oct 8, 2021
    Posts:
    43
    I have found that the problem is with my code. I have tried to rewrite it and used some Debug.Log to find what values I'm getting tilemap.origin is at (-17,-21,0), I would have expected it to be at (0,0,0) but tilemap.transform.position gives this instead. Is there a way to get local origin, like you can with local position of a transform.

    Code (CSharp):
    1. void GenerateNodes()
    2.     {
    3.         grid = new Node[numTilesX, numTilesY];
    4.  
    5.         Vector3 tilemapOrigin = tilemap.origin;
    6.         Debug.Log(tilemapOrigin.ToString());
    7.         //Prints the origin as (-17,-21,0) for the current grid 40,38
    8.  
    9.         Debug.Log(tilemap.transform.localPosition.ToString());
    10.         Vector3 actualOrigin = tilemap.transform.position;
    11.         //Prints at (0,0,0)
    12.  
    13.         Vector3 cellSize = tilemap.cellSize;
    14.         Debug.Log(cellSize.ToString());
    15.  
    16.         Vector3 originOffset = actualOrigin - tilemapOrigin;
    17.         Debug.Log("The originOffset xyz is, " + originOffset.ToString());
    18.  
    19.         for (int x = 0; x < numTilesX; x++)
    20.         {
    21.             for (int y = 0; y < numTilesY; y++)
    22.             {
    23.                 Vector3Int cellPosition = new Vector3Int(x, y, 0);
    24.  
    25.                 Vector3 cellCentreWorld = tilemap.GetCellCenterWorld(cellPosition);
    26.  
    27.                 bool walkable = !(Physics2D.OverlapCircle(cellCentreWorld, 0.1f, walkableLayerMask));
    28.                 Node node = new Node(walkable, cellCentreWorld);
    29.                 grid[x, y] = node;
    30.             }
    31.         }
    32.     }
     
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    stefaxel and Kurt-Dekker like this.
  6. stefaxel

    stefaxel

    Joined:
    Oct 8, 2021
    Posts:
    43
    Thank you for the help. Although I have still gone wrong somewhere. Where I was using this line of code Vector3 cellCentreWorld = tilemap.GetCellCenterWorld(cellPosition); I was getting this first screenshot. upload_2023-8-30_22-29-39.png

    Using CellToWorld(cellPosition) I'm getting the nodes being placed at the corner of each cell on the grid. Its still not in the correct cell, it should be placed in that very bottom cell.
    upload_2023-8-30_22-31-27.png
     
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    This is the grid layout so I'm not sure what exactly you mean by "bottom cell". Do you mean the cell lowest to the bottom of the image? If so, that cell is likely a negative cell position.

    The very bottom cell isn't always (0,0,0) as that would mean all the cell positions change as you paint. If you paint a cell at (0,0,0) and everything positive then it will be but it's totally up to where you paint. Is this what is confusing perhaps?
     
  8. stefaxel

    stefaxel

    Joined:
    Oct 8, 2021
    Posts:
    43
    I managed to figure it out. I orginally thought that Vector3 tilemapOrigin = tilemap.origin; would be 0,0,0 but it wasn't. My nested for loop started at 0,0,0 which is the world position, not the local position of that cell. So it was that, that needed to be offset.

    If anyone else has this issue here is my code:
    Code (CSharp):
    1. public class Grid : MonoBehaviour
    2. {
    3.     public Tilemap tilemap; // Reference to the isometric tilemap
    4.     public Grid gridBase;
    5.     public LayerMask walkableLayerMask; // For obstacle detection
    6.     public Transform playerPosition;
    7.  
    8.     Node[,] grid;
    9.  
    10.     private int numTilesX; // Number of tiles in the X axis
    11.     private int numTilesY; // Number of tiles in the Y axis
    12.  
    13.     void Start()
    14.     {
    15.         numTilesX = CalculateNumTilesX(tilemap.cellBounds);
    16.         numTilesY = CalculateNumTilesY(tilemap.cellBounds);
    17.  
    18.         Debug.Log("Number of tiles on the x-axis = " + numTilesX.ToString() + ", on the y-axis = " + numTilesY.ToString());
    19.         //Prints 40,38 which are the number of tiles in scene view.
    20.  
    21.         GenerateNodes();
    22.     }
    23.  
    24.     int CalculateNumTilesX(BoundsInt bounds)
    25.     {
    26.         return bounds.size.x;
    27.     }
    28.  
    29.     int CalculateNumTilesY(BoundsInt bounds)
    30.     {
    31.         return bounds.size.y;
    32.     }
    33.  
    34.     void GenerateNodes()
    35.     {
    36.         grid = new Node[numTilesX, numTilesY];
    37.  
    38.         Vector3 tilemapOrigin = tilemap.origin; //Origin of the tilemap
    39.        
    40.         Vector3 actualOrigin = tilemap.transform.position; //Actual position of the tilemap
    41.        
    42.         Vector3 originOffset = actualOrigin - tilemapOrigin; //Calculation to offset the node to the correct tile
    43.  
    44.         for (int x = 0; x < numTilesX; x++)
    45.         {
    46.             for (int y = 0; y < numTilesY; y++)
    47.             {
    48.                 Vector3Int cellPosition = new Vector3Int(x, y, 0);
    49.  
    50.                 cellPosition -= Vector3Int.FloorToInt(originOffset); //Places the nodes on the correct tile
    51.  
    52.                 Vector3 cellCentreWorld = tilemap.GetCellCenterWorld(cellPosition);
    53.  
    54.                 // Check if a tile exists in the current cell
    55.                 bool hasTile = tilemap.HasTile(cellPosition);
    56.  
    57.                 if (!hasTile)
    58.                 {
    59.                     grid[x, y] = null; // Skip this cell
    60.                     continue;
    61.                 }
    62.  
    63.                 bool walkable = !(Physics2D.OverlapCircle(cellCentreWorld, 0.1f, walkableLayerMask)); //Check to see if tile is able to walked on
    64.                 Node node = new Node(walkable, cellCentreWorld); //For the Node, gives information for if the tile is walkable
    65.                 grid[x, y] = node;
    66.  
    67.  
    68.             }
    69.         }
    70.     }
    71.  
    72.  
    73.  
    74.     // Draw Gizmos in the Scene view
    75.     void OnDrawGizmos()
    76.     {
    77.         if (grid != null)
    78.         {
    79.             // Visualize nodes using Gizmos
    80.             foreach (Node node in grid)
    81.             {
    82.                 // Check for null node
    83.                 if (node == null)
    84.                     continue;
    85.  
    86.                 Gizmos.color = node.isWalkable ? Color.green : Color.red;
    87.                 Gizmos.DrawCube(node.position, Vector3.one * 0.1f);
    88.             }
    89.  
    90.             // Draw player's position
    91.             Gizmos.color = Color.blue;
    92.             Gizmos.DrawSphere(playerPosition.position, 0.1f);
    93.         }
    94.     }
    95.  
    96. }
    And here are the gizmos in each cell to determine if there is an obstacle
    upload_2023-8-31_17-27-52.png

    Thank you for the help.
     
    MelvMay likes this.
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    Good stuff. Good luck with your project.