QuickZone v1.1.0 | 60 FPS with 1M+ Zones | Ultra Fast ZonePlus / SimpleZone Alternative

[ QuickZone V1.1.0]

60 FPS While ZonePlus and SimpleZone Drop To 5 FPS

99.6% Memory Reduction Compared To ZonePlus and 5x Smaller Than SimpleZone

*see Benchmarks section for more

Showcase Place | MarketPlace | Documentation | Github Repository | Roblox OSS Community


Overview

Instead of using the physics engine like Zoneplus, SimpleZone and LessSimpleZone, QuickZone just performs geometric calculations all by itself. It provides a predictable, budgeted, and flexible solution for zone detection while using Linear Bounding Volume Hierarchy (LBVH) in the backend. QuickZone makes it possible to track thousands of entities across hundreds of zones with very little impact on your frame rate and memory.


Core Features

  • Lifecycle Management: Use the observe pattern for 100% reliable cleanup. There is no need for juggling onEntered and onExited events anymore (do note that QuickZone still supports Event-Driven Programming).
  • Track Anything: Track BaseParts, Models, Attachments, Bones, Cameras, or even pure Lua tables through duck typing. If it has a position, QuickZone can track it.
  • Shape Support: Supports Blocks, Balls, Cylinders, Wedges and CornerWedges without relying on the collision meshes.
  • Decoupled Architecture: Separate game logic from spatial instances. Bind behaviors to categories of entities (Players, NPCs, Projectiles) for a clean, scalable architecture.
  • Budgeted Scheduler: Remove lag spikes by setting a hard frame budget (e.g., 1ms). Workload is smeared across frames to maintain a flat and predictable performance profile.
  • Zero-Allocation Runtime: By using contiguous arrays and object pooling, QuickZone reduces GC pressure, avoiding memory-related stutters.

How It Works

QuickZone is not instance-bound. It uses a Group-Observer-Zone topology, separating who is being tracked from where the tracking occurs and how the system should respond.

The Secret to Performance

If you are coming from ZonePlus or SimpleZone, you are used to a ā€œZone-Centricā€ model where the Zone itself handles the logic. QuickZone flips this:

  • ZonePlus: ā€œHey Zone, tell me when a player enters you.ā€
  • QuickZone: ā€œHey Observer, tell me when this Group enters any of these Zones.ā€

The Three Pillars

  1. Zones (Where): Mathematical boundaries (Static or Dynamic).
  2. Groups (Who): Collections of entities (Players, NPCs, Projectiles) with shared update rates.
  3. Observers (How): The logic bridge. Define behavior once (e.g., ā€œSafeZone Logicā€) and attach it to as many zones as you need.


Quick Start

The following example showcases a swim system. QuickZone supports two coding styles, so choose the one that fits your workflow.

Option A: The QuickZone Approach (Recommended)

Best for clean, modern code. You define relationships in a configuration table (Declarative) and use a single function to manage the active state (Lifecycle).

local QuickZone = require(game.ReplicatedStorage.QuickZone)
local Zone, Group, Observer = QuickZone.Zone, QuickZone.Group, QuickZone.Observer

-- Create a LocalPlayerGroup that automatically tracks the client's character (including respawns)
local myPlayer = Group.localPlayer()

-- Create an observer subscribed to that group. 
-- Priority 42 ensures this logic overrides lower-priority overlaps.
local swimObserver = Observer.new({ 
    priority = 42,
    updateRate = 30,
    precision = 0.1,
    groups = { myPlayer } 
})

-- Define behavior
swimObserver:observeLocalPlayer(function()
    local character = Players.LocalPlayer.Character
    if not character then return end

    local humanoid = character:FindFirstChild('Humanoid')
    if not humanoid then return end
    
    -- On Enter
    humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, true)
    humanoid:ChangeState(Enum.HumanoidStateType.Swimming)

    -- Return cleanup (On Exit)
    return function() 
        humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
    end
end)

