Placement system collision not filtering descendant

i currently have a problem with my placement system.

I want to make a collision function using :GetTouchingParts and i want to filter some parts, i used table.remove() but it doesn’t work somehow.

local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local TweenInfo = TweenInfo.new(0.5)

local PlaceEvent = game:GetService("ReplicatedStorage").PlaceEvent

local currentMode = nil
local currentObject = nil
local mouseHitPart = nil
local mouseHitPos = nil
local mouseHitNormal = nil
local canplace = false

local orientation = CFrame.new()

local player = game:GetService("Players").LocalPlayer
local camera = workspace.CurrentCamera

local function raycast()
	local mousePos = UserInputService:GetMouseLocation() -- Returns a Vector2 of the mouse position

	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {workspace:FindFirstChild("Placement")} -- Ignore the object we're placing and the character
	params.FilterType = Enum.RaycastFilterType.Include

	local unitRay = camera:ScreenPointToRay(mousePos.X, mousePos.Y) -- This creates a UnitRay from the mouse's position

	return workspace:Raycast(unitRay.Origin, unitRay.Direction * 200, params)
end

local function getRotatedSize(size)
	local newModelSize = orientation * CFrame.new(size) -- Multiply the orientation by the size
	newModelSize = Vector3.new(
		math.abs(newModelSize.X),
		math.abs(newModelSize.Y),
		math.abs(newModelSize.Z)
	) -- math.abs so our results aren't negative

	return newModelSize
end

local function getPlacementPos(size)
	local newModelSize = getRotatedSize(size)

	return Vector3.new(
		math.floor(mouseHitPos.X / 0.5 + 0.5) * 0.5 + mouseHitNormal.X * (newModelSize.X / 2),
		math.floor(mouseHitPos.Y / 0.5 + 0.5) * 0.5 + mouseHitNormal.Y * (newModelSize.Y / 2),
		math.floor(mouseHitPos.Z / 0.5 + 0.5) * 0.5 + mouseHitNormal.Z * (newModelSize.Z / 2)
	) -- Snap the position to 0.5 and offset it using the surface normal
end

local function getTouchingParts(part)
	local connection = part.Touched:Connect(function() end)
	local result = part:GetTouchingParts()
	connection:Disconnect()
	return result
end

local function checkCollision(instance) 
	local IsColliding = false
	if instance:IsA("BasePart") then 
		local result = getTouchingParts(instance)
		for i, v in pairs(result) do
			if v:IsDescendantOf(instance.Parent) then
				table.remove(result,i)
				print(v)
			end
		end
		if #result ~= 0 then IsColliding = true end
	end

	return IsColliding
end

UserInputService.InputBegan:Connect(function(input, gp)
	if gp then return end

	local key = input.KeyCode

	if key == Enum.KeyCode.E then
		if currentObject then
			currentObject:Destroy()
			currentObject = nil
		end

		if currentMode ~= "Build" then
			currentMode = "Build"
			currentObject = workspace.Chair:Clone()
			currentObject.Parent = workspace
			currentObject.PrimaryPart = currentObject:FindFirstChild("Main")
			currentObject.PrimaryPart.Transparency = 1
			for i, item in pairs(currentObject:GetChildren()) do
				if item ~= currentObject.PrimaryPart then
					item.Material = Enum.Material.ForceField
					item.CanCollide = false
				end
			end
		else
			currentMode = nil
		end
	elseif key == Enum.KeyCode.R then
		orientation = CFrame.Angles(0, math.rad(90), 0) * orientation
	elseif key == Enum.KeyCode.T then
		orientation = CFrame.Angles(0, 0, math.rad(90)) * orientation
	elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
		if currentMode == "Build" and currentObject ~= nil then
			if canplace == true then
				PlaceEvent:FireServer(currentObject.Name, currentObject.PrimaryPart.CFrame, orientation)
			end
		elseif currentMode == "Destroy" and currentObject == nil then
			-- placeholder
		end
	elseif key == Enum.KeyCode.G then
		if currentObject then
			currentObject:Destroy()
			currentObject = nil
		end
		if currentMode ~= "Build" then
			currentMode = "Destroy"
		end
	end
end)

