Spatial Hashing
Godots native collisios checks are usually very fast and well integrated. Therefore this tool uses Area3D and CharacterBody4D's built in collision checks and events on many occasions.
However, for more flexibility and in order to perform custom qeueries, a spatial hashing system implemented in gdscript is provided.
A good example to check its use is RTS_DamageDealerAoE which uses
RTS_SpatialHashArea.main_grid.find_entities(...)
to find nearby entities.
Overview
Apart from RTS_SpatialHashArea, the remaining spatial hash scripts (RTS_HashClient, RTS_SpatialHashFast, RTS_SpatialHashUtils, RTS_HashNode) can be used as an independent library, as they are self containing.
Spatial hashing divides the map into a grid for faster spatial queries.
Benefits
- Fast spatial queries - O(1) instead of O(n)
- Better performance - Especially with many units
- Efficient updates - Only rebuild affected cells
- Scalable - Works with large maps and unit counts
Basic Implementation
While the internal spatial hashing algorithms are complex, the RTS_SpatialHashArea component provides a simple interface for querying entities efficiently.
RTS_SpatialHashArea Component
RTS_SpatialHashArea is an Area3D that automatically manages a spatial hash grid for fast entity lookups.
Setup
Create a scene with an RTS_SpatialHashArea node and a CollisionShape3D child:
Level (Node3D)
├── RTS_SpatialHashArea (Area3D)
│ └── CollisionShape3D (BoxShape3D)
└── Units...
Properties
@export var id: String = "1" # Unique identifier for the grid
@export var INIT_CELL_SIZE = 1.0 # Size of each grid cell (larger = fewer cells)
@export var visual_debug: bool = false # Visualize grid (requires DebugDraw3D addon)
@export var auto_update_clients: bool = false # Auto-update entity positions each frame
How It Works
- Automatic Registration - When an
RTS_Entitywithspace_hash = trueenters the scene, it's automatically added to the grid - Grid Cells - The collision shape defines the grid bounds; cell size determines query speed vs accuracy
- Position Tracking - Entity positions update in the grid (manually or automatically)
- Fast Queries - Find entities by position/radius in O(1) time instead of O(n)
Static Main Grid
The last RTS_SpatialHashArea running in the scene becomes the main grid:
# (in _ready)
#set main_grid
main_grid = self
# Access globally
var nearby = RTS_SpatialHashArea.main_grid.find_entities(position, radius, exact=false)
Overwrite this functionality if you want to assign main_grid in a different fashion. It is important that this main_grid is set, as most queries will use the main_grid to query by default. Ofcourse it is possible use multiple SpatialHashAreas and query them independently as well.
Common Methods
Query entities by position:
# Find by radius
var entities = spatial_hash.find_entities(position, radius, exact=false)
# Find by bounds (Vector2 x/z size)
var entities = spatial_hash.find_entities_bounds(position, bounds, exact=false)
# Find by AABB (3D bounding box)
var entities = spatial_hash.find_entities_using_aabb(aabb, exact=true)
# exact=true: Precise distance checks
# exact=false: Faster, grid-based checks
Faction Filtering
Find only enemies or allies:
# Group parameter: -1 = all, 0 = PLAYER faction, 1 = ENEMY faction, 2 = NEUTRAL
var enemies = spatial_hash.find_entities(position, radius, exact=false, group=1)
Cell Size Tuning
Larger cells = Fewer cells to check, but more false positives
INIT_CELL_SIZE = 5.0 # Each cell is 5x5 units
Smaller cells = More accurate but slower
INIT_CELL_SIZE = 1.0 # Each cell is 1x1 units
Rule of thumb: Set cell size roughly equal to average entity search radius
Example Usage
# Find nearby enemies to attack
func find_targets(attack_range: float) -> Array[RTS_Entity]:
var nearby = RTS_SpatialHashArea.main_grid.find_entities(
global_position,
attack_range,
exact=false,
group=RTS_Entity.Faction.ENEMY
)
return nearby
# Check if area is clear of enemies
func is_area_clear(center: Vector3, radius: float) -> bool:
var enemies = RTS_SpatialHashArea.main_grid.find_entities(
center,
radius,
exact=true,
group=RTS_Entity.Faction.ENEMY
)
return enemies.is_empty()
Manual Updates
If auto_update_clients = false, manually update positions:
# Call when entities move significantly
RTS_SpatialHashArea.main_grid.update_clients()
If false, make sure to call this before doing any queries.
Visual Debugging
Consider adding the DebugDraw3D addon to debug the grid. This draws grid cells and entity positions for troubleshooting.
See Also
- Best Practices - Performance tips
- Core Concepts - System architecture