-- Find all current and future instances with the 'Water' tag and wrap them in a Zones object.
-- By passing the observer in the config, all created zones are attached automatically.
Zone.fromTag('Water', { 
    observers = { swimObserver } 
})

Option B: The Classic Approach (ZonePlus / SimpleZone Style)

Best for familiarity or for migrating ZonePlus code to QuickZone. You manually ā€˜wire’ objects together (Imperative) and use standard events like onEntered to trigger one-off actions (Event-Driven).

local QuickZone = require(game.ReplicatedStorage.QuickZone)
local Zone, Group, Observer = QuickZone.Zone, QuickZone.Group, QuickZone.Observer

local myPlayer = Group.localPlayer()
local swimObserver = Observer.new():setPriority(42):setUpdateRate(30):setPrecision(0.1)

-- Subscribe the logic to the group
swimObserver:subscribe(myPlayer)

-- Connect events
swimObserver:onLocalPlayerEntered(function(zone)
    print('Entered water zone:', zone:getId())
    -- Add swimming logic here
end)

swimObserver:onLocalPlayerExited(function(zone)
    print('Exited water zone:', zone:getId())
    -- Remove swimming logic here
end)

-- Create a Zones object from a folder of parts.
-- The returned 'zones' object allows you to manage the entire collection at once.
local zones = Zone.fromParts(workspace.WaterParts:GetChildren())
zones:attach(swimObserver)

Usage Guide

QuickZone is designed around a three-tier architecture: Zones (where), Groups (who), and Observers (how).

1. Zones (Where)

Zones represent physical areas in the world. They are mathematical boundaries that can be static (fixed in space) or dynamic (following a part). They can be created from existing parts or defined manually with a CFrame and Size.

Bulk Creation

The easiest way to create zones is using the bulk constructors. The fromParts, fromDescendants, fromChildren, and fromTag return a Zones collection object, which acts as a logical unit allowing you to manage multiple zones at once.

-- Create zones from a CollectionService tag
local lavaZones = Zone.fromTag('Lava', {
  metadata = { damage = 10 },
  observers = { damageObserver },
})

-- Create zones from an array of parts
local safeZones = Zone.fromParts(workspace.SafeZones:GetChildren())

-- Create zones from all BaseParts inside a Model or Folder
local hazardZones = Zone.fromDescendants(workspace.TrapModel)

-- Create zones from only the direct children of a Folder
local flatZones = Zone.fromChildren(workspace.FlatFolder)

-- You can attach an observer to the entire collection at once
safeZones:attach(invincibilityObserver)

Manual Creation

Useful for procedural generation or areas without physical parts.

local zone = Zone.new({
  cframe = CFrame.new(0, 10, 0),
  size = Vector3.new(10, 10, 10),
  shape = 'Block',
  isDynamic = true,
  metadata = { Name = 'Lobby' }
})

Single & Dynamic Creation

For maximum perfomance, use isDynamic = true for zones attached to moving platforms, vehicles, or projectiles.

local trainZone = Zone.fromPart(workspace.TrainCarriage, {
  isDynamic = true,
  metadata = { route = 'North' },
  observers = { trainObserver }
})

Updating Zones

If you create a zone manually or want to sync a dynamic zone to a new reference, use :syncToPart().

-- Manually move a dynamic zone
dynamicZone:setPosition(Vector3.new(0, 50, 0))

-- Sync a dynamic zone to its associated part's current CFrame, Size, and Shape
dynamicZone:syncToPart()

2. Groups (Who)

Groups are collections of entities (Parts, Models, Players, etc.).

Specialized Groups

QuickZone provides built-in abstractions that automatically handle player lifecyles.

-- Tracks all players in the server
local allPlayers = Group.players()

-- Tracks only the local player (client-side only)
local myPlayer = Group.localPlayer()

Custom Groups

For NPCs, projectiles, or vehicles, create a standard Group.

local projectiles = Group.new({
    entities = workspace.Projectiles:GetChildren()
})

