ZonePlus v3.2.0 | Construct dynamic zones and effectively determine players and parts within their boundaries

You should try the template place to understand how it works.

No, the module has perfect autocomplete normally when I use it, I don’t think I’m doing anything wrong it looks the same as how I always used it

Is there a way to use the same container in different zones?

i.e;

local ZoneOne = ZonePlus.new(workspace.Zone)
ZoneOne:bindToGroup("One")
ZoneOne.localPlayerEntered:Connect(function()
	print("Entered zone one!")
end)

local ZoneTwo = ZonePlus.new(workspace.Zone)
ZoneTwo:bindToGroup("Two")
ZoneTwo.localPlayerEntered:Connect(function()
	print("Entered zone two!")
end)

When doing this, only ZoneTwo’s localPlayerEntered event gets called. Binding them to different groups also doesn’t seem to solve the issue.

My use case is that I have one zone that has various mechanism acting on it. For example, ambience and game mechanics.

-- In AmbienceController.lua
Zone.localPlayerEntered:Connect(function()
    game.Lighting.Ambient = Color3.new(0.5, 0.5, 0.5)
    game.Lighting.OutdoorAmbient = Color3.new(0.5, 0.5, 0.5)
end)
-- In MinigameController.lua
Zone.localPlayerEntered:Connect(function()
    startMiniGame()
end)

I do not necessarily want to merge both of these into one script. Apologize if otherwise but the documentation does not seem to explain this.

For now, my temporary solution is to initialize only one zone in a controller and create a getter function ZoneController:GetZoneForArea(area) and act on that instead.

Ideally, being able to bind to different groups to create two different functionalities for the same container would be best, so I do not need to have one system just dedicated to managing ‘zones’.

EDIT: FIXED ALREADY, HAD TO DEAL WITH OVERLAP AND RAYCASTPARAMS


Hello! I’m using this system to make a “zone entering and leaving notification” to a game i’m working on. But i’m having some troubles that the query system is affecting the camera (making it zoom in as it had collision) and blocking shots (as seen in the video below 0:06)

I’m separating each zone with large parts

Is there a way to fix those problems i’m having? (I got zero to none knowledge with query, so i really dont know if there is a method that i could use to avoid those)

Watch Desktop 2024.05.15 - 19.14.52.11 | Streamable (somehow this video isnt embedding so… sorry about that)

1 Like

In my map, there are gonna be big areas and I am gonna be detecting when the player enters a certain area, what would be the best way to do this? I can imagine that having big parts to represent the areas might cause lag

Hello, @ForeverHD!

Trying to run :getRandomPoint on an unanchored part returns a exhaustion timeout request, this was a mistake on my end as the zone was not intended to be unanchored, but I thought it would be best to let you know.

Thanks!

2 Likes

Hey, I know that this is a old topic, but i can’t seen to be found a thing, I want to make custom zones for different ambients, that means like desert zones, ice zones etc, so how can i get the zone part name without being from a variable?

Use the example game. It is uncopylocked and you can look at the code

You would just do:

local Part = game.Workspace:WaitForChild("Part Name Here")

bro, what did i just said? i said without it being from a variable, i want to check which zone name when the player touches the zone

Loop through all zones

It should look something like this:

local ZoneFolder = Put all of the zones in a folder and reference it here
for _,Zone in ZoneFolder do
     Zone.PlayerEntered:Connect(function(plr)
           print(plr.Name.." has entered "..Zone.Name
     end
end

k, thanks i’m gonna try it and let you know if it works

Im having this errors on the tracker and it might gave me a memory leak. Error came from the client since I have zones that are only controlled client side.

How can I fix this?

2 Likes

Hello,

I am checking out the playground in studio, and like every 3rd time or so, I hit Play, I get these errors…

Anyone else, and or why? It is unchanged, so running as is.


:27.795  ReplicatedStorage.Zone:693: Script timeout: exhausted allowed execution time  -  Client - Zone:632
  12:31:27.795  Stack Begin  -  Studio
  12:31:27.796  Script 'ReplicatedStorage.Zone', Line 632 - function findPoint  -  Studio - Zone:632
  12:31:27.796  Script 'ReplicatedStorage.Zone', Line 693 - function getRandomPoint  -  Studio - Zone:693
  12:31:27.796  Script 'Players.Lord_BradyRocks.PlayerScripts.RandomPoints', Line 26  -  Studio - RandomPoints:26
  12:31:27.796  Stack End  -  Studio

Read this, this may be your issue.

Best,
Matthew

Has anyone used CollectionService to dynamically build/destroy zones? I’m able to add new zones easily but destroying them I found a little more challenging. In a nut shell: how do I determine which zone to destroy given the basepart (container) that is being removed by the collection service?

It is totally possible to create dynamic zones using CollectionService though some testing would be needed on your end to determine the best method.

If your zone only contains one part (The part being added being the zone itself), then it’s pretty straight forward, you can cache the part into some array when GetInstanceAddedSignal is fired.

local zoneForPart = {
-- ex: [ref to Part23] = zone
} :: { [Instance]: Zone }

And upon CollectionService GetInstanceRemovedSignal, simply destroy the zone using zoneForPart[part]:destroy()

This way you’re able to destroy the zone associated to this part.

Though please note: If your zone will be destroyed instead of being removed from the tag, I found a post from HD that said that:

For our next update we’ll have zones automatically destroy/cleanup themselves if their container/descendants are completely destroyed, however for the time being you must ensure you’re calling zone:destroy()

So if this is your case, it could be avoided altogether.

If your case considers 1 zone but multiple parts being into that zone, things get more complicated and the API doesn’t mention any methods for dynamically adding / removing parts from 1 zone, however I found this post which might help you: ZonePlus v3.2.0 | Construct dynamic zones and effectively determine players and parts within their boundaries - #314 by laughablehaha

Best of luck

1 Like
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Events = ReplicatedStorage:WaitForChild("Events")
local Modules = ReplicatedStorage:WaitForChild("Modules")

local Manager = require(game:GetService("ServerScriptService"):WaitForChild("DataService"):WaitForChild("DataManager"))

local ZonePlus = require(Modules:WaitForChild("Zone"))

local Variants = workspace:WaitForChild("Variants")

local boughtData = game:GetService("DataStoreService"):GetDataStore("VariantCopies")

local module = {}

-- Functions
function module:OwnsVariant(player, variantName)
	local success, result = pcall(function()
		return boughtData:GetAsync(player.UserId .. "_" .. variantName)
	end)
	if success then
		print("Data retrieved successfully for player:", player.Name, "Variant:", variantName, "Result:", result)
		return result and result > 0
	else
		warn("Failed to get data for player:", player.Name, "Variant:", variantName, result)
		return false
	end
end

function module:setupVariants()
	for _, variant in pairs(Variants:GetChildren()) do
		local name = variant.Name
		local initialQuantity = variant:WaitForChild("Quantity").Value
		local productId = variant:WaitForChild("ProductId").Value
		local variantZone = ZonePlus.new(variant:WaitForChild("Zone"))

		variantZone.playerEntered:Connect(function(player)
			print("Player entered zone for variant:", name, "Player:", player.Name)
			local copiesLeft = initialQuantity
			local success, result = pcall(function()
				return boughtData:GetAsync("CopiesSold_" .. name)
			end)
			if success and result ~= nil then
				copiesLeft = initialQuantity - result
				print("Copies left for variant:", name, "Copies left:", copiesLeft)
			else
				warn("Failed to get copies sold for variant:", name, result)
			end

			Events:WaitForChild("PromptVariant"):FireClient(player, name, copiesLeft, productId)
		end)

		variantZone.playerExited:Connect(function(player)
			print("Player exited zone for variant:", name, "Player:", player.Name)
			Events:WaitForChild("PromptVariant"):FireClient(player, nil)
		end)

		-- Initialize copies sold in DataStore if not already set
		local successInit, resultInit = pcall(function()
			return boughtData:GetAsync("CopiesSold_" .. name)
		end)
		if successInit and resultInit == nil then
			local successSet, resultSet = pcall(function()
				boughtData:SetAsync("CopiesSold_" .. name, 0)
			end)
			if not successSet then
				warn("Failed to set initial copies sold for variant:", name, resultSet)
			else
				print("Initialized copies sold for variant:", name)
			end
		elseif not successInit then
			warn("Failed to initialize copies sold for variant:", name, resultInit)
		end
	end
end

function module:VariantBought(userId, variantName)
	local success, result = pcall(function()
		return boughtData:IncrementAsync(userId .. "_" .. variantName, 1)
	end)
	if success then
		print("Variant bought recorded for user:", userId, "Variant:", variantName)
		local successCopies, resultCopies = pcall(function()
			return boughtData:IncrementAsync("CopiesSold_" .. variantName, 1)
		end)
		if not successCopies then
			warn("Failed to increment copies sold for variant:", variantName, resultCopies)
		end
	else
		warn("Failed to record variant bought for user:", userId, "Variant:", variantName, result)
	end
end

function module:init()
	print("Initializing Variants Module")
	module:setupVariants()
end

return module

In this script zone plus is causing the script that required the module to require this module to freeze and not continue past requiring it. I know it is zone plus because of my extensive testing and confusion. Any help is appreciated. It isn’t by how I used zoneplus from my testing, it was simply requiring it that caused the issues.

That’s a great idea to keep the array and I will give it a try.

1 Like

What should I do if I have multiple zones of the same name?

PRIORITY TO ZONES
I’m surprised this wasn’t already a feature but I’ve kind of figured out a way to give priority to a zone inside of a zone. This is sort of a hacky solution and could be buggy but I haven’t done enough testing to confirm.

  1. Define what priority is

  2. Make sure your zones are grouped and the “onlyEnterOnceExitedAll” configuration is enabled.
    image

  3. In the ZoneController heartbeat function, add this for loop and redefine the occupantToKeepZone variable to MainComparing.

I don’t know if this works with a zone inside a zone inside a zone but it does work with a zone inside a zone.

3 Likes