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:
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:
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!