Managing Entities

You can add BaseParts, Models, Attachments, Bones, or tables with a Position.

-- Add a Model (tracks the PrimaryPart or Pivot)
enemies:add(npcModel)

-- Add a specific Attachment (tracks the exact point)
-- This is great for offsets if you do not want to track the middle of a part (e.g. sword tip)
enemies:add(sword.TipAttachment)

-- Add a table
local spell = { Position = Vector3.new(10, 5, 0) }
enemies:add(spell)

-- Clear the enemies group when done
enemies:clear()

3. Observers (How)

Observers act as the logic layer. They subscribe to Groups and attach to Zones to bridge spatial data with game behavior.

Setup

An Observer listens to its subscribed Groups and checks if they overlap with its attached Zones.

local observer = Observer.new({
	updateRate = 60,   -- Check up to 60 times a second
	precision = 1.0,   -- Only query if the entity moves more than 1 stud
	priority = 5       -- Used to resolve overlapping zones
})

observer:subscribe(allPlayers)
healingZones:attach(observer)

Lifecycle Management

For logic that should persist while an entity is inside a zone (e.g., UI, music, status effects), use the observe methods. These accept a callback that returns a cleanup function, which runs automatically when the entity exits.

-- Generic observation
observer:observe(function(entity, zone)
	print('Entered', entity)
	local highlight = Instance.new('Highlight', entity)
	
	return function()
		print('Exited', entity)
		highlight:Destroy()
	end
end)

-- The callback fires when the first entity of a group enters, and the 
-- returned cleanup function fires when the last entity of the group leaves.
observer:observeGroup(function(group, zone)
	print('Group ' .. group:getId() .. ' has arrived!')
	local boss = workspace.Boss:Clone()
	boss.Parent = workspace
	
	return function()
		print('The group has been wiped out or left.')
		boss:Destroy()
	end
end)

-- Player specific
observer:observePlayer(function(player, zone)
	local forceField = Instance.new('ForceField', player.Character)
	
	return function()
		forceField:Destroy()
	end
end)

-- LocalPlayer specific
observer:observeLocalPlayer(function(zone)
	local sound = workspace.Sounds.SafeZoneAmbience
	sound:Play()

	return function()
		sound:Stop()
	end
end)

Events

For logic that happens exactly once on entry or exit (e.g., playing a sound effect, dealing damage, analytics), use the event listeners.


-- Individual entity events
observer:onEntered(function(entity, zone)
    print(entity.Name .. ' entered ' .. zone:getId())
end)
observer:onExited(function(entity, zone)
    print(entity.Name .. ' exited ' .. zone:getId())
end)

-- Transition event (Fires when swapping between overlapping zones within the same observer)
observer:onTransitioned(function(entity, newZone, oldZone)
    print(entity.Name .. ' seamlessly moved to a new zone without leaving the area!')
end)

-- Group-level events
observer:onGroupEntered(function(group, zone)
    print('The first member of group ' .. group:getId() .. ' entered!')
end)
observer:onGroupExited(function(group, zone)
    print('The last member of group ' .. group:getId() .. ' left!')
end)

-- Convenient player events
observer:onPlayerEntered(function(player, zone) ... end)
observer:onPlayerExited(function(player, zone) ... end)
observer:onPlayerTransitioned(function(player, newZone, oldZone) ... end)
observer:onLocalPlayerEntered(function(zone) ... end)
observer:onLocalPlayerExited(function(zone) ... end)
observer:onLocalPlayerTransitioned(function(newZone, oldZone) ... end)

Handling Overlapping Zones (Transitions vs. Priorities)

When zones physically overlap in the world, QuickZone offers two distinct architectural patterns to handle them, depending on your goal.

Observers use a priority system to handle overlapping zones. An entity ā€˜belongs’ to only one zone state per observer at a time when using priorities.

Pattern 1: The Data-Driven Pattern (Single Observer + Transitions)

