High-Speed Cars Stutter and Break When Near Player Despite Collision Groups

Hi everyone,

I’m currently working on a game where high-speed cars move along a set path, but I’m encountering a strange issue. Even though the player’s character is assigned to a collision group that does not collide with the cars’ collision group, the cars seem to stutter or act as if they are colliding when they pass close to the player.

When this happens, the cars sometimes break apart due to a script I’ve added to handle collisions. However, the player is not directly interacting with the cars—there’s no actual physical collision happening. Here’s what I’ve checked so far:

  1. Collision Groups:
  • I verified that the player and car collision groups are properly configured using CollisionGroupsAreCollidable, and they indeed shouldn’t interact.
  • All parts of the player (including accessories) and the car are assigned to their respective groups.
  1. Network Ownership:
  • The cars’ network ownership is assigned to the server to prevent client-side physics issues.
  1. Custom Scripts:

Could you try disabling CanQuery and CanTouch in every single part of the car? Maybe one of those cause issues, especially CanTouch.

Edit: Realized those could break your scripts, but could be worth a try just to test it.

While that resolved the issue, it unfortunately disrupted the scripts and altered the car’s behavior.

Do you have any checks on the Touched events? I’m pretty sure the touch events don’t care if the collisions are on or off.

Yes, the cars do have checks on Touched events. It’s possible that the Touched events are still triggering despite the collision settings. If that’s the case, it might explain the strange behavior. Do you know if there’s a way to prevent Touched events from firing entirely for specific parts or objects, even if they don’t collide?

If I recall correctly, no. Could you mind showing the checks on the events? Maybe you aren’t noticing a mistake

Edit: It might be also possible that (if) you’re performing some raycasts without checking if the hit is a players character.

Apologies if this isn’t clear, as I’m still getting the hang of this, but here’s the script I’m using in ServerScriptService. I’ll also be looking for other scripts that might be involved in this issue. I hope this is helpful!

	local breakParts = {}
	for _, part in pairs(workspace:GetDescendants()) do
		if part:IsA("BasePart") and part.Name == "Break" then
			table.insert(breakParts, {part = part, parent = part.Parent})
		end
	end
	return breakParts
end

local function haveDifferentParents(part1, part2)
	return part1.parent ~= part2.parent
end

local function breakJoints(part)
	part:BreakJoints()
end

local function disableCanCollideInModel(model)
	for _, child in pairs(model:GetDescendants()) do
		if child:IsA("BasePart") then
			child.CanCollide = false
		end
	end
end

local barrierMap = {
	["F1"] = {"Secondinv", "Secondinv 2"},
	["F2"] = {"Barrierinv", "Barrierinv 2"},
	["F3"] = {"Wallinv", "Wallinv 2"},
	["F4"] = {"Fourthinv", "Fourthinv 2"}
}

local function disableBarriersForCars(parent1, parent2)
	local parents = {parent1, parent2}
	for _, parent in pairs(parents) do
		if barrierMap[parent.Name] then
			for _, barrierName in pairs(barrierMap[parent.Name]) do
				local barrierModel = workspace:FindFirstChild(barrierName)
				if barrierModel then
					disableCanCollideInModel(barrierModel)
				end
			end
		end
	end
end

local function isPrivateServer()
	return game.PrivateServerId ~= ""
end

while true do
	local breakParts = findBreakParts()
	for _, breakPartInfo1 in pairs(breakParts) do
		breakPartInfo1.part.Touched:Connect(function(hit)
			for _, breakPartInfo2 in pairs(breakParts) do
				if breakPartInfo1.part ~= breakPartInfo2.part and 
					haveDifferentParents(breakPartInfo1, breakPartInfo2) and 
					hit == breakPartInfo2.part then
					breakJoints(breakPartInfo1.part)
					breakJoints(breakPartInfo2.part)

					if not isPrivateServer() then
						disableBarriersForCars(breakPartInfo1.parent, breakPartInfo2.parent)
					end
				end
			end
		end)
	end
	wait(1)
end

I’m noticing that there’s no checks at the breakPartInfo1.part.Touched for a players character, could try adding a return if hit is a part of one (you could use hit:FindFirstAncestorOfClass("Model") with the combination of Players:GetPlayerFromCharacter(model) to check for a character)

