Ragdoll inconsistent between clients



If i move player2, only the humanoidrootpart movement is seen on player1, the limbs just float in space most of the time, but sometimes they glide towards the humanoid

--client side
CollectionService:GetInstanceAddedSignal("Ragdoll"):Connect(function(instance : Model)
	if not instance then return end
	if not instance:FindFirstChild("Humanoid") then return end
	local hrp = instance:FindFirstChild("HumanoidRootPart")
	warn("ragdolling")
	instance.Humanoid.PlatformStand = true
	instance.Humanoid.AutoRotate = false
	
	for _, part in pairs(instance:GetDescendants()) do
		if part:IsA("BasePart") then
			if part.Name == "HumanoidRootPart" then warn("HRP") continue end
			--part.CanCollide = true
			for _, child in pairs(part:GetChildren()) do
				if child:IsA("Motor6D") and not table.find(exclusionList, child.Name) then
					warn(child.Name)
					-- Create BallSocketConstraint to replace the Motor6D joints
					local ballSocket : BallSocketConstraint = Instance.new("BallSocketConstraint")
					local attachment0 = Instance.new("Attachment")
					local attachment1 = Instance.new("Attachment")

					-- Position attachments at the joint location
					attachment0.Position = child.C0.Position
					attachment1.Position = child.C1.Position

					-- Set up the constraint
					attachment0.Parent = child.Part0
					attachment1.Parent = child.Part1
					ballSocket.Attachment0 = attachment0
					ballSocket.Attachment1 = attachment1
					ballSocket.Parent = child.Part0

					-- Configure the constraint for realistic ragdoll movement
					ballSocket.LimitsEnabled = true
					ballSocket.UpperAngle = 25
					ballSocket.TwistLimitsEnabled = true
					ballSocket.TwistUpperAngle = 30
					ballSocket.TwistLowerAngle = -30

					-- Disable the original Motor6D
					child.Enabled = false
				end
			end
		end
	end
end)
-- server side
local function EnableCollisions(character : Model, enabled:boolean)
	for _, part in character:GetChildren() do
		if part:IsA("BasePart") and part.Name ~= "HumanoidRootPart" then
			if not part:FindFirstChild("CollidePart") then warn("couldnt find") continue end
			part.CanCollide = not enabled -- collision on the body part
			part.CollidePart.CanCollide = enabled -- for the collide part
			warn(part.Name .. " " .. tostring(part.CanCollide))
			warn(part.CollidePart.Name .. " " .. tostring(part.CollidePart.CanCollide))
		end
	end
end

local function BuildCollision(character : Model)
	for _, part in character:GetChildren() do
		if part:IsA("BasePart") and not table.find(collisionPartsExclusionList, part.Name) then
			part.Transparency = 0.5
			local collisionPart = Instance.new("Part")
			collisionPart.CanCollide = false
			collisionPart.Massless = true
			collisionPart.Transparency = 1
			collisionPart.Size = Vector3.new(1,1,1)
			collisionPart.Parent = part
			collisionPart.Transparency = 0.5
			collisionPart.Color = Color3.new(1, 0.0941176, 0.0941176)
			collisionPart.Name = "CollidePart"
			local weld = Instance.new("Weld")
			weld.Parent = collisionPart
			weld.Part0 = part
			weld.Part1 = collisionPart
			if part.Name == "Head" then continue end
			weld.C1 = CFrame.new(Vector3.new(0, 0.5, 0))
		end
	end
end

Players.PlayerAdded:Connect(function(player : Player)
	--[]
	player.CharacterAdded:Connect(function(character : Model)
		local humanoid : Humanoid = character:WaitForChild("Humanoid")
		if not humanoid then return end 
		
		BuildCollision(character)
		
		humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, false)
		humanoid.RequiresNeck = false
		humanoid.BreakJointsOnDeath = false
		
		humanoid.HealthChanged:Connect(function(health)
			if health > 0 then return end
			CollectionService:AddTag(character, "Ragdoll")
			EnableCollisions(character, true)
			--humanoid.Parent:SetNetworkOwner(nil)
		end)
	end)
	
	
	player:LoadCharacter()
end)

This just seems to be a problem with client > server replication.

I’m, not exactly sure on the specifics of how characters are replicated, however I found that setting the Torso network owner to the player made (mostly) client-side ragdolls work in my game.

My game however, clones the player’s character when they die and makes custom joints on the server before then parenting and setting the network owner to the player.

I’m still learning ragdolls, and don’t know much about living ragdolls, but I hope this helps.

1 Like

hey i have a similar bug where the server sees both player’s ragdolls fine and player 2 sees player 1s ragdoll but player1 sees the player2s ragdoll broken for whatever reason. let me know if you find out how to fix it

RagdollModule(serverside):