Best for: Systems that share the exact same logic, but use different values (e.g., all Environmental Hazards, all Healing Zones, all XP Zones).

If a player walks from a Lava zone into an overlapping SuperLava zone attached to the same observer, they never actually ā€œleftā€ the observer’s overall coverage area. Therefore, onExited and onEntered will not fire. Instead, the engine fires an onTransitioned event.

This allows you to update metadata instantly!

hazardObserver:observePlayer(function(player, initialZone)
    local currentDamage = initialZone:getMetadata().Damage or 10
    local active = true

    local disconnectTransition = hazardObserver:onPlayerTransitioned(function(transitioningPlayer, newZone, oldZone)
        if transitioningPlayer ~= player then return end
        
        currentDamage = newZone:getMetadata().Damage or 10
        print(player.Name .. " transitioned. New damage: " .. currentDamage)
    end)

    task.spawn(function()
        while active do
            player.Character.Humanoid:TakeDamage(currentDamage)
            task.wait(1)
        end
    end)

    return function()
        active = false
        disconnectTransition() -- Clean up the listener
    end
end)

Pattern 2: The State-Machine Pattern (Multiple Observers + Priorities)

Best for: Systems with mutually exclusive logic that need to strictly override each other (e.g., Camera Filters, Music Tracks, UI States).

If you have overlapping zones that do fundamentally different things, you should attach them to different observers and assign them a Priority. QuickZone’s engine will automatically force the entity out of the lower-priority observer and into the higher-priority one.

local lowPriority = Observer.new({ priority = 0 })
local highPriority = Observer.new({ priority = 10 })

-- If a player is inside Zone A (Low) and Zone B (High) simultaneously:
-- 1. highPriority:onEntered() fires for Zone B.
-- 2. lowPriority:onExited() fires for Zone A.

Observer State

Observers can be toggled to pause logic without destroying the configuration.

observer:setEnabled(false) -- Fires 'onExited' for everyone inside
task.wait(5)
observer:setEnabled(true) -- Fires 'onEntered' if they are still there

Utility

Frame Budget

To maintain a high framerate in complex scenes, you can constrain the total CPU time QuickZone is allowed to consume per frame.

-- Allow 0.5 milliseconds per frame (default is 1ms)
QuickZone:setFrameBudget(0.5)

Immediate Spatial Queries

Perform instant checks without using the Observer/Group pattern.

-- Get all zones at a specific vector
local zones = QuickZone:getZonesAtPoint(Vector3.new(10, 5, 0))

-- Get the group an entity belongs to
local group = QuickZone:getGroupOfEntity(workspace.Part)

Limitations

  • Point-Intersection: QuickZone tracks the precise coordinate of an entity. It does not account for the full volume of the entity itself.

  • Budgeted Latency: To prevent frame drops, QuickZone ā€˜smears’ workload across multiple frames. In high-load scenarios (e.g., thousands of active entities), there may be a slight delay between an entity physically entering a zone and the event firing.


Benchmarks

We stress-tested QuickZone against the most popular alternatives in two distinct scenarios: Entity Stress (lots of moving parts) and Map Stress (lots of zones).

Note: For the QuickZone benchmark, we used a frame budget of 1ms, the entities’ update rate was set to 60Hz, and precision was 0.0.

Test 1: High Zone Count

Scenario: 500 moving entities, 10,000 zones, recorded over 30 seconds.

This test highlights the fundamental flaw in traditional Zone-Centric libraries. As map complexity grows, their performance degrades exponentially.

Metric QuickZone ZonePlus SimpleZone QuickBounds Empty Script
FPS 59.33 3.84 5.53 58.95 59.28
Events/s 648 627 519 328 0
Memory Usage (MB) 18.57 4230 99.79 17.62 0.65