game:GetService("RunService").Heartbeat:Connect(function()
	local raycastResult = raycast() or {}

	mouseHitPart = raycastResult.Instance
	mouseHitPos = raycastResult.Position
	mouseHitNormal = raycastResult.Normal

	if currentMode == "Build" then
		if mouseHitNormal and mouseHitPos and currentObject then
			currentObject:SetPrimaryPartCFrame(CFrame.new(getPlacementPos(currentObject.PrimaryPart.Size)) * orientation)
		end

		local iscolliding = checkCollision(currentObject.PrimaryPart)

		if iscolliding and currentObject ~= nil then
			for i, item in pairs(currentObject:GetChildren()) do
				if item ~= currentObject.PrimaryPart then
					item.BrickColor = BrickColor.new("Really red")
					canplace = false
				end
			end
		else
			for i, item in pairs(currentObject:GetChildren()) do
				if item ~= currentObject.PrimaryPart then
					item.BrickColor = BrickColor.new("Lime green")
					canplace = true
				end
			end
		end
	end
end)

if possible, i want feedback too for my placement system (and collision), i want to know if my script is efficient enough or should i use just use a module.
I would really appreciate any help. Thank you.

1 Like

BasePart:GetTouchingParts() is deprecated. Use WorldRoot:GetPartsInPart(): https://create.roblox.com/docs/reference/engine/classes/WorldRoot#GetPartsInPart. This accepts two arguments, the part, and an OverlapParams: OverlapParams | Documentation - Roblox Creator Hub. Using the OverlapParams, you can filter some instances out.

2 Likes

i changed it and used :GetPartsInPart(): and filtered the currentObject’s children variable out but it seems like it still doesn’t work for some reason.

local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local TweenInfo = TweenInfo.new(0.5)

local PlaceEvent = game:GetService("ReplicatedStorage").PlaceEvent

local currentMode = nil
local currentObject = nil
local mouseHitPart = nil
local mouseHitPos = nil
local mouseHitNormal = nil
local canplace = false

local orientation = CFrame.new()

local player = game:GetService("Players").LocalPlayer
local camera = workspace.CurrentCamera

local function raycast()
	local mousePos = UserInputService:GetMouseLocation()

	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {workspace:FindFirstChild("Placement")}
	params.FilterType = Enum.RaycastFilterType.Include

	local unitRay = camera:ScreenPointToRay(mousePos.X, mousePos.Y)

	return workspace:Raycast(unitRay.Origin, unitRay.Direction * 200, params)
end

local function getRotatedSize(size)
	local newModelSize = orientation * CFrame.new(size) 
	newModelSize = Vector3.new(
		math.abs(newModelSize.X),
		math.abs(newModelSize.Y),
		math.abs(newModelSize.Z)
	)

	return newModelSize
end

local function getPlacementPos(size)
	local newModelSize = getRotatedSize(size)

	return Vector3.new(
		math.floor(mouseHitPos.X / 0.5 + 0.5) * 0.5 + mouseHitNormal.X * (newModelSize.X / 2),
		math.floor(mouseHitPos.Y / 0.5 + 0.5) * 0.5 + mouseHitNormal.Y * (newModelSize.Y / 2),
		math.floor(mouseHitPos.Z / 0.5 + 0.5) * 0.5 + mouseHitNormal.Z * (newModelSize.Z / 2)
	)
end

local function getTouchingParts(part, params)
	local connection = part.Touched:Connect(function() end)
	local result = workspace:GetPartsInPart(part, params)
	connection:Disconnect()
	return result
end

local function checkCollision(instance) 
	local IsColliding = false
	if instance:IsA("BasePart") then 
		for i, v in pairs(instance.Parent:GetChildren()) do
			local filter = {}
			filter[i] = v
			local params = OverlapParams.new()
			params.FilterType = Enum.RaycastFilterType.Exclude
			params.FilterDescendantsInstances = filter
		end
	end
	return IsColliding
end