--// Services
local PhysicsService = game:GetService("PhysicsService")
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local CoreManager = require(ServerScriptService.Services.CoreManager)

--// Modules
local RagdollModule = {}
RagdollModule.PlayersCollisionGroup = "Players" -->> idk, if u have another name for the players collision group just change it
RagdollModule.CanCollide = false -->> set true if you want the parts to collide with each other

local RagdollModule = {}
RagdollModule.PlayersCollisionGroup = "Players" 
RagdollModule.CanCollide = false 

-- 1. Ensure the "Uncollidable" group exists
if not PhysicsService:IsCollisionGroupRegistered("Uncollidable") then
	PhysicsService:RegisterCollisionGroup("Uncollidable")
end

-- 2. Ensure the "Players" group exists
if not PhysicsService:IsCollisionGroupRegistered(RagdollModule.PlayersCollisionGroup) then
	PhysicsService:RegisterCollisionGroup(RagdollModule.PlayersCollisionGroup)
end

-- 3. Now that BOTH are guaranteed to exist, set the relationships
PhysicsService:CollisionGroupSetCollidable("Uncollidable", RagdollModule.PlayersCollisionGroup, false)
PhysicsService:CollisionGroupSetCollidable("Uncollidable", "Uncollidable", RagdollModule.CanCollide)

--// Main Function's
function RagdollModule:Ragdoll(Character: Model)
	if not Character:GetAttribute("Ragdoll") then
		local IsNpc = Players:GetPlayerFromCharacter(Character) == nil
		local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")

		if not Humanoid then return end

		for _, Animation in Humanoid:GetPlayingAnimationTracks() do
			Animation:Stop()
		end
		Character:SetAttribute("Ragdoll", true)
		CoreManager.SetCore(Character, "CombatStates.Ragdoll", true)
		CoreManager.SetCore(Character, "IFrames.Grounded", true)

		Humanoid.BreakJointsOnDeath = false
		Humanoid.AutoRotate = false
		Humanoid.RequiresNeck = false
		
		--Humanoid.WalkSpeed = 0
		--Humanoid.JumpPower = 0

		Humanoid:SetStateEnabled(Enum.HumanoidStateType.Ragdoll, true)
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false)
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)

		Character:FindFirstChild("HumanoidRootPart").CanCollide = false
		
		-- Inside RagdollModule:Ragdoll
		local rootPart = Character:FindFirstChild("HumanoidRootPart")
		local player = Players:GetPlayerFromCharacter(Character)
		if player and rootPart then
			rootPart:SetNetworkOwner(player)
		end
		if IsNpc then
			Humanoid:ChangeState(Enum.HumanoidStateType.Ragdoll)
		end

		self:ReplaceJoints(Character)
	end
end

function RagdollModule:unRagdoll(Character : Model)
	if Character:GetAttribute("Ragdoll") then
		local IsNpc = Players:GetPlayerFromCharacter(Character) == nil
		local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")

		if not Humanoid then return end
		if Humanoid.Health <= 0.1 then return end
		
		local rootPart = Character:FindFirstChild("HumanoidRootPart")		
		local Torso = Character:FindFirstChild("Torso")
		if rootPart and Torso then
			rootPart.CFrame = Torso.CFrame
		end

		Character:SetAttribute("Ragdoll", false)
		CoreManager.SetCore(Character, "CombatStates.Ragdoll", false)
		CoreManager.SetCore(Character, "IFrames.Grounded", false)

		Humanoid.AutoRotate = true
		Humanoid.RequiresNeck = false

		Humanoid:SetStateEnabled(Enum.HumanoidStateType.Ragdoll, false)
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, true)
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)

		Character:FindFirstChild("HumanoidRootPart").CanCollide = true

		if IsNpc then
			Humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
		end

		rootPart.CanCollide = true
		self:ResetJoints(Character)
	end
end

--// Attachments CFrame's
local AttachmentCFrames = {
	["Neck"] = {CFrame.new(0, 1, 0, 0, -1, 0, 1, 0, -0, 0, 0, 1), CFrame.new(0, -0.5, 0, 0, -1, 0, 1, 0, -0, 0, 0, 1)},
	["Left Shoulder"] = {CFrame.new(-1.3, 0.75, 0, -1, 0, 0, 0, -1, 0, 0, 0, 1), CFrame.new(0.2, 0.75, 0, -1, 0, 0, 0, -1, 0, 0, 0, 1)},
	["Right Shoulder"] = {CFrame.new(1.3, 0.75, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1), CFrame.new(-0.2, 0.75, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1)},
	["Left Hip"] = {CFrame.new(-0.5, -1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1), CFrame.new(0, 1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1)},
	["Right Hip"] = {CFrame.new(0.5, -1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1), CFrame.new(0, 1, 0, 0, 1, -0, -1, 0, 0, 0, 0, 1)},
}

