In this Entry I decided to make a Grid System. I’ve been working on a Personal Project of mine where I need to use a 3D Grid system and I decided instead of dealing with countless bugs and errors I just wanted to make the system in a clean Unity Project. So here it is 🙂
So What does this Grid System do, well for what it is it can do a lot and can be altered largely. This is what I mean – This system allows Unity using it’s built in Gizmos() Function to create a Grid with X amount of Cells that we can set. The System works with Building GameObjects. These GameObjects can take up different sizes on the grid and occupy different cells. We can also detect building stacking too. Nothing is stopping the building stacking but the logic is all here. The functions being used can be called in different places like through UI.Â
If you use UI to place a building down it can be prevented with how we detect where buildings are on the grid. Take a look at the script and it’ll be clearer.
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
public class Building : MonoBehaviour
{
[Tooltip("Direct Reference to the Grid Manager Class")]
private Grid_Manager gridManager; // Reference to our Grid Manager CS file
[Header("Building Grid Size")]
[Tooltip("Cell Building Scale On X Axis")]
public int buildingSizeX = 2; // Set your desired size
[Tooltip("Cell Building Scale On Z Axis")]
public int buildingSizeZ = 2; // Set your desired size
private int lastOccupiedX = -1; // Last position in the Grid On X
private int lastOccupiedZ = -1; // Last Position in the Grid On Z
private int currentlyOccupiedX = -1; // Current Position in Grid on X
private int currentlyOccupiedZ = -1; // Current Position in Grid on Z
private string occupyingBuildingName = ""; // Current Building GameObject String name inside a cell
private bool hasOccupiedCell = false; // Bool to check if a cell is occupied
private bool isBeingDragged = false; // Boolean controlling if mouse is down and dragging a building
// New variables to keep track of the last occupied cell for each building
private Dictionary<string, Vector2Int> buildingLastOccupiedCells = new Dictionary<string, Vector2Int>();
void Awake()
{
gridManager = FindObjectOfType<Grid_Manager>(); // Find the Grid Manager inside the scene
if (gridManager == null) // if we do not find the grid manager
{
Debug.LogError("Grid Manager not found. Make sure it is present in the scene."); // Log Error since nothing works without it
}
if (buildingSizeX >= 5 || buildingSizeZ >= 5)
Debug.LogWarning("This Script Does not support larger buildings well"
+ " Please Reduce the size if you see any abnormalities");
}
void Start()
{
// when grid manager is aquired
if (gridManager != null)
{
PlaceBuilding(); // Run PLace Building Function
}
}
void Update()
{
// When true
if (isBeingDragged)
{
// Update the building position during drag
UpdateGridOccupancy();
}
}
void PlaceBuilding()
{
UpdateGridOccupancy(); // Initial update
}
void SnapToGrid(int x, int z)
{
// Calculate the center position of the grid cells
float centerX = (x + 0.5f * (buildingSizeX - 1)) * gridManager.cellSize;
float centerZ = (z + 0.5f * (buildingSizeZ - 1)) * gridManager.cellSize;
// Set the new position while maintaining the Y position
transform.position = new Vector3(centerX, transform.position.y, centerZ);
}
private void OnMouseDrag()
{
// Get the mouse position in world coordinates
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.transform.position.y));
// Calculate the half size of the building on the X and Z axes
float halfSizeX = buildingSizeX * 0.5f * gridManager.cellSize;
float halfSizeZ = buildingSizeZ * 0.5f * gridManager.cellSize;
// Calculate the snapped position based on the closest grid cell
float snappedX = Mathf.Floor((mousePosition.x - halfSizeX) / gridManager.cellSize) * gridManager.cellSize + halfSizeX;
float snappedZ = Mathf.Floor((mousePosition.z - halfSizeZ) / gridManager.cellSize) * gridManager.cellSize + halfSizeZ;
// Set the new position while maintaining the Y position
transform.position = new Vector3(snappedX, transform.position.y, snappedZ);
// Debug logs for diagnosis
//Debug.Log($"Mouse Position: {mousePosition}, Snapped Position: {snappedX}, {snappedZ}, Actual Position: {transform.position}");
}
void OnMouseDown()
{
isBeingDragged = true;
}
void OnMouseUp()
{
isBeingDragged = false;
}
void UpdateGridOccupancy()
{
// grid cell index on the Axis where the center of the building is located.
int x = Mathf.FloorToInt((transform.position.x - gridManager.transform.position.x) / gridManager.cellSize);
int z = Mathf.FloorToInt((transform.position.z - gridManager.transform.position.z) / gridManager.cellSize);
// Debug log for actual position
Debug.Log($"Actual Position: {transform.position}");
// Check if the building is within the grid
if (x >= 0 && x < gridManager.gridSize - buildingSizeX + 1 &&
z >= 0 && z < gridManager.gridSize - buildingSizeZ + 1)
{
SnapToGrid(x, z);
// Check if the cell is already occupied by another building
if (gridManager.IsCellOccupiedByOther(x, z, buildingSizeX, buildingSizeZ, gameObject.name))
{
// Cell is already occupied by another building, handle accordingly (e.g., print a message)
Debug.LogWarning($"Cannot place {gameObject.name} in occupied cell ({x}, {z}). Already occupied by another building.");
// When a Building moves towards an occupied Cell it refreshes the grid since we are no longer in the old cell
gridManager.ReleaseCells(lastOccupiedX, lastOccupiedZ, buildingSizeX, buildingSizeZ, gameObject.name);
return; // Skip updating last occupied values if there is a conflict
}
// Rest of the logic for handling placement inside the grid
if (!hasOccupiedCell)
{
occupyingBuildingName = gameObject.name;
Debug.Log($"Cell ({x}, {z}) is now occupied by {occupyingBuildingName}.");
hasOccupiedCell = true;
}
else if (occupyingBuildingName == gameObject.name)
{
Debug.Log($"Cell ({x}, {z}) is still occupied by {gameObject.name}.");
}
else
{
// Check if the cell was previously occupied by another building with a different name
if (lastOccupiedX != -1 && lastOccupiedZ != -1 &&
(lastOccupiedX == x && lastOccupiedZ == z))
{
// Cell is already occupied by another building, handle accordingly (e.g., print a message)
Debug.LogWarning($"Cannot place {gameObject.name} in occupied cell ({x}, {z}). Already occupied by another building.");
return; // Skip updating last occupied values if there is a conflict
}
}
// If the building moved to a new cell, update the currently occupied cell for debugging
currentlyOccupiedX = x;
currentlyOccupiedZ = z;
// If the building moved to a new cell, revert the color of the last occupied cells to green
if (lastOccupiedX != -1 && lastOccupiedZ != -1 && (lastOccupiedX != x || lastOccupiedZ != z))
{
gridManager.ReleaseCells(lastOccupiedX, lastOccupiedZ, buildingSizeX, buildingSizeZ, gameObject.name);
}
// Update the grid to occupy the current cells
gridManager.OccupyCells(x, z, buildingSizeX, buildingSizeZ, gameObject.name);
// Update the last occupied cell for this building
buildingLastOccupiedCells[gameObject.name] = new Vector2Int(x, z);
lastOccupiedX = x;
lastOccupiedZ = z;
}
else
{
// If the building is outside the grid, revert the color of the last occupied cells to green
if (lastOccupiedX != -1 && lastOccupiedZ != -1)
{
gridManager.ReleaseCells(lastOccupiedX, lastOccupiedZ, buildingSizeX, buildingSizeZ, gameObject.name);
currentlyOccupiedX = -1;
currentlyOccupiedZ = -1;
// If the building is outside the grid, clear its occupancy from the grid
gridManager.ClearBuildingOccupancy(gameObject.name);
}
lastOccupiedX = -1;
lastOccupiedZ = -1;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grid_Manager : MonoBehaviour
{
// Size of the Grid for X and Z axis (10x10)
public int gridSize = 10;
public float cellSize = 1f; // Size of each cell inside the grid
// States used to manage how a cell can be occupied
public enum CellState { Empty, Occupied, Reserved }
// New array to store building names
public CellState[,] cellStates;
private string[,] cellBuildingNames;
// Positions where each cell is on the grid / in world space
private List<Vector3> cellPositions = new List<Vector3>();
void OnDrawGizmos()
{
if (cellStates == null) // Add null check prevents runtime error
{
// CellStates array is not initialized yet, return without drawing Gizmos
return;
}
// Grid Starts Blue
Gizmos.color = Color.blue;
// For each grid values on x
for (int i = 0; i < gridSize; i++)
{
// for each grid value on Z
for (int j = 0; j < gridSize; j++)
{
// Position of each cell in the grid we need to see how big eah cell is since different sized cells take up more space
Vector3 cellPosition = new Vector3(i * cellSize, 0f, j * cellSize);
Gizmos.DrawWireCube(transform.position + cellPosition, new Vector3(cellSize, 0.1f, cellSize)); // Make the grid
// when the cell is occupied
if (cellStates[i, j] == CellState.Occupied)
{
Gizmos.color = Color.red; // make grid cells red
Gizmos.DrawCube(transform.position + cellPosition, new Vector3(cellSize, 0.1f, cellSize)); // draw cube inside grid which is red
}
// Draw a small radius at each cell position
Gizmos.color = Color.blue;
float radius = 0.1f;
Gizmos.DrawWireSphere(cellPosition, radius);
}
}
}
void CreateGrid()
{
cellStates = new CellState[gridSize, gridSize]; // Make new state with cells
cellBuildingNames = new string[gridSize, gridSize]; // Initialize the new array
// For each Cell on X
for (int i = 0; i < gridSize; i++)
{
// For each cell on Z
for (int j = 0; j < gridSize; j++)
{
cellStates[i, j] = CellState.Empty; // Initialize the cell state
cellBuildingNames[i, j] = ""; // Initialize the building name
cellPositions.Add(new Vector3(i * cellSize, 0f, j * cellSize)); // add cell psotions to List
}
}
}
void Start()
{
CreateGrid(); // create grid new on Start
}
/// <summary>
/// Function to say if we can place a building down
/// Can be used to place a building using UI - returns a bool 0(false) or 1(true)
/// </summary>
/// <param name="x"></param>
/// <param name="z"></param>
/// <param name="sizeX"></param>
/// <param name="sizeZ"></param>
/// <param name="buildingName"></param>
/// <returns></returns>
public bool CanPlaceBuilding(int x, int z, int sizeX, int sizeZ, string buildingName)
{
// Check if any of the cells are already taken by the specified building
return !IsCellOccupiedByOther(x, z, sizeX, sizeZ, buildingName);
}
/// <summary>
/// Function used to check if the current cell a building could access is occupied. Use Enum to check Cell enum state
/// Check to that we do not over stack a cell area (Buildings are not on top of eachother)
/// </summary>
/// <param name="x"></param>
/// <param name="z"></param>
/// <param name="sizeX"></param>
/// <param name="sizeZ"></param>
/// <param name="buildingName"></param>
/// <returns></returns>
public bool IsCellOccupiedByOther(int x, int z, int sizeX, int sizeZ, string buildingName)
{
// For each cell on X
for (int i = x; i < x + sizeX; i++)
{
// For Each Cell on Z
for (int j = z; j < z + sizeZ; j++)
{
// When grid is made
if (i >= 0 && i < gridSize && j >= 0 && j < gridSize)
{
// Check if the cell is occupied by another building with a different name
if (cellStates[i, j] == CellState.Occupied && cellBuildingNames[i, j] != buildingName)
{
return true; // Cell is occupied by a different building
}
}
else
{
return true; // Assuming cells outside the grid are taken
}
}
}
return false; // Cell is not occupied by a different building
}
/// <summary>
/// Function that helps Occupy a Cell that is not taken by a building
/// </summary>
/// <param name="x"></param>
/// <param name="z"></param>
/// <param name="sizeX"></param>
/// <param name="sizeZ"></param>
/// <param name="buildingName"></param>
public void OccupyCells(int x, int z, int sizeX, int sizeZ, string buildingName)
{
// Get X values for cells
for (int i = x; i < x + sizeX; i++)
{
// Get Z values for Cells
for (int j = z; j < z + sizeZ; j++)
{
if (i >= 0 && i < gridSize && j >= 0 && j < gridSize)
{
cellStates[i, j] = CellState.Occupied; // Cell is now occupied
cellBuildingNames[i, j] = buildingName; // store string of building name occupying cells
}
}
}
}
/// <summary>
/// Function allows Cells that are currently taken to be put back into an empty state as building moved away
/// </summary>
/// <param name="x"></param>
/// <param name="z"></param>
/// <param name="sizeX"></param>
/// <param name="sizeZ"></param>
/// <param name="buildingName"></param>
public void ReleaseCells(int x, int z, int sizeX, int sizeZ, string buildingName)
{
// Get X values of cells
for (int i = x; i < x + sizeX; i++)
{
// Get Z values of cells
for (int j = z; j < z + sizeZ; j++)
{
// If grid is made
if (i >= 0 && i < gridSize && j >= 0 && j < gridSize)
{
cellStates[i, j] = CellState.Empty; // Grid that was occupied is now empty
cellBuildingNames[i, j] = ""; // building string name reset for nothing is occupying
}
}
}
}
public void ClearBuildingOccupancy(string buildingName)
{
for (int i = 0; i < gridSize; i++)
{
for (int j = 0; j < gridSize; j++)
{
if (cellBuildingNames[i, j] == buildingName)
{
cellStates[i, j] = CellState.Empty;
cellBuildingNames[i, j] = "";
}
}
}
}
}