you do not need to set a .touched event every second, rather, just set a touched event at the start, and add an if statement to check if the part should have its joints broken (whether it has different parents and isn’t a breakpart)

also, you do not need two for loops for this case, using a loop inside of a loop can often be inefficient unless you know what you are doing

for your case with the cars stuttering, it is because the network ownership of the cars are being transferred to the player when close, so the client simulates them instead of the server. this is automatic, and can be negated by setting the network ownership manually to the server or client (idk why u wouldn’t use the client)

hope this helps

1 Like

I noticed your while loop is re-adding items that are already added to the table.

while true do
	local breakParts = findBreakParts() — this add multiple copies of the same item since there is not a check to see if the item is already in the table
	for _, breakPartInfo1 in pairs(breakParts) do
		breakPartInfo1.part.Touched:Connect(function(hit)
			for _, breakPartInfo2 in pairs(breakParts) do
				if breakPartInfo1.part ~= breakPartInfo2.part and 
					haveDifferentParents(breakPartInfo1, breakPartInfo2) and 
					hit == breakPartInfo2.part then
					breakJoints(breakPartInfo1.part)
					breakJoints(breakPartInfo2.part)

					if not isPrivateServer() then
						disableBarriersForCars(breakPartInfo1.parent, breakPartInfo2.parent)
					end
				end
			end
		end)
	end
	wait(1)
end

I think this will cause a touched function to fire for each part each time the loop runs. If that’s the case, then multiple touch events will fire exponentially for every part named break.

You might consider checking if the part is already in the table. You might also want to clear any destroyed items from the table.

Something like:

local brakeParts = {}

local function findBrakeParts()
	
	-- remove destroyed items from table
	
	for _, part in pairs(breakParts) do
		if not workspace:FindFirstChild(part) then
			table.remove(breakParts, part)
		end
	end
	
	-- add new items to table
	
	for _, part in pairs(workspace:GetDescendants()) do
		if part.Name == "Break" and part:IsA("BasePart") then
			if not table.find(breakParts, part) then
				table.find(breakParts, part)
			end
		end
	end
	
	return breakParts
end

I made some adjustments to the code to reduce lag by checking for breakable parts every 12 seconds instead of every 1 second. This should help reduce the stuttering when the player is near, and the lag spikes are less frequent. However, there’s still a slight delay when the breakable parts update every 12 seconds. The next step would be to fine-tune it further, but this version should be better in terms of performance.

local workspace = game:GetService("Workspace")

local breakParts = {}

local function findBreakParts()
    for i = #breakParts, 1, -1 do
        local part = breakParts[i]
        if not workspace:FindFirstChild(part) then
            table.remove(breakParts, i)
        end
    end

    for _, part in pairs(workspace:GetDescendants()) do
        if part.Name == "Break" and part:IsA("BasePart") then
            if not table.find(breakParts, part) then
                table.insert(breakParts, part)
            end
        end
    end

    return breakParts
end

local function breakJoints(part)
    part:BreakJoints()
end

local function disableCanCollideInModel(model)
    for _, child in pairs(model:GetDescendants()) do
        if child:IsA("BasePart") then
            child.CanCollide = false
        end
    end
end

local barrierMap = {
    ["F1"] = {"Secondinv", "Secondinv 2"},
    ["F2"] = {"Barrierinv", "Barrierinv 2"},
    ["F3"] = {"Wallinv", "Wallinv 2"},
    ["F4"] = {"Fourthinv", "Fourthinv 2"}
}

local function disableBarriersForCars(parent1, parent2)
    local parents = {parent1, parent2}
    for _, parent in pairs(parents) do
        if barrierMap[parent.Name] then
            for _, barrierName in pairs(barrierMap[parent.Name]) do
                local barrierModel = workspace:FindFirstChild(barrierName)
                if barrierModel then
                    disableCanCollideInModel(barrierModel)
                end
            end
        end
    end
end

local function isPrivateServer()
    return game.PrivateServerId ~= ""
end