UserInputService.InputBegan:Connect(function(input, gp)
	if gp then return end

	local key = input.KeyCode

	if key == Enum.KeyCode.E then
		if currentObject then
			currentObject:Destroy()
			currentObject = nil
		end

		if currentMode ~= "Build" then
			currentMode = "Build"
			currentObject = workspace.Chair:Clone()
			currentObject.Parent = workspace
			currentObject.PrimaryPart = currentObject:FindFirstChild("Main")
			currentObject.PrimaryPart.Transparency = 1
			for i, item in pairs(currentObject:GetChildren()) do
				if item ~= currentObject.PrimaryPart then
					item.Material = Enum.Material.ForceField
					item.CanCollide = false
				end
			end
		else
			currentMode = nil
		end
	elseif key == Enum.KeyCode.R then
		orientation = CFrame.Angles(0, math.rad(90), 0) * orientation
	elseif key == Enum.KeyCode.T then
		orientation = CFrame.Angles(0, 0, math.rad(90)) * orientation
	elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
		if currentMode == "Build" and currentObject ~= nil then
			if canplace == true then
				PlaceEvent:FireServer(currentObject.Name, currentObject.PrimaryPart.CFrame, orientation)
			end
		elseif currentMode == "Destroy" and currentObject == nil then
			-- placeholder
		end
	elseif key == Enum.KeyCode.G then
		if currentObject then
			currentObject:Destroy()
			currentObject = nil
		end
		if currentMode ~= "Build" then
			currentMode = "Destroy"
		end
	end
end)

game:GetService("RunService").Heartbeat:Connect(function()
	local raycastResult = raycast() or {}

	mouseHitPart = raycastResult.Instance
	mouseHitPos = raycastResult.Position
	mouseHitNormal = raycastResult.Normal

	if currentMode == "Build" then
		if mouseHitNormal and mouseHitPos and currentObject then
			currentObject:SetPrimaryPartCFrame(CFrame.new(getPlacementPos(currentObject.PrimaryPart.Size)) * orientation)
		end

		local iscolliding = checkCollision(currentObject.PrimaryPart)

		if iscolliding and currentObject ~= nil then
			for i, item in pairs(currentObject:GetChildren()) do
				if item ~= currentObject.PrimaryPart then
					item.BrickColor = BrickColor.new("Really red")
					canplace = false
				end
			end
		else
			for i, item in pairs(currentObject:GetChildren()) do
				if item ~= currentObject.PrimaryPart then
					item.BrickColor = BrickColor.new("Lime green")
					canplace = true
				end
			end
		end
	end
end)

I can still place the currentObject when its colliding

1 Like

For Raycast Filter Types, they follow a whitelist or blacklist system. You can think of a whitelist as an exclusive party; you need to have your name on the list to be invited inside. A blacklist is more like buying an airplane ticket; there is a no-fly list, and you need your name to not be on there to buy the ticket. If you want your raycast to automatically include everything, but exclude just the parent parts, you need a blacklist, or an Exclude filter type.

1 Like

As for your script, I would say it’s pretty good, but there are a few parts I find a little weird:

  1. Why don’t you use mouse.Hit.Position? I see the 200, but you could just see if the position is less than 200 studs from the camera.
  2. The connection to grab parts isn’t necessary, instead just switch Connect to Once, and the connection will only be fired once.
  3. The filter array you create when going through the colliding parts serves no purpose, so not sure why it’s there.
  4. Pretty small, but in your InputBegan function, you don’t need to check if CanPlace is equal to true, as it is already a Boolean.
  5. SetPrimaryPartCFrame is deprecated, so use PivotTo instead (pivots the model).
  6. You set canPlace to true/false inside for loops, which should only be done once per frame.

It is structured and organized pretty well though. I’ve written both of these responses from my phone, so there may be spelling/grammar mistakes.

1 Like

i’m very very sorry about the late reply (i took a break for a while bc of burnout) but i made a solution according to your advice and changed the script a bit. (i followed an old community tutorial so the script is a bit outdated which i didn’t realize that for a while)

also, thanks for your help.

1 Like

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