Skip to content

Positions and the Mouse

This page explains the three coordinate systems a tilemap uses and the helpers that convert between them, including finding the cell under the mouse.

Concept

Space Unit Example
Grid Whole cells, int x and y Cell (3, -2)
Local Units in the tilemap's own transform (0.48, -0.32)
World Units in the scene Wherever the tilemap's transform puts it

Grid and local space are tied together by CellSize. Cell (gx, gy) covers the local rectangle from (gx * CellSize.x, gy * CellSize.y) to one cell further up and to the right, so cell (0, 0) has its bottom left corner on the tilemap's pivot. Negative cells extend down and to the left with no special handling.

Local and world space are tied together by the tilemap's Transform: position, rotation and scale. A tilemap can sit anywhere, turned in any direction; the tiles always lie on its local XY plane.

CellSize defaults to the tileset's tile size in pixels divided by its Pixels Per Unit when you assign the tileset. A 32 pixel tile at 100 pixels per unit gives cells of 0.32 by 0.32.

Converting

using UnityEngine;
using CreativeSpore.SuperTilemapEditor;

public static class TilemapPositionExamples
{
    // World position to grid cell.
    public static Vector2Int WorldToCell(STETilemap tilemap, Vector3 worldPos)
    {
        Vector2 local = tilemap.transform.InverseTransformPoint(worldPos);
        return TilemapUtils.GetGridPositionInt(tilemap, local);
    }

    // Grid cell to the world position of its centre.
    public static Vector3 CellToWorldCenter(STETilemap tilemap, int gridX, int gridY)
    {
        return TilemapUtils.GetGridWorldPos(tilemap, gridX, gridY);
    }

    // Grid cell to the local position of its centre.
    public static Vector2 CellToLocalCenter(STETilemap tilemap, int gridX, int gridY)
    {
        return TilemapUtils.GetTileCenterPosition(tilemap, gridX, gridY);
    }
}

To put an object on the centre of a cell:

enemy.transform.position = TilemapUtils.GetGridWorldPos(tilemap, 10, 4);

The z of the result is the tilemap's own plane. Adjust it if your object needs to sit in front.

The cell under the mouse

TilemapUtils.GetMouseGridX and GetMouseGridY cast a ray from the camera through the mouse onto the tilemap's plane, so they work with perspective cameras and rotated tilemaps as well as with a 2D orthographic setup.

using UnityEngine;
using CreativeSpore.SuperTilemapEditor;

public class ClickToErase : MonoBehaviour
{
    [SerializeField] private STETilemap m_tilemap;
    [SerializeField] private Camera m_camera;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            int gx = TilemapUtils.GetMouseGridX(m_tilemap, m_camera);
            int gy = TilemapUtils.GetMouseGridY(m_tilemap, m_camera);
            m_tilemap.Erase(gx, gy);
            m_tilemap.UpdateMesh();
        }
    }
}

These two read Input.mousePosition from the Input Manager. If your project's Active Input Handling is set to Input System Package (New), that property throws. Use ScreenToLocalPosition instead and pass the screen position yourself, for example Mouse.current.position.ReadValue() from the Input System:

Vector2 local = TilemapUtils.ScreenToLocalPosition(screenPos, m_tilemap, m_camera);
Vector2Int cell = TilemapUtils.GetGridPositionInt(m_tilemap, local);

TilemapUtils.ScreenToWorldPosition(screenPos, camera) does the same onto the world XY plane at z = 0, without a tilemap.

Note

With a Parallax Factor other than 1, the tilemap is drawn displaced from where its transform says it is, by tilemap.GetParallaxDisplacement(camera). The helpers above use the transform, so they answer for the undisplaced tilemap. Subtract the displacement from a world position under the cursor before converting it, when the tilemap has no scaled or rotated parent.

Precision at cell edges

Grid conversion floors the position divided by the cell size, after adding a tiny epsilon. A position exactly on a cell boundary belongs to the cell above and to the right. Floating point error can still push a boundary value to the wrong side, for example a position of 0.32 with cells of 0.16. When you compute a position only to convert it back to a cell, use the cell centre, not its corner.

Reference

TilemapUtils is a static class. Every method below is public.

Method Returns
GetGridX(tilemap, localPos), GetGridY(tilemap, localPos) The grid column or row containing a local position.
GetGridX(localPos, cellSize), GetGridY(localPos, cellSize) The same without a tilemap.
GetGridPosition(tilemap, localPos) Both as a Vector2.
GetGridPositionInt(tilemap, localPos), GetGridPositionInt(localPos, cellSize) Both as a Vector2Int.
GetGridWorldPos(tilemap, gridX, gridY) World position of the cell's centre.
GetGridWorldPos(gridX, gridY, cellSize) Centre of the cell with no transform applied, so a local position.
GetTileCenterPosition(tilemap, gridX, gridY) Local position of the cell's centre.
GetTileCenterPosition(tilemap, localPos) Local centre of the cell containing a local position.
GetMouseGridX(tilemap, camera), GetMouseGridY(tilemap, camera) The cell under the mouse. Input Manager only.
ScreenToLocalPosition(screenPos, tilemap, camera) A screen position projected onto the tilemap, in local space.
ScreenToWorldPosition(screenPos, camera) A screen position projected onto the world XY plane.
OverlapRect(tilemap, rect) True if any cell touched by a local Rect holds a tile with a collider.
IterateTilemapWithAction(tilemap, action) Calls action for every cell inside the map bounds.

BrushUtil holds the two functions the conversions above are built on, plus one more:

Method Returns
BrushUtil.GetGridX(position, cellSize), BrushUtil.GetGridY(position, cellSize) The same as the TilemapUtils versions.
BrushUtil.GetSnappedPosition(position, cellSize) The bottom left corner of the cell containing a local position.

On STETilemap:

Member Returns
CellSize Cell size in local units. Call Refresh() after changing it.
IsPositionInsideTilemap(localPos) True if a local position is inside the map bounds.
IsGridPositionInsideTilemap(gridX, gridY) True if a cell is inside the map bounds.
GetParallaxDisplacement(camera) How far parallax moves the tilemap for that camera.