while true do
    local breakParts = findBreakParts()
    for _, breakPartInfo1 in pairs(breakParts) do
        breakPartInfo1.part.Touched:Connect(function(hit)
            for _, breakPartInfo2 in pairs(breakParts) do
                if breakPartInfo1.part ~= breakPartInfo2.part and 
                    breakPartInfo1.parent ~= breakPartInfo2.parent and 
                    hit == breakPartInfo2.part then

                    breakJoints(breakPartInfo1.part)
                    breakJoints(breakPartInfo2.part)

                    if not isPrivateServer() then
                        disableBarriersForCars(breakPartInfo1.parent, breakPartInfo2.parent)
                    end
                end
            end
        end)
    end
    wait(12)
end

Getting all Descendants of the workspace will cause lag unless the workspace in mostly empty.

Can you put the items in a folder and just check the folder?

Also, I noticed a problem. In the findBreakParts function, you are looking for FirstChild when removing items from the table and Descendants when adding items to the table.

My bad. I didn’t notice earlier.

You will need to look for parts they same way each time.

So you need to use GetDescendants when removing items.

If you decide to use a folder, you might want to use ChildAdded to trigger a refresh of the table.

It seems the stuttering issue when the player is in the cars’ path persists with this script. I’m starting to run out of ideas on how to resolve it.

local workspace = game:GetService("Workspace")

local breakParts = {}

local function findBreakParts()
	-- Clear the table before re-adding parts
	local updatedParts = {}

	for _, model in pairs(workspace:GetChildren()) do
		if model:IsA("Model") then
			for _, part in pairs(model:GetDescendants()) do
				if part:IsA("BasePart") and part.Name == "Break" then
					-- Only add the part if it's not already in the table
					table.insert(updatedParts, {part = part, parent = model})
				end
			end
		end
	end

	-- Update the global breakParts table with the new parts
	breakParts = updatedParts
end

local function breakJoints(part)
	part:BreakJoints()
end

local function disableCanCollideInModel(model)
	for _, child in pairs(model:GetDescendants()) do
		if child:IsA("BasePart") then
			child.CanCollide = false
		end
	end
end

local barrierMap = {
	["F1"] = {"Secondinv", "Secondinv 2"},
	["F2"] = {"Barrierinv", "Barrierinv 2"},
	["F3"] = {"Wallinv", "Wallinv 2"},
	["F4"] = {"Fourthinv", "Fourthinv 2"}
}

local function disableBarriersForCars(parent1, parent2)
	local parents = {parent1, parent2}
	for _, parent in pairs(parents) do
		if barrierMap[parent.Name] then
			for _, barrierName in pairs(barrierMap[parent.Name]) do
				local barrierModel = workspace:FindFirstChild(barrierName)
				if barrierModel then
					disableCanCollideInModel(barrierModel)
				end
			end
		end
	end
end

local function isPrivateServer()
	return game.PrivateServerId ~= ""
end

local function handleCollisions(breakPartInfo1, hit)
	for _, breakPartInfo2 in pairs(breakParts) do
		if breakPartInfo1.part ~= breakPartInfo2.part and
			breakPartInfo1.parent ~= breakPartInfo2.parent and 
			hit == breakPartInfo2.part then

			breakJoints(breakPartInfo1.part)
			breakJoints(breakPartInfo2.part)

			if not isPrivateServer() then
				disableBarriersForCars(breakPartInfo1.parent, breakPartInfo2.parent)
			end
		end
	end
end

local function setupCollisionHandlers()
	for _, breakPartInfo1 in pairs(breakParts) do
		breakPartInfo1.part.Touched:Connect(function(hit)
			local hitModel = hit:FindFirstAncestorOfClass("Model")
			local hitPlayer = Players:GetPlayerFromCharacter(hitModel)

			if hitPlayer then return end

			handleCollisions(breakPartInfo1, hit)
		end)
	end
end

local function initialize()
	-- Initially find all breakable parts and set up collision handlers
	findBreakParts()
	setupCollisionHandlers()
end

while true do
	initialize()  -- Initialize on start or whenever needed
	wait(12)  -- Wait before refreshing
end

The issue I noticed was about duplicated parts with a Touched event already assigned to them.

I think your latest code re-introduces that problem. If a part already has a touched event assigned to it then you don’t want to assign another one.

As for the stuttering, I am not sure.

You said the players and cars are in different collision groups, but the touch event seems to be firing when a player is near a car.

