SimpleZone | A simple, fast and new Zone module!

SimpleZone is a simple(r) alternative to other Zone modules such as ZonePlus.
SimpleZone offers much customizability because of its clean source code and short amount of dependencies.

Documentation:

QueryOptions

The QueryOptions object is simply something that contains information about how you want the Zone to query BaseParts, and is used when calling constructors.

You can construct a new QueryOptions object using SimpleZone.QueryOptions.new(), or simply just manually making it by using the table constructor {...}

  1. QueryOptions.FireMode :: "OnEnter"|"OnExit"|"Both"|"None" = "Both"

    • All this determines is what events will be fired when an item enters or exits the Zone.
      If FireMode is "OnEnter", then Zone.ItemExited will not fire, only Zone.ItemEntered.
      Same thing for all the other FireModes.

  2. QueryOptions.TrackItemEnabled :: boolean = false

    • This boolean determines wether or not you are able to use Zone:TrackItem(...).
      The reason why this needs to exist is because it adds 1 more for loop to the :Update() method, which may be undesirable for people who only want the default behaviour :slight_smile:

  3. QueryOptions.StoreByClass :: boolean = false

    • This boolean determines wether or not you are able to use Zone:GetItemsWhichAreA().
      The reason why this needs to exist is because it does more index operations per :Update() which may be undesirable.

  4. QueryOptions.AcceptMetadata :: boolean = true

    • This boolean determines wether or not Zone.Query() returns which are of type Metadata will be accepted differently as opposed to like other returns.

  5. QueryOptions.ThrottlingEnabled :: boolean = false

    • This boolean determines if QueryOptions.UpdateInterval will be considered when updating the zone.

  6. QueryOptions.UpdateInterval :: number = 1/60

    • This number determines the cooldown between Zone updates. If it is higher, the zone will be updated less frequently. This reduces precision but also reduces lag

Constructor

The SimpleZone module has 6 constructors, each of which return a Zone object.

  1. SimpleZone.fromPart(part: BasePart, queryOp: QueryOptions)

    • This constructor creates a Zone using a BasePart.
      If you want your Zone to respect certain geometries, or if you want your Zone to be able to move around with a Part, then use this!

  2. SimpleZone.fromBox(cframe: CFrame, size: Vector3, queryOp: QueryOptions?)

    • This constructor creates a Zone using a bounding box (a CFrame and a Vector3 size!)
      If you wanna save more computation time by using workspace:GetPartBoundsInBox() instead of workspace:GetPartsInPart(), then use this!

  3. SimpleZone.fromBoxes(boxes: {{cframe: CFrame, size: Vector3}}, queryOp: QueryOptions?)

    • If you wish to create a single Zone encompassing multiple bounding boxes, this is the constructor you use!
      This constructor uses a Bounding Volume Hierarchy structure for optimal efficiency when querying multiple bounding boxes.

  4. SimpleZone.fromCustom(queryFn: (params: OverlapParams?) -> {Instance}, queryOp: QueryOptions)

    • If any of the previous constructors didn’t suit your needs, then you can use SimpleZone.fromCustom(...) and provide your own custom query function!
      Make sure it returns an array of Instances.

  5. SimpleZone.new(...)

    • This is the overloaded constructor function that encompasses the first 2 constructor function.
      If you don’t wanna waste time typing out all those letters, then use this!

  6. SimpleZone.fromPartParallel(part: BasePart, queryOp: QueryOptions?)

    • This constructor is similar to SimpleZone.fromPart(), except it constructs a new Actor for dealing with spatial queries.
      Due to this, it significantly speeds things up, especially for hundreds of Zones!

And that’s basically all of the constructor functions! Now onto the methods of Zone itself.

Methods and Callbacks