The Result: QuickZone maintained a perfect 60 FPS.

  • ZonePlus and SimpleZone imploded, dropping to 3-5 FPS, making the game unplayable.

  • ZonePlus consumed over 4 GB of memory, which would crash most mobile devices instantly.

  • QuickZone proved it is O(N) relative to entities, not zones. You can add as many zones as you want without performance penalties.

  • QuickZone vs. QuickBounds: Both libraries scaled well by maintaining ~60 FPS. However, QuickZone still maintained a slight FPS lead and, more importantly, delivered double the event throughput (643 vs 328) compared to QuickBounds.

Test 2: High Entity Count

Scenario: 2,000 moving entities, 100 zones, recorded over 30 seconds.

Metric QuickZone ZonePlus SimpleZone QuickBounds Empty Script
FPS 42.37 29.88 37.23 41.31 42.73
Events/s 2271 2482 2518 566 0
Memory Usage (MB) 2.13 159 1.77 2.60 1.04

The Result: QuickZone is the only library that maintained near-baseline FPS (-1% impact).

  • ZonePlus caused a 28% drop in framerate, and SimpleZone decreased by 13%.

  • QuickZone handled the load with 98% less memory than ZonePlus.

  • QuickZone vs. QuickBounds: QuickZone squeezes out more performance, averaging ~1 FPS higher than QuickBounds. More importantly, QuickZone processed 4x the volume of events (2,271 vs 566).


If you want to read more about QuickZone’s technical details, read ā€˜why use quickzone’ in the docs.

There may still be bugs. If you encounter one, write a comment down below or, preferably, create an ā€˜Issue’ on GitHub.

QuickZone is free and open source! Contributors are welcome!

Showcase Place | MarketPlace | Documentation | Github Repository | Roblox OSS Community

61 Likes

Is there any demand for box intersections instead of point intersections to trigger events? If so, I may implement that in the future.

This library was made for the game @Clinkety and I are currently working on. Contributions are super welcome!

1 Like

Looks very cool, a very good alternative to ZonePlus and SimpleZone

1 Like

This is really nice, It’s definitely an improvement on the SimpleZone thing i had going on which was pretty much structured badly and didnt use the correct data structures. I’m considering reworking SimpleZone in the future before the devforum age checks get implemented cause it’s one of my most popular resources and i do think it deserves a reboot :V

Also, if you do end up using volume intersections instead of points, i must warn you that i spent a reaaally long time trying to make good volume intersection checking functions for all ShapeTypes, the hardest ones to make being Cylinder and Wedge (Cylinder was courtesy of Proffessor because the only thing i could find relating to it was a long research paper written in pseudocode, he helped me out because hes insanely good at math unlike me lol), though i never ended up implementing it into LessSimpleZone because i decided to archive SimpleZone before then.

if you do manage to make these volume intersection functions correctly i’d actually be pretty impressed :V Good luck

3 Likes

QuickZone v0.3.0 - Lifecycle Management Made Easy

Additions

  • Observer:observe()
  • Observer:observePlayer()
  • Observer:observeLocalPlayer()

Details

QuickZone now supports the observe pattern for 100% reliable cleanup. Instead of juggling onEntered and onExited events, you can now define your logic and cleanup in one place.

For logic that should persist while an entity is inside a zone (e.g., UI, music, status effects), use the observe methods. These accept a callback that returns a cleanup function, which runs automatically when the entity exits.

-- Generic observation
observer:observe(function(entity, zone, metadata)
    print("Entered", entity)
    local highlight = Instance.new("Highlight", entity)
    
    return function()
        print("Exited", entity)
        highlight:Destroy()
    end
end)

-- Player specific
observer:observePlayer(function(player, zone)
    local forceField = Instance.new("ForceField", player.Character)
    
    return function()
        forceField:Destroy()
    end
end)

-- Local Player specific
observer:observeLocalPlayer(function(zone)
    local sound = workspace.Sounds.SafeZoneAmbience
    sound:Play()

    return function()
        sound:Stop()
    end
end)

Thank you very much!

