GetPartsInPart Glitching

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? I am making a area where the players can stay in safely, and if they go out they get teleported back, but there’s a annoying bug that RUINS EVERYTHING

  2. What is the issue? Using my latest script that uses GetPartsInPart and my custom TouchEnded function for that event I made a entity similar to Glitch from DOORS, which will trigger when you are out of bounds. But this is what happens:

(The purple boxes are the safe areas)
As you can see when I enter the purple boxes, the game prints that I entered the safe zone, but a second later it summons the glitch-like entity because it also thinks I just left the safe area and I’m technically out of bounds. And it does that on repeat.

coroutine.wrap(function() -- Always checks 4 people
	wait(2)
	while wait(1) do
		for _,area in ipairs(GlitchSafeZones) do
			local OverlapParamsZone = OverlapParams.new()
			
			OverlapParamsZone.FilterType = Enum.RaycastFilterType.Exclude
			OverlapParamsZone.FilterDescendantsInstances = {area}
			
			
			--print(area.Name)
			
			local TouchingParts = workspace:GetPartsInPart(area, OverlapParamsZone)
			
			for _,part in ipairs(TouchingParts) do
				local Player = game.Players:GetPlayerFromCharacter(part.Parent)
				
				if Player then
					local root = Player.Character:FindFirstChild('Torso')
					if not table.find(TOUCHING_GLITCH_AREAS, root) then
						print('ok uenterd')
						if root then
							wasinzonebefore = true
							table.insert(TOUCHING_GLITCH_AREAS, root)
							print('glitc  safezone')
						
							wasinzonebefore = true
						else
							
						end
					end
				end
			end
			
			for _,plr in ipairs(game.Players:GetPlayers()) do
				local character = plr.Character
				
				if character then
					local root = plr.Character:FindFirstChild('Torso')
					

					if root then
						if table.find(TOUCHING_GLITCH_AREAS, root) then
							if not table.find(TouchingParts, root) then
								print('ok u left')
								if wasinzonebefore == true then
									if _G.GlitchEnabled == true then
										
										game.ReplicatedStorage.Storage.Remotes.GlitchRemote:FireClient(plr, "Glitch Appear")
										table.remove(TOUCHING_GLITCH_AREAS, table.find(TOUCHING_GLITCH_AREAS, root))
										
										print('LEFT THE ANTI-GLITCH AREA !!')
										wait(1)
										wasinzonebefore = false
									end
								end
								--if SpamDebounce2 == false then
	
								--end
							end
						end
					end
				end
			end
		end
	end
end)()
  1. What solutions have you tried so far? I used a lot of debounce as you can see, yet nothing helps.

i hope there is a solution to this bug but ill try rescripting the thing entirely

1 Like

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.

Thanks for your reply!

I’ll try using your advice and I’ll see if it works or not. :smiley:

1 Like

Although one small issue
What’s annoying is that there are multiple safe zone regions that have the size of the room they are in.

Well, when i go to a different room, the glitchy entity triggers because I left the region. Otherwise you really helped, now I need to get rid of this bug.

EDIT:I will try union the parts and see if it works

You can always expand on the system and class. You can, for instance, add another variable to the class called “RegionType” or “RegionName” so you can “merge” regions without actually merging them (or use inheritance, although I’m not entirely sure how well Lua supports that). If the glitch triggers every time you switch rooms, maybe add some type of check to see if they entered a new room, and if they did, what room. You can also overlap regions too, which means that you can eliminate the chance of the player not being in a region when leaving one (so the player will always be in at least one region, unless they are out of bounds). There is a lot of stuff you can do, so feel free to get creative and expand. My idea just covers the basic usage of entering and leaving regions (I still don’t understand how the glitch works and when it should activate).

I turned the whole area into a giant invisible union, and it worked. (using a script of course)

If you end up doing that, please use WorldRoot:GetPartsInPart() instead (assuming that you used my implementation). The WorldRoot:GetPartBoundsInBox() checks a box region, so it would not check the union geometry accurately. Make use of WorldRoot:GetPartsInPart() as it takes actual volume and geometry into consideration when doing collision calculations. It is a simple change, just change

local TouchingParts = game.Workspace:GetPartBoundsInBox(RegionPart.CFrame, RegionPart.Size, BoundingParams)

to

local TouchingParts = game.Workspace:GetPartsInPart(NewRegion.Region, BoundingParams)

(And you can delete the the local RegionPart = NewRegion.Region)

In my opinion, a less hacky solution would work better, but you can use this in the meantime and focus on a less-hacky solution in the future.

1 Like

So I have to use GetPartsInPart() instead of GetPartBoundsInBox() now?

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.