World
SpatialService
Named spatial indexes for fast proximity queries, parts that keep their own position up to date, and zones with Entered and Exited events.
Getting it
local SpatialService = Engine:GetService("SpatialService")Overview
SpatialService answers "what is near here?" and "who is inside this area?" without looping over everything.
- An index stores items by position (built on Octree) and can track moving parts by itself.
- A zone is an area made of spheres and boxes that fires
EnteredandExitedwhen players (or items in an index) move in and out.
local SpatialService = Engine:GetService("SpatialService")
local spawnArea = workspace.SpawnArea
local safeZone = SpatialService:CreateZone("SafeZone", {
{ Type = "Box", CFrame = spawnArea.CFrame, Size = spawnArea.Size },
})
safeZone.Entered:Connect(function(player)
setInvulnerable(player, true)
end)
safeZone.Exited:Connect(function(player)
setInvulnerable(player, false)
end)Indexes
CreateIndex
SpatialService:CreateIndex(name: string, config: { cellSize: number?, bounds: Bounds?, maxItems: number? }?): SpatialIndexCreates a named index. cellSize is roughly how big the smallest cells should be in studs; the depth of the underlying octree is derived from it. bounds defaults to a cube of DefaultBoundsExtent studs around the origin. Names are unique.
GetIndex
SpatialService:GetIndex(name: string): SpatialIndex?SpatialIndex
A SpatialIndex has every method of an Octree (Insert, Move, Remove, Contains, GetPosition, QueryRadius, CountInRadius, QueryBox, QueryAABB, QueryRay, Nearest, ForEach, GetAllItems, Clear) plus:
| Member | Description |
|---|---|
:TrackPart(part): TrackHandle | Inserts the part and refreshes its position automatically (DefaultTrackHz times per second). Tracking stops when the part is destroyed or you call handle:Disconnect(). A part can only be tracked once per index. |
:Destroy() | Stops all tracking and removes the index. |
Name, Size, IsEmpty, Bounds, CellSize, Destroyed | Read-only. |
local enemies = SpatialService:CreateIndex("Enemies", { cellSize = 32 })
for _, npc in workspace.Enemies:GetChildren() do
enemies:TrackPart(npc.HumanoidRootPart)
end
local function explode(position: Vector3, radius: number)
for _, root in enemies:QueryRadius(position, radius) do
damage(root.Parent, 50)
end
endZones
CreateZone
SpatialService:CreateZone(name: string, volumes: { Volume }, config: ZoneConfig?): ZoneCreates a named zone from one or more volumes; a position inside any of them counts as inside the zone.
| Volume | Fields |
|---|---|
| Sphere | { Type = "Sphere", Center: Vector3, Radius: number } |
| Box | { Type = "Box", CFrame: CFrame, Size: Vector3 } (may be rotated) |
| Region3 | { Type = "Region3", Min: Vector3, Max: Vector3 } |
| Config | Description |
|---|---|
trackPlayers: boolean? | Check every player's character (its HumanoidRootPart, or PrimaryPart). Default from configuration. |
index: SpatialIndex? | Also check every item in this index. |
checkIntervalSeconds: number? | Seconds between checks. |
Zone
| Member | Description |
|---|---|
Entered: Signal<occupant> | A player or index item moved inside. |
Exited: Signal<occupant> | It moved out, left the game, or disappeared from the index. |
Occupants: { any } | Who is inside right now. |
:ContainsPoint(position): boolean | Whether a point is inside. |
:ContainsAt(at): boolean | Same for a Vector3, CFrame, BasePart, Attachment or Model. |
:Destroy() | Stops checking and removes the zone. |
Name, Volumes, Destroyed | Read-only. |
Occupants are players when they come from trackPlayers, and whatever you inserted when they come from an index.
GetZone / GetZonesAt
SpatialService:GetZone(name: string): Zone?
SpatialService:GetZonesAt(at: Vector3 | CFrame | Instance): { Zone }GetZonesAt returns every zone that contains the position right now, which is handy for "which area is the player in" on demand.
Configuration
DefaultConfiguration/Services/SpatialService6 settings| Setting | Default | What it does |
|---|---|---|
| DefaultCellSize | 16 | Studs; :CreateIndex() aims for leaf cells about this big when config.cellSize is omitted. |
| DefaultBoundsExtent | 4096 | +/- studs on every axis around the origin when config.bounds is omitted. |
| DefaultMaxIndexDepth | 12 | Hard clamp on the octree depth a tiny cellSize/huge bounds could otherwise compute. |
| DefaultTrackHz | 15 | Position-refresh rate of SpatialIndex:TrackPart(). |
| DefaultTrackPlayers | true | Whether a Zone checks every Player's character when config.trackPlayers is omitted. |
| DefaultCheckIntervalSeconds | 0.2 | Seconds between a Zone's re-scans of its candidates for Entered/Exited. |
Types
export type SpatialIndex
export type Zone
export type Volume
export type IndexConfig = { cellSize: number?, bounds: Bounds?, maxItems: number? }
export type ZoneConfig = { trackPlayers: boolean?, index: SpatialIndex?, checkIntervalSeconds: number? }