I’ve already got most of the code related to volume intersection thanks to @unityjaeger (the creator of QuickBounds) and got it to work before I drastically overhauled QuickZone. It supported the box, cylinder and ball shapes. However, the wedge wasn’t implemented, and that one seems very difficult to do. I could simply make wedges use the box shape, but that would make QuickZone’s behavior a little inconsistent.

Also, due to my optimizations, I introduced an edge-case for box intersections: if an entity rotates while remaining in the same position, it will not perform a recalculation. To fix that, I would need a rotational check in addition to a positional check which seems a little wasteful.

Also, I question how much utility volume intersections would really have. It would complicate the library, and you can probably just get away with having multiple points (tracking the Attachments) instead which would be more performant in most cases.

1 Like

QuickZone v0.3.11 - Optimization and Bug Fixes

Fixes

Fixed issue where metadata of entity was not passed down, resulting in ObservePlayer() and ObserveLocalPlayer() not working.

Thank you, @RoyzerM, for the Issue!

Improvements

Reduced memory by 20% and increased processing speed around 10% in initial testing.

2 Likes

QuickZone v0.4.0 - The Declarative Update

This is a major architectural rework designed to future-proof the API. With this update, QuickZone moves towards a more stable, professional and declarative design pattern that significantly reduces boilerplate code.

Key Highlights

1. Declarative API (New)

You can now define your entire spatial topology (Groups, Observers, and Zones) purely through configuration tables. This removes the need for calling :attach() or :subscribe() manually on initialization, but you can still do that if you want.

Old Way (Imperative)

local localPlayerGroup = QuickZone.LocalPlayerGroup()
local enemyGroup = QuickZone.Group()
local observer = QuickZone.Observer()
local zone = QuickZone.ZoneFromPart(part)

observer:subscribe(localPlayerGroup):subscribe(enemyGroup)
zone:attach(observer)

New Way (Declarative)

local localPlayerGroup = QuickZone.Group.localPlayer()
local enemyGroup = QuickZone.Group.new()

local observer  = QuickZone.Observer.new({
    groups = { localPlayerGroup, enemyGroup } -- immediately subscribes to localPlayerGroup and enemyGroup
})

QuickZone.Zone.fromPart(part, {
    observers = { observer } -- Immediately attaches to observer
})

2. Class-Based API (New)

As you can see in the previous example, we have moved away from global factory functions (ZoneFromPart, PlayerGroup) to static constructors located within their respective classes.

  • ZoneFromPart(...) → Zone.fromPart(...)
  • ZoneFromParts(...) → Zone.fromParts(...)
  • PlayerGroup(...) → Group.players(...)
  • LocalPlayerGroup(...) → Group.localPlayer(...)

3. Batch Creation (Zone.fromParts) (New)

A brand new constructor designed for map loading.

  • Zone.fromParts(parts): Creates multiple zones from a list of parts in a single call. This is significantly cleaner than looping through a list of parts to create individual zones.

4. Bulk Entity Operations (New)

New methods for Groups, useful for heavy gameplay elements like projectiles.

  • Group:addBulk(entities): Adds a list of entities to a group in one call
  • Group:removeBulk(entities): Removes a list of entities.
  • Group:clear(): Removes all entities from a group.

Breaking Changes & Migration

This update deprecates imperative factory patterns in favor of the new Class-Based API and Config tables.

1. Constructors

  • Global Functions Removed: ZoneFromPart, ZoneFromParts, PlayerGroup, and LocalPlayerGroup have been removed. Use their Class.method equivalents.

2. Configuration

  • Observer.new: Now accepts a single config table instead of individual arguments.
  • Group.new: Does not have the ā€˜safety’ property anymore in the config. This has been moved to Observer.new.

3. Zone:update() → Zone:syncToPart()

The generic :update() method has been removed to reduce ambiguity.

  • Old: zone:update() (synced to part) or zone:update(cframe, size) (manual update).
  • New:
    • Use zone:syncToPart() if you want the zone to read properties from its attached BasePart.
    • Use zone:setCFrame() or zone:setSize() if you want to manually modify the zone.
