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.
public class Grid : MonoBehaviour
{
public Tilemap tilemap; // Reference to the isometric tilemap
public LayerMask walkableLayerMask; // For obstacle detection
Node[,] grid;
int gridSizeX, gridSizeY;
void Start()
{
gridSizeX = tilemap.cellBounds.size.x; // x-axis grid size from tilemap
gridSizeY = tilemap.cellBounds.size.y; // y-axis grid size from tilemap
CreateGrid();
}
void CreateGrid()
{
grid = new Node[gridSizeX, gridSizeY];
BoundsInt bounds = tilemap.cellBounds; // boundaries of tilemap in cell co-ordinates
TileBase[] allTiles = tilemap.GetTilesBlock(bounds); // Retrieves all tiles in the given bounds
for (int x = 0; x < gridSizeX; x++) // For x-axis tiles
{
for (int y = 0; y < gridSizeY; y++) // For y-axis tiles
{
TileBase tile = allTiles[x + y * bounds.size.x];
Vector3 cellWorldPos = tilemap.GetCellCenterWorld(new Vector3Int(x, y, 0)); // Gets tilemap centre
bool walkable = !(Physics2D.OverlapCircle(cellWorldPos, 0.1f, walkableLayerMask));
grid[x, y] = new Node(walkable, cellWorldPos); // Create a Node instance and store it
}
}
}
public Node NodeFromWorldPoint(Vector3 worldPosition)
{
// Calculate the percentage of the x and y co-ordinates within the tilemap
float percentX = Mathf.Clamp01((worldPosition.x + tilemap.cellSize.x * 0.5f - tilemap.origin.x) / tilemap.size.x);
float percentY = Mathf.Clamp01((worldPosition.y + tilemap.cellSize.y * 0.5f - tilemap.origin.y) / tilemap.size.y);
// Calculate the array indices based on the percentages
int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
return grid[x, y];
}
void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(gridSizeX, gridSizeY, 1));
if (grid != null)
{
foreach (Node n in grid)
{
Gizmos.color = n.isWalkable ? Color.white : Color.red;
Gizmos.DrawCube(n.position, Vector3.one * (Mathf.Min(tilemap.cellSize.x, tilemap.cellSize.y) - 0.1f));
}
}
}
}
I think the grid is of by about 31,22, from quickly counting it.
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.
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.
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.
void GenerateNodes()
{
grid = new Node[numTilesX, numTilesY];
Vector3 tilemapOrigin = tilemap.origin;
Debug.Log(tilemapOrigin.ToString());
//Prints the origin as (-17,-21,0) for the current grid 40,38
Debug.Log(tilemap.transform.localPosition.ToString());
Vector3 actualOrigin = tilemap.transform.position;
//Prints at (0,0,0)
Vector3 cellSize = tilemap.cellSize;
Debug.Log(cellSize.ToString());
Vector3 originOffset = actualOrigin - tilemapOrigin;
Debug.Log("The originOffset xyz is, " + originOffset.ToString());
for (int x = 0; x < numTilesX; x++)
{
for (int y = 0; y < numTilesY; y++)
{
Vector3Int cellPosition = new Vector3Int(x, y, 0);
Vector3 cellCentreWorld = tilemap.GetCellCenterWorld(cellPosition);
bool walkable = !(Physics2D.OverlapCircle(cellCentreWorld, 0.1f, walkableLayerMask));
Node node = new Node(walkable, cellCentreWorld);
grid[x, y] = node;
}
}
}
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.
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.
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?
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:
public class Grid : MonoBehaviour
{
public Tilemap tilemap; // Reference to the isometric tilemap
public Grid gridBase;
public LayerMask walkableLayerMask; // For obstacle detection
public Transform playerPosition;
Node[,] grid;
private int numTilesX; // Number of tiles in the X axis
private int numTilesY; // Number of tiles in the Y axis
void Start()
{
numTilesX = CalculateNumTilesX(tilemap.cellBounds);
numTilesY = CalculateNumTilesY(tilemap.cellBounds);
Debug.Log("Number of tiles on the x-axis = " + numTilesX.ToString() + ", on the y-axis = " + numTilesY.ToString());
//Prints 40,38 which are the number of tiles in scene view.
GenerateNodes();
}
int CalculateNumTilesX(BoundsInt bounds)
{
return bounds.size.x;
}
int CalculateNumTilesY(BoundsInt bounds)
{
return bounds.size.y;
}
void GenerateNodes()
{
grid = new Node[numTilesX, numTilesY];
Vector3 tilemapOrigin = tilemap.origin; //Origin of the tilemap
Vector3 actualOrigin = tilemap.transform.position; //Actual position of the tilemap
Vector3 originOffset = actualOrigin - tilemapOrigin; //Calculation to offset the node to the correct tile
for (int x = 0; x < numTilesX; x++)
{
for (int y = 0; y < numTilesY; y++)
{
Vector3Int cellPosition = new Vector3Int(x, y, 0);
cellPosition -= Vector3Int.FloorToInt(originOffset); //Places the nodes on the correct tile
Vector3 cellCentreWorld = tilemap.GetCellCenterWorld(cellPosition);
// Check if a tile exists in the current cell
bool hasTile = tilemap.HasTile(cellPosition);
if (!hasTile)
{
grid[x, y] = null; // Skip this cell
continue;
}
bool walkable = !(Physics2D.OverlapCircle(cellCentreWorld, 0.1f, walkableLayerMask)); //Check to see if tile is able to walked on
Node node = new Node(walkable, cellCentreWorld); //For the Node, gives information for if the tile is walkable
grid[x, y] = node;
}
}
}
// Draw Gizmos in the Scene view
void OnDrawGizmos()
{
if (grid != null)
{
// Visualize nodes using Gizmos
foreach (Node node in grid)
{
// Check for null node
if (node == null)
continue;
Gizmos.color = node.isWalkable ? Color.green : Color.red;
Gizmos.DrawCube(node.position, Vector3.one * 0.1f);
}
// Draw player's position
Gizmos.color = Color.blue;
Gizmos.DrawSphere(playerPosition.position, 0.1f);
}
}
}
And here are the gizmos in each cell to determine if there is an obstacle