Snap objects to a grid

I've recently been prototyping a game where you place stuff on a grid. I couldn't find a tutorial on the topic so I had to figure it out on my own and will here tell what I learned. This is what we will achieve:

place stuff on grid gif

First of all we need a plane because we are going to fire rays from the mouse position and we need a plane with a collider to figure out where the ray hits the world. So add a plane and resize it so the plane covers the screen. Then you need an empty gameobject, and as child to this gameobject you should add a quad. Rotate the quad so it faces up and resize it to 0.9 in each direction. We will use this quad to display the grid and our cell size will be 1 m, so the quad should be smaller so we can see each cell.

Now you need to add a new script called GridController. Add this script to an empty gameobject and drag the empty gameobject with a quad to this script.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GridController : MonoBehaviour
{
    //The quad used to display the cells
    public GameObject cellQuadObj;

    //The size of one cell
    public float cellSize = 1f;
    //How many cells do we have in one row?
    public int gridSize = 20;

    //To make it easier to access the script from other scripts
    public static GridController current;

    void Start()
    {
        current = this;
    
        //Display the grid cells with quads
        for (int x = 0; x < gridSize; x++)
        {
            for (int z = 0; z < gridSize; z++)
            {
                //The center position of the cell
                Vector3 centerPos = new Vector3(x + cellSize / 2f, 0f, z + cellSize / 2f);

                GameObject newCellQuad = Instantiate(cellQuadObj, centerPos, Quaternion.identity, transform);

                newCellQuad.SetActive(true);
            }
        }
    }

    //Is a world position within the grid?
    public bool IsWorldPosInGrid(Vector3 worldPos)
    {
        bool isWithin = false;

        int gridX = TranslateFromWorldToGrid(worldPos.x);
        int gridZ = TranslateFromWorldToGrid(worldPos.z);

        if (gridX >= 0 && gridZ >= 0 && gridX < gridSize && gridZ < gridSize)
        {
            isWithin = true;
        }

        return isWithin;
    }

    //Translate from world position to grid position
    private int TranslateFromWorldToGrid(float pos)
    {
        int gridPos = Mathf.FloorToInt(pos / cellSize);

        return gridPos;
    }
}

Now we need something to snap to the grid. So create an empty gameobject. As children to this gameobject you need to add cubes. These cubes will determine the shape of the object to make snapping to the grid easier so make sure they have the same size as your cell size. It should look like this:

snap to grid object

snap to grid object 2

Add a new script called MoveObject, and add this script to each object you want to snap to the grid:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//Move an object with the mouse and try to lock the object to the grid
public class MoveObject : MonoBehaviour 
{
    //The squares that approximate the shape of the object
    public GameObject[] squaresArray;

    //How many square in which directions does this object consist of
    public int squaresX;
    public int squaresZ;
	
	void Update() 
	{
        LockObjectToGrid();

        RotateObject();
    }

    //Rotate the object
    private void RotateObject()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            transform.Rotate(new Vector3(0f, -90f, 0f));

            SwapSquares();
        }
        else if (Input.GetKeyDown(KeyCode.E))
        {
            transform.Rotate(new Vector3(0f, 90f, 0f));

            SwapSquares();
        }
    }

    //Swap how many squares we have in each direction
    private void SwapSquares()
    {
        int temp = squaresX;

        squaresX = squaresZ;

        squaresZ = temp;
    }

    //Lock the object to the grid
    private void LockObjectToGrid()
    {
        //Find the coordinate of the mouse cursor in world space
        RaycastHit hit;

        if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
        {
            Vector3 worldPos = hit.point;

            //Snap the center of the object to the grid

            //Need to add 0.5 if the shape is uneven, like in a 3x3
            float xComp = 0f;
            float zComp = 0f;

            if (squaresX % 2 != 0)
            {
                xComp = 0.5f;
            }
            if (squaresZ % 2 != 0)
            {
                zComp = 0.5f;
            }

            float cellSize = GridController.current.cellSize;

            float snapX = cellSize * Mathf.Round(worldPos.x / cellSize) + xComp;
            float snapZ = cellSize * Mathf.Round(worldPos.z / cellSize) + zComp;

            //Snap the object to the grid
            transform.position = new Vector3(snapX, worldPos.y, snapZ);


            //But dont snap if all the squares that approximate this object are not within the grid
            if (!AreAllSquaresWithinTheGrid())
            {
                transform.position = worldPos;
            }
        }
    }

    //Check if all squares that approximate the object are within the grid
    private bool AreAllSquaresWithinTheGrid()
    {
        bool isWithinGrid = true;

        for (int i = 0; i < squaresArray.Length; i++)
        {
            Vector3 squareWorldPos = squaresArray[i].transform.position;

            //If this square is not within the grid
            if (!GridController.current.IsWorldPosInGrid(squareWorldPos))
            {
                isWithinGrid = false;
                
                //Dont need to check the other squares
                break;
            }
        }

        return isWithinGrid;
    }
}

Drag the cubes you approximated the shape of the object with to the array. You also need to fill in how many cubes you added in each direction. In the first image above there are 3 cubes in z direction and 2 cubes in x direction. This is important because we snap the center of the object to the grid and it depends on if the number of cubes in a certain direction is even or uneven. These two values will be swapped if we rotate the object.

If you reposition the camera and press play you should now see that you can snap the object to the grid!