Correctly Performing Spatial Queries

How can I perform spatial queries correctly? Besides the original post, there is very little documentation.

My main concerns are:

  • How do I format OverlapParams properly?
  • How do I check when the return of a spatial query has updated? Constantly running them and comparing the results seems like bad practice.

I eventually figured out OverlapParams are formatted the same way as Raycasts.


local Params =
Params.MaxParts = 5
workspace:GetPartsInPart(part, Params)

I still haven’t found a better method for checking updated spatial queries. I currently check every frame via RunService.Heartbeat:Wait() which seems excessive.

I highly recommend marking your reply as the solution or updating your post with your new question.

Also, spatial queries do not “update”. Querying an area only finds everything in the provided area at the moment you query it. If you want to track the changes in that area then you must query it repeatedly over time and track the changes yourself by comparing the new results with past results.

If you believe RunService.Heartbeat too little time then you can use a loop with a custom delay using task.wait(delayTime). However, if you want to know the results immediately then it’s best to connect it to RunService.Heartbeat. I have used the spatial query API before and was worried about the same thing. However, after monitoring the script performance I can safely say that it is not a big concern. It does, however, depend on your usage. If your game will be querying a lot then you will need to find/create performance optimizations.

Here’s an example for tracking the changes in an area over time:

local RunService = game:GetService("RunService")

local areaCache = {} -- an array containing the the areas we are tracking

local function trackArea(location, size, params)
	local area = {}

	area.CFrame = location
	area.Size = size
	area.CurrentQuery = {} -- the returned value of the most recent query
	area.PreviousQuery = {} -- the returned value of the previous query
	area.Params = params -- params do not have to be provided

	table.insert(areaCache, area)

local find = table.find
RunService.Heartbeat:Connect(function() -- the function that is run every frame and checks the areas
	for _,area in ipairs(areaCache) do
		area.PreviousQuery = area.CurrentQuery -- update the previous query
		area.CurrentQuery = workspace:GetPartBoundsInBox(area.CFrame, area.Size, area.Params) -- this can be any type of spatial query you want, whether it's in a part or radius. this is only for example purposes

		local prev,cur = area.PreviousQuery,area.CurrentQuery
		local maxLength = if #cur >= #prev then #cur else #prev
		for i=1,maxLength do
			local prevPart = prev[i]
			local curPart = cur[i]

			if prevPart and not find(cur, prevPart) then
				print("Part Left Area: ",prevPart)
			if curPart and not find(prev, curPart) then
				print("Part Entered Area: ",curPart)

If you don’t know much about optimizing your scripts and/or systems then you can use a pre-existing module for what you need. There are a few that are really good that you can use and have already been tested, improved, and optimized. I have provided their links below, in order of perceived usefulness to you. I have also provided a few other that use the spatial query API that you may find useful to use or learn from.


By the way, I’ve updated my response with the relevant code if you need it.