local RagdollInstanceNames = {
	["RagdollAttachment"] = true,
	["RagdollConstraint"] = true,
	["ColliderPart"] = true,
}


function RagdollModule:CreateColliderPart(Part: BasePart)
	if not Part then return end
	local RagdollColliderPart = Instance.new("Part")
	RagdollColliderPart.Name = "ColliderPart"
	RagdollColliderPart.Size = Part.Size / 1.7
	RagdollColliderPart.Massless = true			
	RagdollColliderPart.CFrame = Part.CFrame
	RagdollColliderPart.Transparency = 1

	RagdollColliderPart.CollisionGroup = "Uncollidable"

	local WeldConstraint = Instance.new("WeldConstraint")
	WeldConstraint.Part0 = RagdollColliderPart
	WeldConstraint.Part1 = Part

	WeldConstraint.Parent = RagdollColliderPart
	RagdollColliderPart.Parent = Part
end


function RagdollModule:ReplaceJoints(Character: Model)
	local Humanoid : Humanoid = Character:FindFirstChildWhichIsA("Humanoid")

	for _, Motor: Motor6D in Character:GetDescendants() do
		if Motor:IsA("Motor6D") then
			if not AttachmentCFrames[Motor.Name] then continue end
			Motor.Enabled = false
			local Attachment0, Attachment1 = Instance.new("Attachment"), Instance.new("Attachment")
			Attachment0.CFrame = AttachmentCFrames[Motor.Name][1]
			Attachment1.CFrame = AttachmentCFrames[Motor.Name][2]

			Attachment0.Name = "RagdollAttachment"
			Attachment1.Name = "RagdollAttachment"

			self:CreateColliderPart(Motor.Part1)

			local BallSocketConstraint = Instance.new("BallSocketConstraint")
			BallSocketConstraint.Attachment0 = Attachment0
			BallSocketConstraint.Attachment1 = Attachment1
			BallSocketConstraint.Name = "RagdollConstraint"

			BallSocketConstraint.Radius = 0.15
			BallSocketConstraint.LimitsEnabled = true
			BallSocketConstraint.TwistLimitsEnabled = true
			BallSocketConstraint.MaxFrictionTorque = 0
			BallSocketConstraint.Restitution = 0
			BallSocketConstraint.UpperAngle = 45
			BallSocketConstraint.TwistLowerAngle = -70
			BallSocketConstraint.TwistUpperAngle = 70
			BallSocketConstraint.Restitution = 0

			Attachment0.Parent = Motor.Part0
			Attachment1.Parent = Motor.Part1
			BallSocketConstraint.Parent = Motor.Parent
		end
	end
end

function RagdollModule:ResetJoints(Character: Model)
	local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")

	if Humanoid then	
		if Humanoid.Health < 0.1 then return end
		for _, Instance in Character:GetDescendants() do
			if RagdollInstanceNames[Instance.Name] then
				Instance:Destroy()
			end

			if Instance:IsA("Motor6D") then
				Instance.Enabled = true
			end
		end
	end
end

return RagdollModule



and the client side script

--// Services
local Players = game:GetService("Players")

--// Constants
local Player : Player = Players.LocalPlayer
local Character : Model = Player.Character or Player.CharacterAdded:Wait()

--// Instances
local Torso: BasePart = Character:WaitForChild("Torso")
local Humanoid: Humanoid = Character:FindFirstChildWhichIsA("Humanoid") or Character:WaitForChild("Humanoid")

--// Things
local PlayerScripts = Player:WaitForChild("PlayerScripts")
local Controllers = PlayerScripts:WaitForChild("Controllers")
local CombatFolder = Controllers:WaitForChild("Combat")
local ShiftLockModule = CombatFolder:WaitForChild("SmoothShiftLock")
local EditShiftLockConfig = ShiftLockModule:WaitForChild("EditConfig")

--// Local Function's
local function PushCharacter()
	--Torso:ApplyImpulse(Torso.CFrame.LookVector * -100)
end

--// Function's
Character:GetAttributeChangedSignal("Ragdoll"):Connect(function()
	local IsRagdoll = Character:GetAttribute("Ragdoll")

	if IsRagdoll then
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false)
		Humanoid:ChangeState(Enum.HumanoidStateType.Physics)
		Humanoid.PlatformStand = true
		Humanoid.AutoRotate = false
		EditShiftLockConfig:Fire("AutoRotate", false)
		PushCharacter()
	else
		-- Ensure PlatformStand is off
		Humanoid.PlatformStand = false
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, true)
		Humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
		

		-- Wait for the physics to sync back to the root
		task.wait(0.1) 

		Humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
		Humanoid.AutoRotate = true
		EditShiftLockConfig:Fire("AutoRotate", true)
	end
end)