The Zone object has quite a number of methods, so lets go through them one by one!

  1. Zone:BindToHeartbeat(params: OverlapParams?): ()

    • Once you’ve created your Zone using one of the 4 constructor functions, make sure to call this method to initiate automatic querying of the Zone!
      Otherwise, none of the other things would really work :sweat_smile:

  2. Zone:UnbindFromHeartbeat(): ()
    • This one is self-explanatory, really. It just stops the current heartbeat loop (if any).
      Useful if you want to pause the Zone query, maybe for a instakill area that periodically deactivates!


      Note that calling this will also clear all stored items inside the Zone.

  3. Zone:ListenTo(datatype: string, mode: "Entered"|"Exited", fn: (item: Instance) -> ()): RBXScriptConnection

    • Now, with all this Zone query stuff, you probably want to detect when something of a certain ClassName datatype enters/exits the Zone.


      And that’s exactly what this method is for!


      You can choose if fn will be called when the item enters or exits with the mode argument.

  4. Zone:SearchFor(properties: {[string]: "Tag"|any}, mode: "And"|"Or"): {BasePart}

    • This is personally my favorite method, it allows you to search the Zone for Instances matching specific properties!
      • mode argument: What is it?

        If mode is “And”, then this method will only return Instances that fully match the properties provided.
        Otherwise, if mode is “Or”, then this method will return Instances that match atleast 1 of the properties provided.
      • How is the properties table supposed to be formatted like?

        Heres the rundown of how you format the properties table to search for specific attributes, tags, children, parents, etc!

        -- properties table example
        {
        Parent_Parent_Parent_Name = "Foo" -- Yes, you can recursively search parents!
        Child1_Child2_Child3_Name = "Bar" -- Yes, you can even recursively search children!
        
        SomeProperty = 123 -- This is just for normal properties, like Anchored = true for example.
        
        SomeTagName = "Tag" -- If you want to specify that something is a tag, simply change its value to "Tag"!
        Attribute_SomeAttributeName = "Baz" -- If you want to specify that something is an attribute, simply prefix "Attribute_" before the name!
        }
        

  1. Zone.Query(params: OverlapParams?): {Instance}

    • This is a callback function that will be called anytime any of the Zones methods needs to query the Zone.
      You can change this to something else if you wish to update the query part/bounding box, for example!

  2. Zone:Update(params: OverlapParams?, onEnter: boolean?, onExit: boolean?): ()

    • All this method does is simply update the items of the Zone and fire the corresponding events based on the result of Zone.Query().
      This is the method that Zone:BindToHeartbeat() calls every heartbeat

  3. Zone:Destroy()

    • If you don’t want a Zone to exist anymore (maybe to free up some memory), use this method to completely destroy it!

  4. Zone:TrackItem(item: any)

    • If a descendant of item is found during query, item is returned instead of the descendant, similar to ZonePlus:trackItem(…).
      Note that you need to set TrackItemEnabled in the QueryOptions to true for this to work!

  5. Zone:UntrackItem(item: any)

    • Self explanatory xd

And that’s all of the methods covered! I know, quite short, right? Now, onto the events!

Events

The Zone object only has 2 events, so this should be pretty short!

  1. Zone.ItemEntered<Instance>

    • This event fires whenever a Part or Player is detected entering the Zone, and passes it as an argument to the connected function.
      However, this event fires for every type of item that enters. If you wish to narrow it down to either Parts or Players, then use Zone:GetItemSignal(...) instead!

  2. Zone.ItemExited<Instance>

    • Similar to Zone.ItemEntered, except when an item exits the Zone!

And that’s all! Now, by now you may be wondering how to use this all together. Well, I’ve put together a section going over use cases for EVERYTHING!

Example Usage
  1. A biome script!
local Zone = require(path_to_simplezone)

local biomeZone = Zone.fromBox(workspace.Biome.CFrame, workspace.Biome.Size) -- Using a Part here instead of a Model, doesn't affect anything as long as you provide the correct arguments!

biomeZone:BindToHeartbeat()

biomeZone:ListenTo("Player", "Entered", function(player: Player)
	player:SetAttribute("Biome", "ThisBiome") -- Setting a Biome attribute on the player to this biome!
end)

biomeZone:ListenTo("Player", "Exited", function(player: Player)
	player:SetAttribute("Biome", "None") -- Player has exited the zone, so theyre not in any biome!
end)
  1. A periodically-disabling instakill area!
local Zone = require(path_to_simplezone)
local QueryOptions = Zone.QueryOptions

-- We only care about the players entering the zone, dead players cant leave! XD
local options = QueryOptions.new()
options.FireMode = "OnEnter"

local killZone = Zone.fromBox(workspace.KillZone.CFrame, workspace.KillZone.Size, options)

killZone:ListenTo("Player", "Entered", function(player: Player)
	local character = player.Character
	local humanoid = character:WaitForChild("Humanoid")
	
	-- KILL THEM >:3
	humanoid.Health = 0
end)

while true do
	killZone:BindToHeartbeat()
	task.wait(10)
	-- Disable the zone for 10 seconds!
	killZone:UnbindFromHeartbeat()
	task.wait(10)
end
  1. A zone that destroys any parts that come inside it!
local Zone = require(path_to_simplezone)
local QueryOptions = Zone.QueryOptions

-- We only care about the parts entering the zone, because were destroying the parts!!!!
local options = QueryOptions.new()
options.FireMode = "OnEnter"

local killZone = Zone.fromBox(workspace.DestroyZone.CFrame, workspace.DestroyZone.Size, options)

killZone:BindToHeartbeat()

killZone:ListenTo("Part", "Entered", function(part: Part)
	part:Destroy() -- DIEEEE
end)
  1. A zone that kills anyone who exits it!
local Zone = require(path_to_simplezone)
local QueryOptions = Zone.QueryOptions

-- We only care about the players exiting the zone, players who stay inside it are good boys >:3
local options = QueryOptions.new()
options.FireMode = "OnExit"

local roomZone = Zone.fromBox(workspace.RoomBox.CFrame, workspace.RoomBox.Size, options)

roomZone:BindToHeartbeat()

roomZone:ListenTo("Player", "Exited"):Connect(function(player: Player)
	local character = player.Character
	local humanoid = character:WaitForChild("Humanoid")
	
	-- DIEIEEEEE..... AGAIN!!!
	humanoid.Health = 0
end)

Hopefully these use cases can help you see how and where to use SimpleZone!

