OPTIMIZATION

Spatial Partition

What is it?

Spatial Partition is an optimization technique that organizes objects in a game world based on their physical position. Instead of checking every object against every other object, the world is divided into smaller regions such as grid cells, quadtrees, octrees, or similar structures.

The main idea is simple: when something happens in one area, the game only checks the objects near that area.

This makes spatial queries like collision checks, distance checks, visibility checks, and “who is near me?” searches much cheaper, especially when the scene contains many objects.

When is it used?

Spatial Partition is useful when a scene contains many dynamic objects that often need proximity, collision, or range checks. It is commonly used in games with large numbers of enemies, bullets, NPCs, units, resources, or interactable objects.

Typical use cases include open-world object management, bullet-enemy collision systems, RTS unit selection, crowd simulations, flocking behavior, dynamic LOD systems, and chunk-based world loading.

However, it is not always necessary. If a scene only has a few objects, a simple loop through all objects may be faster and easier to maintain.

Animation

SPATIAL PARTITIONdivide the world into cells — check only nearby cells

Code

Assets / Scripts / Optimization / SpatialPartitionSpatialGrid.cs
READ ONLY
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using System.Collections.Generic;
using UnityEngine;

namespace Patterns.Optimization.SpatialPartition
{
    public class SpatialGrid
    {
        private float _cellSize;
        private Dictionary<Vector2Int, List<Transform>> _grid = new();

        public SpatialGrid(float cellSize)
        {
            this._cellSize = cellSize;
        }

        public void Clear()
        {
            _grid.Clear();
        }

        public void Add(Transform obj)
        {
            Vector2Int cell = GetCell(obj.position);

            if (!_grid.ContainsKey(cell))
                _grid[cell] = new List<Transform>();

            _grid[cell].Add(obj);
        }

        public List<Transform> GetNearby(Vector3 position)
        {
            List<Transform> nearby = new();
            Vector2Int centerCell = GetCell(position);

            for (int x = -1; x <= 1; x++)
            {
                for (int y = -1; y <= 1; y++)
                {
                    Vector2Int cell = centerCell + new Vector2Int(x, y);

                    if (_grid.TryGetValue(cell, out List<Transform> objects))
                    {
                        nearby.AddRange(objects);
                    }
                }
            }

            return nearby;
        }

        private Vector2Int GetCell(Vector3 position)
        {
            return new Vector2Int(
                Mathf.FloorToInt(position.x / _cellSize),
                Mathf.FloorToInt(position.z / _cellSize)
            );
        }
    }
}

Advantages and Disadvantages

• Advantages:

  • It improves performance by limiting checks to nearby areas.
  • It can keep CPU work lower and frame rates more stable.
  • Different partition structures can fit different distributions of objects.

• Disadvantages:

  • Moving objects must be updated in the partition system correctly.
  • Bad cell size choices can reduce the benefit.
  • It adds extra management complexity.

Tips

In Unity, it is usually a good idea to test the built-in physics queries first. Methods like Physics.OverlapSphere and Physics.OverlapBox can already solve many proximity-related problems.

If you build your own grid, choose the cell size carefully. A good starting point is to make the cell size close to the query range or the average spacing between objects.

For moving objects, avoid updating the grid every frame if the object is still inside the same cell.

For heavier systems, Unity's NativeArray, NativeMultiHashMap, Job System, and Burst Compiler can help process many objects more efficiently.

Finally, visualize the grid while debugging. Using OnDrawGizmos with Gizmos.DrawWireCube makes it much easier to see whether your setup is working correctly.