Push mechanic working oddly on map

I’m making a game where you have to push people off a map. I got started on my round system, but the push mechanic becomes super buggy on the map.
Here is a video demonstrating the bug:

Here is my push script:

local Debris = game:GetService("Debris")
local tool = script.Parent

local RANGE = 3
local WIDTH, HEIGHT = 6, 4
local DELTA_V = 120
local RAGDOLL_TIME = 2

local remote = tool:WaitForChild("PushEvent")

local function ragdoll(character)
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid:ChangeState(Enum.HumanoidStateType.Physics)
	end

	local motors = {}
	for _, joint in pairs(character:GetDescendants()) do
		if joint:IsA("Motor6D") then
			table.insert(motors, {Motor=joint, Parent=joint.Parent, C0=joint.C0, C1=joint.C1})
			local socket = Instance.new("BallSocketConstraint")
			local a1 = Instance.new("Attachment")
			local a2 = Instance.new("Attachment")
			a1.Parent = joint.Part0
			a2.Parent = joint.Part1
			socket.Parent = joint.Parent
			socket.Attachment0 = a1
			socket.Attachment1 = a2
			a1.CFrame = joint.C0
			a2.CFrame = joint.C1
			socket.LimitsEnabled = true
			socket.TwistLimitsEnabled = true
			joint:Destroy()
		end
	end
	return motors
end

local function standUp(character, motors)
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid.PlatformStand = false
	end

	for _, data in ipairs(motors) do
		if data.Parent and character.Parent then
			local newMotor = Instance.new("Motor6D")
			newMotor.Part0 = data.Motor.Part0
			newMotor.Part1 = data.Motor.Part1
			newMotor.C0 = data.C0
			newMotor.C1 = data.C1
			newMotor.Parent = data.Parent
		end
	end
end

local function push(character)
	local hrp = character:FindFirstChild("HumanoidRootPart")
	if not hrp then return end

	local size = Vector3.new(WIDTH, HEIGHT, RANGE)
	local cf = hrp.CFrame * CFrame.new(0, 0, -(RANGE/2 + 2))
	local partsInBox = workspace:GetPartBoundsInBox(cf, size)
	local alreadyHit = {}

	local hitboxVisual = Instance.new("Part")
	hitboxVisual.Size = size
	hitboxVisual.CFrame = cf
	hitboxVisual.Anchored = true
	hitboxVisual.CanCollide = false
	hitboxVisual.CanQuery = false
	hitboxVisual.CanTouch = false
	hitboxVisual.Transparency = 0.5
	hitboxVisual.Color = Color3.fromRGB(255, 0, 0)
	hitboxVisual.Parent = workspace
	Debris:AddItem(hitboxVisual, 0.15)

	for _, part in ipairs(partsInBox) do
		local otherChar = part:FindFirstAncestorOfClass("Model")
		local hum = otherChar and otherChar:FindFirstChildOfClass("Humanoid")
		local otherHRP = otherChar and otherChar:FindFirstChild("HumanoidRootPart")
		if not (hum and otherHRP) then continue end
		if otherChar == character then continue end
		if alreadyHit[otherChar] then continue end
		alreadyHit[otherChar] = true

		local targetChar = otherChar
		local targetHRP = otherHRP
		local myHRP = hrp

		task.spawn(function()
			if not (targetChar and targetHRP and myHRP) then return end

			local motors = ragdoll(targetChar)
			task.wait(0.05)

			local originalOwner = targetHRP:GetNetworkOwner()
			local dir = targetHRP.Position - myHRP.Position
			dir = Vector3.new(dir.X, 5, dir.Z)

			if dir.Magnitude > 0 then
				dir = dir.Unit
				local impulse = dir * targetHRP.AssemblyMass * DELTA_V
				targetHRP:SetNetworkOwner(nil)
				task.wait()
				if targetHRP.ApplyImpulse then
					targetHRP:ApplyImpulse(impulse)
				else
					targetHRP.AssemblyLinearVelocity += impulse / targetHRP.AssemblyMass
				end
			end

			task.wait(RAGDOLL_TIME)
			if targetChar.Parent then
				standUp(targetChar, motors)
				if originalOwner then
					targetHRP:SetNetworkOwner(originalOwner)
				end
			end
		end)
	end
