ZoneController - Retrieving players within an area/zone

This kind of question has popped up a lot of times over the years so I thought I’d finally address it:

What’s the best method of retrieving players within an area/zone?


Common ‘solutions’

  • "Using touched/untouched events!" - This was actually how I handled zone checking when I first began developing. Not only do these work incorrectly (untouched often fires before touched, untouched sometimes never fires at all when leaving a part, etc) but they can cause lag when complex shapes enter them.

  • "GetTouchingParts!" - The one major flaw with this function is it doesn’t work with uncancollided parts. If the player jumps off the part, they won’t be registered. buildthomas and evaera have created a solution for this if you’re interesting in exploring this path.

  • "Magnitude checks!" - Sorry circle worshippers, but the square lovers are going to have something to say here.

  • "Region3" (by itself) - Polgons hate him. Scientists at Cambridge university have found out… no.

  • "Raycasting" (by itself) - Fire ray below player’s character, check if part below, bam! Polygons and circles are happy bunnies now. Shame the server isn’t. This method is fine for small servers, but fails to scale effectively for servers with increasingly larger amounts of players with recursive checks.


The holy grail

Region3 + Raycasting

Method

  1. Setup a group of parts to represent your ‘zone’

  2. Calculate the maximum and minimum boundaries of this zone

  3. Create a Region3 value using these bounds

ᅠᅠᅠᅠᅠimage

  1. Get players in this region using FindPartsInRegion3. This function returns a list of parts, which you can use to check for a player’s character.

  2. For the players returned in the region check, fire a ray below from the HumanoidRootPart (not too far as this causes lag). If one of the zone’s parts is returned, we can safely say the player is within that zone.

    image

Benefits

  • No need to unnecessarily check every player within the server. Simply calculate a ‘rough’ area and determine who’s in the exact zone for players within that region.

  • Region3 and raycasting are ‘light-weight’. You can run these multiple times a second with minimal effect to performance.


Open Source example

I’ve created a free-to-use example which you can take here.

Setup

  • Create your zone out of flat parts and parent these to a folder or model. This is a ‘zone’.

  • Call :GetPlayersInZone(zone) to retrieve an array of players within that zone.

Methods

ZoneController:GetPlayersInZone(zone, regionHeight)

  • Returns an array of players within the exact zone
  • regionHeight is 20 unless specified

ZoneController:GetPlayersInRegion(zone, regionHeight)

  • Returns an array of players within the zone’s Region3 (the rough zone area)
  • regionHeight is 20 unless specified
55 Likes

Thank you for this extremely useful bit of information! :grinning::smirk: “the holy grail” method is something I never even thought about using for finding players within a zone, I’ll be sure to use this a resource for future projects!

2 Likes

I’ve been looking for a good way to get players in a zone for a long time, thank you for this post! :grinning:

2 Likes

This is actually a smart way to go about it. It even supports arbitrary space, which is a huge plus point when working with regions.

This thread dismisses clunky methods and mini-wars about the best way to do this (especially wrt magnitude), then brings the two best methods to the table and has them cooperate: the basis being the raycast and the Region3 being a gatekeeping supportive arm.

Of course, naturally, not all other methods should be dismissed (actually just the last 3) as they do have utility in some cases. Majority of cases seem to be simple area/room scans though so that should be covered well enough by this.

Thank you for sharing. This thread will go a long way.

5 Likes

so, the best way to keep finding players in zone is using loop?

while wait(1) do
    for _,PlayersInZone in pairs(ZoneController:GetPlayersInZone(Workspace.Zone)) do
        print(PlayerInZone)
    end
end
1 Like

Can you make a Demo place when they are in safe zone and giving them an ForceField and leaving safe zone removes ForceField.

Sure, I’ve setup a basic safe zone example here:

Source code:

-- << CONFIG >>
local safeZoneCheckInterval = 0.5
local forceFieldName = "SafeZoneFF"


-- << SERVICES/VARIABLES >>
local players = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage")
local zoneController = require(replicatedStorage.ZoneController)
local safeZone = workspace.SafeZone


-- << SETUP >>
local forceFieldTemplate = Instance.new("ForceField")
forceFieldTemplate.Name = forceFieldName


-- << SAFE ZONE >>
while true do
	wait(safeZoneCheckInterval)
	
	-- Get players in SafeZone
	local playersInZone = zoneController:getPlayersInZone(safeZone)
	local playersInZoneDictionary = {}
	for _, plr in pairs(playersInZone) do
		playersInZoneDictionary[plr] = true
	end
	
	-- Add/remove ForceField accoridngly
	for _, plr in pairs(players:GetChildren()) do
		local char = plr.Character
		if char then
			local forceField = char:FindFirstChild(forceFieldName)
			if playersInZoneDictionary[plr] then
				if not forceField then
					forceField = forceFieldTemplate:Clone()
					forceField.Parent = char
				end
			elseif forceField then
				forceField:Destroy()
			end
		end
	end
	
end
2 Likes

For situations like the safe zone above, a loop works great, assuming you’re not checking too many times a second (a 0.5 second interval for example will be absolutely fine).

As a side-tip, you might want to check out the article colbert wrote on the While Wait-Do Idiom:

2 Likes

now this is epic. i’m most definitely going to use this in the future with my current project.

3 Likes

Interesting methodology! My own game uses GetTouchingParts to determine region occupation and I must say the fact that I didn’t see this simple yet elegant solution is almost insulting to me. I’ll definitely be upgrading my private system to make use of this method.

1 Like