What’s the best method of retrieving players within an area/zone on the server?
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
Setup a group of parts to represent your zone
Calculate the maximum and minimum boundaries of this zone
Create a Region3 value using these bounds
ᅠᅠᅠᅠᅠ
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.
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.
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.
Example Uses
Safe Zone (using additionalHeight, a loop and 2000 randomly generated parts)
Safe Zone (using uncancollided parts and zone events)
Zone+ provides an efficient way to determine players within an area, however should be used with consideration and is not always the optimal solution:
On the server, zones should be relatively small areas (ideally no more than 200x200x200), otherwise Region3 checks begin to become expensive. Minimise the volume of your zones where possible.
On the server, Zones should not be used extensively and should not cover a large proportion of a map. Zone+ is optimised for minimal, small zones. If you wish to cover a lot of your map, instead consider setting up zones on the client, or creating your own system which fires a ray from all players instead of performing additional unnecessary Region3 checks.
When using zone events (and calling :initLoop() or :initClientLoop()), or calling your own loop, keep the amount of calls per minute to a minimum. By default when :initLoop() is called (with a default delay of 0.1), 600 checks will be performed per minute. Instead, by specifying a delay of 3 seconds (i.e. :initLoop(3)) only 20 checks will be performed per minute). Obviously the greater the delay, the greater the potential period it takes for a zone to detect a player entering or exiting.
On the client, when using events call initClientLoop() instead of initLoop() for situations where you only intend to check for the local player (e.g. in a local sound system). This method bypasses the zones default region3 checks which are unnecessary for most situations on the client.
On the client, a raycast is fired for every zone. (i.e. ray*zone*(1/delay) per second) due to its object-like nature. While this has a negligible impact on performance, creating your own system which simply fires a ray every delay from the local player’s HRP is more efficient (i.e. ray*(1/delay) per second instead)
Thank you for this extremely useful bit of information! “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!
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.
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:
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.
Another solution for more complex shapes to check if a point is inside a polygon is to simply raycast
from anywhere outside the polygon to your point and count how often it hits a side of the polygon.
(Odd # hits = inside). This shouldn’t be very performance intensive at all and should satisfy most use cases
The method works the same for any type of shape. I believe you may be getting confused with the second image? Instead of creating lots of small boxes within the shape, we instead calculate a single Region3 determined upon the min and max bounds of the shape. Using this Region3, we then get all the players within the rough area, and fire a single ray below their HRP to confirm whether they are in the precise boundary we want to check for:
I meant making that shape out of parts is pretty tedious to do unless it’s hollow. But if it’s hollow firing a raycast downward won’t really be all that useful. I guess it wouldn’t be too tedious if you built its hollow shell and used some plugin to fill it in. I’m sure one exists
If the use case for this system isn’t key to gameplay, the ultimate solution would be to perform your downward raycast every frame on each of the clients for their respective characters, and when they hit a part identified as part of a zone, you can then fire a bindableevent and a remoteevent to cause stuff to happen both clientside and serverside. Clients could lie and fire the remoteevent when they aren’t actually in the zone, but for many applications, this isn’t important. For example, a zone that players must stand in if they want to participate in a minigame round, or a zone where stepping in it will apply weather effects (as seen in this thread).
If you want to further expand the utility of this module, you could add this clientside solution as a toggleable alternative, because for some requirements, the clientside method just makes more sense. For example, if someone wanted to open a shop UI upon the player entering a zone, it wouldn’t make sense to detect it on the server and then send a remoteevent signal to that player’s client.
Nice idea! This will work effectively if developed with the correct sanity checks.
Absolutely, the article focuses on retrieving multiple players server-side so I haven’t worried about this. Firing a downward ray every x amount of time and checking this against a dictionary of parts is almost all you’ll need to do for the client.
Having a module work effectively for both client and server is definitely something I’d like to do in the future.