end

remote.OnServerEvent:Connect(function(player)
	local char = player.Character
	if char then
		push(char)
	end
end)

Here is my round system:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local allMaps = {"Snowy Isles", "Lava Lane", "Grassy Hills", "Desert Dunes"}
local mapImages = {["Snowy Isles"]=75025886098830, ["Lava Lane"]=140550269282963, ["Grassy Hills"]=84750033257545, ["Desert Dunes"]=101262479079545}

local padsModel = workspace:WaitForChild("VotingPads")
local pads = {padsModel:WaitForChild("VotingPad1"), padsModel:WaitForChild("VotingPad2"), padsModel:WaitForChild("VotingPad3")}
local labelsModel = workspace:WaitForChild("Labels")
local labels = {labelsModel:WaitForChild("Label1"), labelsModel:WaitForChild("Label2"), labelsModel:WaitForChild("Label3")}
local imageholdersModel = workspace:WaitForChild("ImageHolders")
local imageHolders = {imageholdersModel:WaitForChild("ImageHolder1"), imageholdersModel:WaitForChild("ImageHolder2"), imageholdersModel:WaitForChild("ImageHolder3")}

local spawns = workspace:WaitForChild("Spawns")
local lobbySpawn = workspace:WaitForChild("LobbySpawn")

local currentMaps = {}
local votes = {}
local alivePlayers = {}

local function shuffle(tbl)
	local t = {table.unpack(tbl)}
	for i = #t,2,-1 do
		local j = math.random(i)
		t[i],t[j]=t[j],t[i]
	end
	return t
end

local function pickMaps()
	local shuffled = shuffle(allMaps)
	currentMaps = {shuffled[1],shuffled[2],shuffled[3]}
	votes = {}

	for i,label in ipairs(labels) do
		local gui = label:WaitForChild("SurfaceGui")
		if gui and gui:FindFirstChild("TextLabel") then
			gui.TextLabel.Text = currentMaps[i]
		end
	end

	for i,image in ipairs(imageHolders) do
		local gui = image:FindFirstChild("SurfaceGui")
		if gui then
			local frame = gui:FindFirstChild("Frame")
			if frame then
				local img = frame:FindFirstChild("ImageLabel")
				if img then
					img.Image = "rbxassetid://"..mapImages[currentMaps[i]]
				end
			end
		end
	end
end

local function updateVotes()
	local counts = {0,0,0}
	for _,padIndex in pairs(votes) do counts[padIndex]+=1 end
	for i,pad in ipairs(pads) do
		local gui = pad:FindFirstChild("BillboardGui")
		if gui and gui:FindFirstChild("TextLabel") then
			gui.TextLabel.Text = currentMaps[i].." - Votes: "..counts[i]
		end
	end
end

local function onPadTouched(padIndex, hit)
	local plr = Players:GetPlayerFromCharacter(hit.Parent)
	if plr and votes[plr] ~= padIndex then
		votes[plr] = padIndex
		updateVotes()
	end
end

for i,pad in ipairs(pads) do
	pad.Touched:Connect(function(hit) onPadTouched(i,hit) end)
end

Players.PlayerRemoving:Connect(function(plr)
	votes[plr]=nil
	updateVotes()
end)