Do you need the players to be able to “touch” things? If not, maybe turn off CanTouch for the players and see if that helps.

forgive me if i made a mistake, but i dont see the lines where u set the network ownership to server from any these scripts

Thanks for pointing that out! I’ve added the network ownership part, and it seems to be working now. Here’s the updated code:

local workspace = game:GetService("Workspace")

local breakParts = {}

local function setNetworkOwnershipToServer(model)
	for _, part in pairs(model:GetDescendants()) do
		if part:IsA("BasePart") then
			part:SetNetworkOwner(nil)
		end
	end
end

local function findBreakParts()
	local updatedParts = {}

	for _, model in pairs(workspace:GetChildren()) do
		if model:IsA("Model") then
			setNetworkOwnershipToServer(model) -- Ensure server controls the physics
			for _, part in pairs(model:GetDescendants()) do
				if part:IsA("BasePart") and part.Name == "Break" then
					table.insert(updatedParts, {part = part, parent = model})
				end
			end
		end
	end

	breakParts = updatedParts
end

local function breakJoints(part)
	part:BreakJoints()
end

local function disableCanCollideInModel(model)
	for _, child in pairs(model:GetDescendants()) do
		if child:IsA("BasePart") then
			child.CanCollide = false
		end
	end
end

local barrierMap = {
	["F1"] = {"Secondinv", "Secondinv 2"},
	["F2"] = {"Barrierinv", "Barrierinv 2"},
	["F3"] = {"Wallinv", "Wallinv 2"},
	["F4"] = {"Fourthinv", "Fourthinv 2"}
}

local function disableBarriersForCars(parent1, parent2)
	local parents = {parent1, parent2}
	for _, parent in pairs(parents) do
		if barrierMap[parent.Name] then
			for _, barrierName in pairs(barrierMap[parent.Name]) do
				local barrierModel = workspace:FindFirstChild(barrierName)
				if barrierModel then
					disableCanCollideInModel(barrierModel)
				end
			end
		end
	end
end

local function isPrivateServer()
	return game.PrivateServerId ~= ""
end

local function handleCollisions(breakPartInfo1, hit)
	for _, breakPartInfo2 in pairs(breakParts) do
		if breakPartInfo1.part ~= breakPartInfo2.part and
			breakPartInfo1.parent ~= breakPartInfo2.parent and
			hit == breakPartInfo2.part then

			breakJoints(breakPartInfo1.part)
			breakJoints(breakPartInfo2.part)

			if not isPrivateServer() then
				disableBarriersForCars(breakPartInfo1.parent, breakPartInfo2.parent)
			end
		end
	end
end

local function setupCollisionHandlers()
	for _, breakPartInfo in pairs(breakParts) do
		if not breakPartInfo.part.TouchedConnected then
			breakPartInfo.part.Touched:Connect(function(hit)
				local hitModel = hit:FindFirstAncestorOfClass("Model")
				local hitPlayer = Players:GetPlayerFromCharacter(hitModel)

				if hitPlayer then return end

				handleCollisions(breakPartInfo, hit)
			end)
			breakPartInfo.part.TouchedConnected = true
		end
	end
end

local function initialize()
	findBreakParts()
	setupCollisionHandlers()
end

while true do
	initialize() -- Refresh breakable parts and reapply network ownership
	wait(12)
end

the cars doesnt stutter anymore?, if so thats good

To my knowledge, the workspace instance has a bool property named TouchesUseCollisionGroups.
Check if the mentioned property is set to true and let me know if it resolved your issue :wink:

1 Like

Where is this part

local phs=game:GetService("PhysicsService")
phs:RegisterCollisionGroup("Car")
phs:RegisterCollisionGroup("Player")
phs:CollisionGroupSetCollidable("Car","Car",false)
phs:CollisionGroupSetCollidable("Car","Player",false)
phs:CollisionGroupSetCollidable("Player","Player",false)

--and this part. Got this linked into too much to post here.

function setCollisionGroup(model, groupName)
	for _, part in ipairs(model:GetDescendants()) do
		if part:IsA("BasePart") then
			part.CollisionGroup = groupName
		end
	end
end

Mine is pretty long as it has to account for respawns and the car spawn events. But nothing touches not even the players. I will PM you the whole script.