Zone system, touch checking unreliable

I’m trying to set a Zone system, similar to Fisch where it checks the location of the bobber, in my case the player relative to invisible parts in the workspace. The only problem is that when I check if the player has touched a new zone, it takes a good 5 seconds to register. I saw there used to be APIs for this like ZonePlus, but every one I’ve found seems quite unnecessarily complicated and Outdated. What is the best way to register players to the zone?

I’d like to add that I don’t need a constant check, just something that registers the player when joining a new zone.

The best way is to use Spatial Queries. If you don’t want to use .Touched, then you have to have a constant check and use them. But the performance hit is almost NOTHING.

--!native
--!optimize 2
-- Local script is best here, this script should basically cost NOTHING in performance

local Players = game:GetService("Players")

local Player = Players.LocalPlayer
local ZoneFolder = workspace:WaitForChild("insert here") -- assuming you have this
local Params = OverlapParams.new()
Params.IncludeInstances = {ZoneFolder}
Params.MaxParts = 1 -- we only care if we are in ONE zone. 
-- no need to search all parts

local CurrentZone: BasePart? = nil

local function CheckZone()
	local character = Player.Character
	
	if not character then 
		return 
	end

	local hrp = character:FindFirstChild("HumanoidRootPart") :: BasePart
	
	if not hrp then
		return 
	end

	local hrpCFrame: CFrame = hrp.CFrame
	local hrpSize: vector = hrp.Size 

	local intersectingZones = workspace:GetPartBoundsInBox(hrpCFrame, hrpSize, Params)
	local foundZone = intersectingZones[1] 

	-- we dont wanna fire twice for the same zone
	if foundZone ~= CurrentZone then
		CurrentZone = foundZone

		-- if we leave then getparts returns empty table, foundZone is nil
		if CurrentZone then
			print("new zone")
		else
			print("left all zones")
		end
	end
end

task.spawn(function()
	while task.wait(0.1) do
		CheckZone()
	end
end)

Although, GetPartsBoundInBox is usually used for easy shapes, if you have some complex ones then GetPartsInPart might be better. And, if you have overlapping zones then the MaxParts should probably not be set..

1 Like

Drop this in a part on the workspace for a quick test.

--ServerScript 
local rs=game:GetService("RunService")
local part = script.Parent
local prev = {}

function getHumanoids()
	local humans = {}
	for _, p in pairs(workspace:GetPartsInPart(part)) do
		local h = p.Parent:FindFirstChildWhichIsA("Humanoid")
		if h and not table.find(humans, h) then table.insert(humans, h) end
	end rs.Heartbeat:Wait()
	return humans
end

while(true)do rs.Stepped:Wait()
	local now = getHumanoids()
	for _, h in pairs(now) do
		if not table.find(prev, h) then
			
			print(h.Parent.Name.." entered")
		end
	end
	for _, h in pairs(prev) do
		if not table.find(now, h) then
			
			print(h.Parent.Name.." exited")
		end
	end	prev = now
	--task.wait(0.033) 
end

I’ve made a few variations of this, but this is the basic concept done on one part.
It can even do a cylinder. I’ve never tried a ball. It’s more of a workaround for TouchEnded.

1 Like

Their is a module for more reliable zone handling… I don’t quite remember the name, but I think it was called zone plus… if that’s not the name you can probably find it on YouTube

There is nothing more reliable than my script for zones (in my opinon).. Not even close to my first try.
That is a version for only one zone however.. Very easily modified to be as many as you want.