Use part.ExtentsSize to fix this btw
QuickZone v1.3.185 - Roblox-ts support
Additions
- Official roblox-ts Support: You can now use QuickZone in your TypeScript projects
- Install it as follows:
npm i @rbxts/quickzone
The package can be found here: https://www.npmjs.com/package/@rbxts/quickzone
- Install it as follows:
Improvements
- Cylinder Calculation Accuracy: Fixed an issue where non-uniform scaling (different X and Z dimensions) caused the internal detection shape to differ from the physical part. It now maps 1-to-1 with the cylinderâs visual regardless of scale.
QuickZone v1.3.187 - Patch
Fixes
- Stale cache: When calling
Observer:unsubscribe(Group), exit callbacks for entities inside zones were not called. QuickZone now properly checks for this.
Seems very good because ZP is lagging my serverside. However this is complicated and seems to lack methods like zone:getRandomPoint(). I use ZP because itâs simple, so Iâd really like if this was as simple too.
It is really not that complicated. This is a very minimal working example that looks a lot like ZonePlus:
local zone = Zone.fromPart(workspace.Zone)
local localPlayerGroup = Group.localPlayer()
local observer = Observer.new():attach(zone):subscribe(localPlayerGroup)
observer:onPlayerEnter(function(player, zone)
print(player.Name .. 'entered the zone!')
end)
observer:onPlayerExit(function(player, zone)
print(player.Name .. 'exited the zone!')
end)
Personally, I prefer to use observe in most cases. This way, you do not need to manually track onEnter and onExit states. Like this:
observer:observe(function(player, zone)
print(player.Name .. 'entered the zone!')
return function()
print(player.Name .. 'exited the zone!')
end
end)
As for something like getRandomPoint or getRandomPointInZone, QuickZone will never add this as a feature. Really, it doesnât even make sense that ZonePlus provides this. Instead, use a simple utility module for getting a random point in a volume. This should work perfectly fine and doesnât require you to use ZonePlus:
local rand = Random.new()
local function between(a, b)
return a + math.random() * (b - a)
end
local function sphere(part: BasePart): Vector3
local origin = part.Position
local radius = part.Size.X / 2
local dir = rand:NextUnitVector()
--cube root to undo bias, since V = 4/3*pi*r^3
local r = math.random()^(1/3)
return origin + dir * r * radius
end
local function block(part: BasePart): Vector3
local cframe = part.CFrame
local half_size = part.Size / 2
local local_point = Vector3.new(
between(-half_size.X, half_size.X),
between(-half_size.Y, half_size.Y),
between(-half_size.Z, half_size.Z)
)
return cframe:PointToWorldSpace(local_point)
end
local TAU = math.pi * 2
local function cylinder(part: BasePart): Vector3
local cframe = part.CFrame
local size = part.Size
local half_height = size.X / 2
local radius = size.Y / 2
local theta = between(0, TAU)
--square root to undo bias, since V = h*pi*r^2
local r = math.random()^.5 * radius
local local_point = Vector3.new(
between(-half_height, half_height),
r * math.sin(theta),
r * math.cos(theta)
)
return cframe:PointToWorldSpace(local_point)
end
local function wedge(part: BasePart): Vector3
local cframe = part.CFrame
local size = part.Size
local sx, sy, sz = size.X, size.Y, size.Z
local r1 = math.random()
local r2 = math.random()
local sqr_r1 = r1^.5
local x = between(-sx / 2, sx / 2)
local y = sqr_r1 * (1 - r2) * sy
local z = sqr_r1 + sqr_r1 * r2 * -sz
local local_point = Vector3.new(x, y - sy / 2, z + sz / 2)
return cframe:PointToWorldSpace(local_point)
end
return {
sphere = sphere,
block = block,
cylinder = cylinder,
wedge = wedge
}
I think it may be worthwhile to add a .Utility field inside the QuickZone module with these functions. Obviously it violates the design philosophy from what I understand but it is an incredible time save for intermediate developers. I think it important to not gate off new innovation due to difficulty (unless itâs something strictly difficult like colliders or fluid simulation)
Your suggestion is possible, but Iâm still not convinced it is appropriate for a spatial query library to have those utilities. I donât want people to use QuickZone only to do stuff like getRandomPointInZone. ZonePlus is especially not the right tool for stuff like that as you need to create a zone object (which is very resource intensive) in order to do some math that could be done in a simple function.
However, I would be open to create a simple library that is specialized in getting a random point in a volume. It would be orders of magnitude faster than ZonePlus and support any shape.
That is a much better idea then what I initially proposed. I think it is best to make QuickZone compatible with libraries like this. It would require some smart abstractions but all in all the utlities are incredibly useful. Some other functions would be things like getting the corner of a zone, gettting the top center most point etc. I know it sounds crazy but I do actually use hacky functions like these.
Although keep in mind if you just provide some functions people could bolt onto QuickZoneâs API that does the same job with 10x less effort.
By the way, genuinely amazing resource. I know itâs nothing new across the game industry but doing this on Roblox of all engines is very impressive!
I have updated the original post to improve readability and added a Quick Setup section to show how easy it is to set up QuickZone in your game.
I was wondering if making budgeting per-zone is possible? Would like to set priorities for zones and that truly set this library as the king of zone modules
Thatâs not the correct mental model because QuickZone is Entity-Centric (we do a spatial query on an entity, not a zone). This is one of the defining features QuickZone that makes spatial queries way faster and actually scalable compared to other libraries. Adding a frame budget to a zone is impossible unless we make it Zone-Centric but that would remove everything that makes QuickZone actually fast.
Instead, QuickZone offers you a way to control how much you want an entity to be processed through the Observerâs update rate (and precision). And this can be further limited with the global frame budget cap. Since the scheduler already smears updates across frames and uses a round-robin strategy to cycle through entities fairly, the system naturally prevents starvation, even when the global budget is hit.
Lol I just used Claude to give me an example script to replace that method
I also was using zones for an npc hitbox. ZP with 10 npcs was crapping up the server, especially because I didnât even realise ZP doesnât auto garbage collect when the part holding the zone is destroyed.
What I can do for my hitbox script is change onpartentered to a Touched event with a debounce, which would still work even if not perfectly well, and I can change the check for players in the hitbox to getpartsinpart checking for humanoidrootpart, which would mean I donât need a zone any more.
But then what do you think is a sensible use for zones? Hereâs my read on the ZP example uses.
These I think use on entered and exit which I assume doesnât have a better non-zone method? So to me these look like sensible uses of zones.
#1 and #3 donât look like a zone is needed and Claude could think of some math. #2 is nonsense.
#1 and #3 again look like onentered and exit. #2 could use touched. or getpartsinpart on a loop. In some cases the behavior of touched not firing repeatedly when a character just stands still is highly unfavorable, like with my hitbox.
What do you think?
QuickZone is useful anytime you need a way to register if an object (anything that has a spatial location) enters and/or exits a volume. QuickZone is also a faster, cleaner and more robust way to do .Touched events.
In the examples you showed, I would use QuickZone for the Safe Zone, Paint Zone, Voting Pads, Ambient Areas, NPC Damager and Sliding Doors.
However, a spatial query library should not be used for things like the Random Part Generator and the Coin Spawner. Itâs the wrong tool for that.
Thank you, thatâs very helpful