local function getWinningMap()
	local counts = {0,0,0}
	for _,idx in pairs(votes) do counts[idx]+=1 end
	local maxVotes = math.max(unpack(counts))
	local winners = {}
	for i,v in ipairs(counts) do if v==maxVotes then table.insert(winners,i) end end
	return currentMaps[winners[math.random(1,#winners)]]
end

local function teleportPlayer(plr)
	local spawnList = spawns:GetChildren()
	local randSpawn = spawnList[math.random(1,#spawnList)]
	local char = plr.Character
	if char and char:FindFirstChild("HumanoidRootPart") then
		char.HumanoidRootPart.Position = randSpawn.Position + Vector3.new(0,3,0)
	end
end

local function endRound(winnerMapName)
	local winner
	if #alivePlayers==1 then winner=alivePlayers[1] end
	if winner then
		print("Winner: "..winner.Name)
	end
	for _,plr in ipairs(Players:GetPlayers()) do
		if plr.Character and plr.Character:FindFirstChild("HumanoidRootPart") then
			plr.Character.HumanoidRootPart.Position = lobbySpawn.Position + Vector3.new(0,5,0)
		end
	end
	local map = workspace:FindFirstChild(winnerMapName)
	if map then
		map.Parent = ReplicatedStorage.Maps
	end
	pickMaps()
	task.delay(15,function()
		local winnerMap=getWinningMap()
		print("Winning Map: "..winnerMap)
		startRound(winnerMap)
	end)
end

function startRound(winnerMapName)
	local pickedMap = ReplicatedStorage.Maps:FindFirstChild(winnerMapName)
	if pickedMap then pickedMap.Parent=workspace end
	alivePlayers = {}
	for _,plr in pairs(Players:GetPlayers()) do
		teleportPlayer(plr)
		table.insert(alivePlayers,plr)
		local char = plr.Character
		if char then
			local hrp = char:FindFirstChild("HumanoidRootPart")
			if hrp then
				hrp.AssemblyLinearVelocity = Vector3.new(0, 0, 0)
			end
			local humanoid = char:FindFirstChildOfClass("Humanoid")
			if humanoid then
				humanoid.Died:Connect(function()
					local idx = table.find(alivePlayers,plr)
					if idx then table.remove(alivePlayers,idx) end
					if #alivePlayers<=1 then endRound(winnerMapName) end
				end)
			end
		end
	end
end

pickMaps()
task.delay(15,function()
	local winnerMap=getWinningMap()
	print("Winning Map: "..winnerMap)
	startRound(winnerMap)
end)

I’ve tried moving the maps from replicatedstorage to serverstorage and I’ve tried keeping the maps in workspace and changing cancollide and transparency properties accordingly.

Update: I have also attempted to remove all obstacles on a map. That did not work.

One thing I’ve noticed with ragdolls is that collisions become really glitchy when joints are replaced by motor 6ds. Try applying an impulse, then ragdolling the player and add invisible collision parts welded to each body part of the player to act as collision parts to prevent the ragdoll getting out of control.

Tried my best to add your suggestion:

local Debris = game:GetService("Debris")
local tool = script.Parent

local RANGE = 3
local WIDTH, HEIGHT = 6, 4
local DELTA_V = 120
local RAGDOLL_TIME = 2

local remote = tool:WaitForChild("PushEvent")

-- ragdoll (destroy Motor6Ds and add BallSocketConstraints)
local function ragdoll(character)
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid:ChangeState(Enum.HumanoidStateType.Physics)
	end

	local motors = {}
	for _, joint in pairs(character:GetDescendants()) do
		if joint:IsA("Motor6D") then
			table.insert(motors, {Motor=joint, Parent=joint.Parent, C0=joint.C0, C1=joint.C1})
			local socket = Instance.new("BallSocketConstraint")
			local a1 = Instance.new("Attachment")
			local a2 = Instance.new("Attachment")
			a1.Parent = joint.Part0
			a2.Parent = joint.Part1
			socket.Parent = joint.Parent
			socket.Attachment0 = a1
			socket.Attachment1 = a2
			a1.CFrame = joint.C0
			a2.CFrame = joint.C1
			socket.LimitsEnabled = true
			socket.TwistLimitsEnabled = true
			joint:Destroy()
		end
	end
	return motors
end

-- Restore original joints
local function standUp(character, motors)
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid.PlatformStand = false
	end

	for _, data in ipairs(motors) do
		if data.Parent and character.Parent then
			local newMotor = Instance.new("Motor6D")
			newMotor.Part0 = data.Motor.Part0
			newMotor.Part1 = data.Motor.Part1
			newMotor.C0 = data.C0
			newMotor.C1 = data.C1
			newMotor.Parent = data.Parent
		end
	end
end

-- Add invisible collision boxes to each limb
local function addCollisionParts(character)
	local colliders = {}
	for _, part in ipairs(character:GetChildren()) do
		if part:IsA("BasePart") then
			local collider = Instance.new("Part")
			collider.Size = part.Size
			collider.CFrame = part.CFrame
			collider.Transparency = 1
			collider.CanCollide = true
			collider.Massless = true
			collider.Anchored = false
			collider.Parent = character

			local weld = Instance.new("WeldConstraint")
			weld.Part0 = part
			weld.Part1 = collider
			weld.Parent = part

			table.insert(colliders, collider)
		end
	end
	return colliders
end

local function removeCollisionParts(parts)
	for _, p in ipairs(parts) do
		if p and p.Parent then
			p:Destroy()
		end
	end
end

-- Main push logic
local function push(character)
	local hrp = character:FindFirstChild("HumanoidRootPart")
	if not hrp then return end

	local size = Vector3.new(WIDTH, HEIGHT, RANGE)
	local cf = hrp.CFrame * CFrame.new(0, 0, -(RANGE/2 + 2))
	local partsInBox = workspace:GetPartBoundsInBox(cf, size)
	local alreadyHit = {}

	-- DEBUG HITBOX VISUAL
	local hitboxVisual = Instance.new("Part")
	hitboxVisual.Size = size
	hitboxVisual.CFrame = cf
	hitboxVisual.Anchored = true
	hitboxVisual.CanCollide = false
	hitboxVisual.CanQuery = false
	hitboxVisual.CanTouch = false
	hitboxVisual.Transparency = 0.5
	hitboxVisual.Color = Color3.fromRGB(255, 0, 0)
	hitboxVisual.Parent = workspace
	Debris:AddItem(hitboxVisual, 0.15)

	for _, part in ipairs(partsInBox) do
		local otherChar = part:FindFirstAncestorOfClass("Model")
		local hum = otherChar and otherChar:FindFirstChildOfClass("Humanoid")
		local otherHRP = otherChar and otherChar:FindFirstChild("HumanoidRootPart")
		if not (hum and otherHRP) then continue end
		if otherChar == character then continue end
		if alreadyHit[otherChar] then continue end
		alreadyHit[otherChar] = true

		local targetChar = otherChar
		local targetHRP = otherHRP
		local myHRP = hrp

		task.spawn(function()
			if not (targetChar and targetHRP and myHRP) then return end

			local originalOwner = targetHRP:GetNetworkOwner()
			local dir = targetHRP.Position - myHRP.Position
			dir = Vector3.new(dir.X, 5, dir.Z)
			if dir.Magnitude > 0 then
				dir = dir.Unit
				local impulse = dir * targetHRP.AssemblyMass * DELTA_V
				targetHRP:SetNetworkOwner(nil)
				if targetHRP.ApplyImpulse then
					targetHRP:ApplyImpulse(impulse)
				else
					targetHRP.AssemblyLinearVelocity += impulse / targetHRP.AssemblyMass
				end
			end

			-- small delay so the push applies while rigid
			task.wait(0.05)

			local motors = ragdoll(targetChar)
			local colliders = addCollisionParts(targetChar)

			-- wait ragdoll duration
			task.wait(RAGDOLL_TIME)

			-- CLEAN UP
			removeCollisionParts(colliders)
			if targetChar.Parent then
				standUp(targetChar, motors)
				if originalOwner then
					targetHRP:SetNetworkOwner(originalOwner)
				end
			end
		end)
	end
end

remote.OnServerEvent:Connect(function(player)
	local char = player.Character
	if char then
		push(char)
	end
end)

It still doesn’t work, kinda just makes the character invisible.

I’d like to emphasize that it works in the lobby but not on the map.

Ok, try removing the suggestion I gave you since it isn’t working.
Could you check if this always happens on the map or only sometimes? Do you have another map you can try it on?
Try hitting a player with the push repeatedly in the lobby and try to recreate this error elsewhere.
Hopefully this will show whether the bug is completely limited to that map or if its a physics bug that happens on certain parts.

It always happens on any map. I’ve tried it on every map (4 of them). It works completely fine in the lobby.

I’ve figured out that after the round, if you try again in the lobby the bug occurs again. It’s possibly something to do with the teleporting.

1 Like

Hi, sorry I wasn’t active.
You teleport the player with char.HumanoidRootPart.Position = randSpawn.Position + Vector3.new(0,3,0)

I think you should try teleporting the player with char.HumanoidRootPart.CFrame = CFrame.new(pos)
From my testing, I think that the character is being offset from its model position due to its position not being set by cframe, causing the push script to incorrectly apply too much knockback.

1 Like

Thank you so much, this worked!

1 Like

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