I took a look at your code, and I unfortunately cannot exactly tell you what is wrong, but from the looks of it, it seems as if the script “adds” the player to the region, does what it has to do, and then “removes” them regardless of whether they are in the region or not. This loops through and the player is stuck in an infinite loop. I doubt this is a GetPartsInPart()
problem though.
My suggested solution to this is to scrap this and go for a more OOP-based approach. In this approach, you’ll create a Region class which handles what players are in the zone and what are not (and also is responsible for telling other scripts what player has entered and left the area).
OOP Implementation
To detect what players enter and leave, you can follow a similar approach to yours. Have an array of players that are already in the region (initially an empty array), and then check what players are currently inside the region. I highly suggest the use of WorldRoot:GetPartBoundsInBox()
instead of WorldRoot:GetPartsInPart()
as the former is faster and cheaper (performance-wise) since it checks a much simpler area.
After retrieving all the parts in the region (I highly suggest the use of a whitelist to save time on the loop), check if the player already exists in the players array, and if it does, remove them and add them to a new, temporary array for players currently in the region. If the player does not exist in the player array already, then it means that they just entered the region.
Once you loop through all the players in the region, the players array might have leftover players. Any players that are in that array are players who left the region after the previous check. From here, just make the players array equal to the current players array. The check ends here.
Using this should solve your issue, and at the same time, make your code cleaner and more modular. Although I don’t recommend using OOP (Object-Oriented Programming) for everything (the horrors of Java strike back), it is good to condense reused code into classes for a cleaner script.
You can try coding this on your own for the experience. I did code this myself and tested it out, and the final code for the checking of regions looked like this:
-- Services --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Classes --
local Classes = ReplicatedStorage:WaitForChild("Classes")
local RegionClass = require(Classes.RegionTemplate)
-- Scripting --
for _, RegionPart in ipairs(workspace.Regions:GetChildren()) do
local Region = RegionClass.new(RegionPart)
Region.PlayerEntered:Connect(function(Player)
print("Player "..Player.Name.." entered "..RegionPart.Name.."!")
end)
Region.PlayerLeft:Connect(function(Player)
warn("Player "..Player.Name.." left "..RegionPart.Name.."!")
end)
coroutine.resume(coroutine.create(function()
while task.wait(0.2) do
Region:UpdatePlayersWithinRegion()
end
end))
end
As you can see, with this approach, the code is much cleaner and you have more control over everything. You can make use of events (I used a custom solution) for the PlayerEntered
and PlayerLeft
and handle each case in whichever way you want. Good thing about classes is that you can easily create new ones with a single line of code while also having the luxury of handling specific logic cases.
Rest of the code
Like I said before, you can try to write this on your own. I highly suggest doing so, but it is understandable if you don’t know how to. If you do copy my code, do try to read through it and understand it. Understanding how something works is going to be essential for your learning experience. Anyways, the important modules are below:
Region Class
-- Services --
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Libraries --
local Libraries = ReplicatedStorage:WaitForChild("Libraries")
local ChangedEventClass = require(Libraries.ChangedEvent)
-- Class --
local RegionObject = {}
RegionObject.__index = RegionObject
function RegionObject.new(_Region: BasePart)
-- Metadata --
local NewRegion = {__type = "RegionObject"}
local FakeRegion = {}
-- Variables --
NewRegion.Region = _Region
NewRegion.Players = {}
local PlayerEntered = ChangedEventClass.new()
local PlayerLeft = ChangedEventClass.new()
local ChangedEvent = ChangedEventClass.new()
-- Functions --
function NewRegion:UpdatePlayersWithinRegion()
local RegionPart = NewRegion.Region
local PlayerCharacters = {}
for _, Player in pairs(Players:GetPlayers()) do
if Player.Character and Player.Character.HumanoidRootPart then
table.insert(PlayerCharacters, Player.Character.HumanoidRootPart)
end
end
local BoundingParams = OverlapParams.new()
BoundingParams.FilterType = Enum.RaycastFilterType.Include
BoundingParams.FilterDescendantsInstances = PlayerCharacters
local TouchingParts = game.Workspace:GetPartBoundsInBox(RegionPart.CFrame, RegionPart.Size, BoundingParams)
local CurrentPlayers = {}
for _, Part in pairs(TouchingParts) do
local Player = Players[Part.Parent.Name]
if Player then
table.insert(CurrentPlayers, Player)
local PlayerIndex = table.find(NewRegion.Players, Player)
if PlayerIndex then
table.remove(NewRegion.Players, PlayerIndex)
else
PlayerEntered:Fire(Player)
end
end
end
for _, Player in ipairs(NewRegion.Players) do
PlayerLeft:Fire(Player)
end
FakeRegion.Players = CurrentPlayers
end
-- Metatable --
setmetatable(FakeRegion, {
__index = function(Table, Key)
if Key:lower() == "changed" then return ChangedEvent end
if Key:lower() == "playerentered" then return PlayerEntered end
if Key:lower() == "playerleft" then return PlayerLeft end
return NewRegion[Key]
end,
__newindex = function(Table, Key, Value)
if NewRegion[Key] == Value or Key:lower() == "changed" or Key:lower() == "playerentered" or Key:lower() == "playerleft" then return end
NewRegion[Key] = Value
ChangedEvent:Fire(Key, Value)
end,
})
-- Return --
return FakeRegion
end
-- Return --
return RegionObject
Changed Event Class
-- Class --
local ChangedEvent = {}
ChangedEvent.__index = ChangedEvent
function ChangedEvent.new()
-- Metadata --
local Event = {}
Event.Connections = {}
-- Methods --
function Event:Connect(Function)
local LocalConnectionTable = {}
function LocalConnectionTable:Disconnect()
local Index = table.find(self.Connections, Function)
if Index then
table.remove(self.Connections, Index)
return true
end
return false
end
function LocalConnectionTable:disconnect()
return LocalConnectionTable:Disconnect()
end
table.insert(self.Connections, Function)
return LocalConnectionTable
end
function Event:Fire(...)
for _, Connection in self.Connections do
Connection(...)
end
end
function Event.DisconnectAll()
table.clear()
end
Event.changed = Event.Changed
-- Return --
setmetatable(Event, ChangedEvent)
return Event
end
-- Return --
return ChangedEvent
You can also download the rblx
to see it in action and mess around a bit. Here it is:
RegionDetectionTest.rbxl (45.0 KB)
Hopefully this helped you solve your problem. If you have any questions, please let me know.