2 Likes

If you’re not on v0.4.0 already, you should really upgrade
You only need to change some minor things to make your code work again, and it will future-proof your code as QuickZone’s API will be quite stable from now on.

1 Like

QuickZone v0.4.1 - The Group Presence Update

This update introduces high-level logical aggregation, allowing Observers to treat entire Groups as single units. It also resolves a critical synchronization bug in LocalPlayer observations.

Additions

1. Observer:observeGroup()

You can now observe a Group’s presence as a singular state. This could useful for capturing objectives, queue areas, etc.

observer:subscribe(myGroup):observeGroup(function(group, zone)
    local roomLights = workspace.LargeBuilding.InteriorLights:GetChildren()
    for _, light in roomLights do
        if light:IsA("Light") then light.Enabled = true end
    end

    -- Cleanup runs when the group left the area
    return function()
        for _, light in roomLights do
            if light:IsA("Light") then light.Enabled = false end
        end
    end
end)

2. Observer:onGroupEntered() and Observer:onGroupExited()

If you don’t need persistent lifecycle management and just want one-off triggers, use the standalone event listeners for groups.

observer:onGroupEntered(function(group, zone)
    print("Sector " .. zone:getId() .. " has been entered by Group " .. group:getId())
end)

Fixes

1. Observer:observeLocalPlayer()

Due to a typo, the cleanup function would incorrectly fire if any player exited the zone, rather than strictly waiting for the LocalPlayer to exit.

2 Likes

Wow! Inredible! This looks cool and nice! How longf did it take to make? I rate it a 9.9999/10!

I may use this in the incoming future! Wow!!!.!!!

1 Like

Haha, I’ll take that 9.9999! I hope I’ll ever get that missing 0.0001, though. Maybe in a future update?

All in all, it took about 2 weeks. The first week was spent on designing the library and writing the code. In the second week, 1 day was spent on improving the API, 2 days were spent on the lifecycle and declarative updates, and I worked for 2 days on the documentation (it takes longer than you think). There were definitely some long nights in the past 2 weeks.

Thanks for the comment!

3 Likes

you create a astounding product. Maybe you get real big game which will be use this incredible library

Thank you! I certainly hope my game will be succesful.

By the way, I have one more big update planned for QuickZone which would improve Group significantly, making it even easier to work with. It could very well be the best one yet. But after that, I don’t really have any other major updates planned. There’s just not much to improve anymore.

Or, perhaps, the community will come up with good suggestions.

1 Like

it seems like " QuickZone:visualize(true) " is bugged

Noted! Will be fixed in the upcoming release!

Edit: It seems to work correctly on my end. Can you provide more information? Are you on v0.4.1?

yes version 0.4.1 and this is the script , I’m sure I made a stupid mistake…

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local QuickZone = require(ReplicatedStorage.Core.Shared.QuickZone)

QuickZone:visualize(true)

local Group = QuickZone.Group
local Observer = QuickZone.Observer
local Zone = QuickZone.Zone

local players = Group.players()

local observer = Observer.new()
observer:subscribe(players)

observer:onPlayerEntered(function(player, zone)
	print("entered")
end)

observer:onPlayerExited(function(player, zone)
	print("exited")
end)

local zone = Zone.new({
	cframe = CFrame.new(0, 10, 0),
	size = Vector3.new(10, 10, 10),
	shape = "Block",
	isDynamic = false,
	metadata = { Name = "Lobby" }
})

zone:attach(observer)

Your script is fine! But I do know where the issue is now.

Visualizations work well while running it in a local script. However, if you run :visualize() on the server and simulate the experience through ā€˜Test’ or ā€˜Test Here’, the visualization won’t show up anymore. Weirdly enough, if you do ā€˜Server & Clients’, it works again. So, there seems to be an inconsistency in how Roblox handles BoxHandleAdornments.

I’ll probably switch to a BasePart for visualization purposes. You’ll see it in the next update.

Nice one! interest for contributions