Flaws

Unfortunately, as simple as SimpleZone is, it doesn’t come without its flaws… well, flaw, really.

Because all queries with SimpleZone are done with a .Query function (with no actual volume information being stored inside the Zone itself), that means functions like :findPoint() or :getRandomPoint() in ZonePlus aren’t able to be implemented into SimpleZone.

For the time being, SimpleZone is purely a spatial-querying module.

SimpleZone Best Practices

In order to reduce optimize zones as much as possible, here are things you can do:

  1. Use SimpleZone.fromParts/SimpleZone.fromBoxes instead of SimpleZone.fromPart/SimpleZone.fromBox for alot of zones

    • If you create a new zone for every single one of your zone parts using SimpleZone.fromPart, this is gonna be extremely laggy as it doing multiple spatial queries not in bulk but seperated, and as we all know doing things in bulk is always faster.

      On the other hand, if you create 1 zone for all your zone parts with SimpleZone.fromParts, there will be a dramatic increase in performance because now all parts are handled through a BVH query system at once per update instead of over multiple updates.

  2. Provide an OverlapParams to Zone:BindToHeartbeat()

    • Alot of people assume this module automatically uses OverlapParams when doing queries similar to ZonePlus, however it does not (atleast for the premade constructors.)

      Lets say you have a Zone whos pure purpose is for detecting players enter and exiting, the most optimal way to do this is to pass an OverlapParams with RaycastFilterType.Include which includes all player characters to BindToHeartbeat.
      This will reduce query size.

      Then theres also OverlapParams.MaxParts and OverlapParams.Tolerance which you can also edit to further reduce checks.

  3. Use QueryOptions.ThrottlingEnabled and QueryOptions.UpdateInterval to reduce load for zones where precision is not necessary

    • Lets say i have a big zone whos pure purpose is for visual biomes, a good way to reduce lag is to set QueryOptions.ThrottlingEnabled to true and increase QueryOptions.UpdateInterval to say 1/30 instead of the default 1/60.

      This will reduce the amount of checks per frame and make the game alot smoother for basically no cost.

And that’s all! Let me know if you find any errors/bugs, and happy (late) new yeaaarr!!!

Download SimpleZone now! →
SimpleZone.rbxm (22.0 KB)

Test place download →
SimpleZoneTest.rbxl (523.7 KB)

(MARKETPLACE LINK IS BROKEN, WAITING FOR ROBLOX SUPPORT TO RESOLVE)

If you want some more features like :GetRandomPoint() or :IsPointWithinZone(), check out LessSimpleZone, an extension of SimpleZone! →

Was this module useful for you?

  • Yes
  • No (Please post your reason down in the replies!)

0 voters

If you want to, you can support my work through this 10 robux donation tshirt :slight_smile:

Your donasion means alot!, and it also means i can keep making updates and more cool resources :heart:

54 Likes

:frowning:

7 Likes

Oops, I forgot to public the module! Give me a second…

1 Like

The module should be public now, enjoy!!!

2 Likes

Update: Changed the module link into a .rbxm download to utilize PackageLinks for ease of updates :slight_smile:

2 Likes

Hm. I would’ve went with the usual .PlayerEntered event format.

4 Likes

I find this cuts down on the amount of members in the object, which is ideal because it is called “SimpleZone” afterall :sweat_smile: If I added a .PlayerEntered and a .PlayerExited, then id have to do the same for parts… and then also have the .ItemEntered and .ItemExited event, that’s 6 events!!

5 Likes

Update: Added a Zone:Update(...) method so that you aren’t forced to use Zone:BindToHeartbeat()!

3 Likes

Great module! I was looking for a functioning zone module for such a long time.

3 Likes

BugReports!

1.
The path to “SimpleSignal” Is wrong in “Types”

Path = Replicated.Modules.Utility.SimpleSignal

Screenshot:
image

2.
Creating a localized queryoptions bugs due to the “table.freeze”

Screenshots:
image

Error:

2 Likes

For the first one, simply just fix it by requiring the SimpleSignal module dependency. I forgot to change it :sweat_smile:

  1. Yeah just remove the table freeze if you want, will be fixing these issues in the download soon, currently away from home
2 Likes

Thats kind of weird though, are you sure you didnt download the old version? Ive published the version with no bugs to the package link

2 Likes

The download link has been updated, I’ll look into why the PackageLink isn’t working

3 Likes

Ok so I think what you did was you downloaded the module through the marketplace, which is no longer being updated xd, also you edit the query options this way, as QueryOptions.new() does not accept any arguments:

local options = ZoneModule.QueryOptions.new()
options.FireMode = "OnEnter"

You could use parallel luau to improve performance for games with many zones, GetPartBoundsInBox can eat up performance https://youtu.be/kBUA-UTjBBQ

Ooh, good idea, I’ll add an option for this.

Give me a minute…

asdadasdadasad

Added!

This is about 40 zones, not much lag even on my bad PC (most of the lag youre seeing is from the pure act of recording xd)!

2 Likes

Feel free to test more zones yourself!

I also added it into the documentation!