Ragdoll Solution -- R15/Customizable ragdolls for use with Layered Clothing

I’ve converted this into a module for easy use and removed the use of the client events as they weren’t needed.

All you need to do is stick this in ReplicatedStorage and require it in the server.
You still need the CharacterSetupScript labeled HumanoidStarterSet in StarterCharacterScripts

Hope this helps :slight_smile:

Heres the source:

local Ragdoll = {}

local RunService = game:GetService("RunService")

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

if RunService:IsServer() then
	local ServerStorage = game:GetService("ServerStorage")
	local RagdollCounterSys = ServerStorage.RagdollCounterSys
	local character = script.Parent
	local lDModeOn
	local diedCounted = false
	
	function Ragdoll:ragdollMe(character)	
		local humanoid = character:WaitForChild("Humanoid")
		humanoid.AutoRotate = false	
		character.HumanoidRootPart.CollisionGroup = "Body"
		character.HumanoidRootPart.CanCollide = false

		if RagdollCounterSys.ragdollsExisted.Value > RagdollCounterSys.ragdollsLdMax.Value then
			lDModeOn = true
		else
			lDModeOn = false
		end

		if RagdollCounterSys.charactersDied.Value >= RagdollCounterSys.charactersDiedMax.Value then
			task.wait(0.1 * RagdollCounterSys.charactersDied.Value) --Activate ragdoll delay, tenth x per character
		end

		if RagdollCounterSys.ragdollsLdEnable.Value == true and lDModeOn == true then --If LD mode is on
			if character.UpperTorso then
				for i,motor6d in pairs(character.UpperTorso:GetChildren()) do
					if motor6d:IsA("Motor6D") then	
						motor6d.Parent.CollisionGroup = "Body"
						motor6d.Parent.CanTouch = false
						motor6d:Destroy()
					end	
				end		
			end

			if character.LeftFoot then
				for i,motor6d in pairs(character.LeftFoot:GetChildren()) do
					if motor6d:IsA("Motor6D") then	
						motor6d.Parent.CollisionGroup = "Body"
						motor6d.Parent.CanTouch = true
						motor6d:Destroy()
					end	
				end		
			end

			if character.RightFoot then
				for i,motor6d in pairs(character.RightFoot:GetChildren()) do
					if motor6d:IsA("Motor6D") then	
						motor6d.Parent.CollisionGroup = "Body"
						motor6d.Parent.CanTouch = true
						motor6d:Destroy()
					end	
				end		
			end

			if character.Head then
				for i,motor6d in pairs(character.Head:GetChildren()) do
					if motor6d:IsA("Motor6D") then	--Getting motor6D joints as joints. Their parents are the parts. 
						motor6d.Parent.CollisionGroup = "Body"
						motor6d.Parent.CanTouch = true
						motor6d:Destroy()
					end	
				end		
			end
		else --If LD mode is off
			RagdollCounterSys.ragdollsExisted.Value += 1
			for i,limbs in pairs(character:GetChildren()) do
				for i,motor6d in pairs(limbs:GetChildren()) do
					if motor6d:IsA("Motor6D") then	--Getting motor6D joints as joints. Their parents are the parts. 
						motor6d.Parent.CollisionGroup = "Body"
						--print("Rookstun ", motor6d.Parent.CollisionGroup, " ", motor6d.Parent.CollisionGroupId, " ", motor6d.Parent)
						motor6d.Parent.CanCollide = true
						motor6d:Destroy()
					end	
				end
			end
		end
	end

	function Ragdoll:activateVelocity(player)
		player.character.HumanoidRootPart.AngularVelocity.Enabled = true
		player.character.HumanoidRootPart.LinearVelocity.Enabled = true
		if player.character:FindFirstChild("LeftUpperLeg") and player.character:FindFirstChild("RightUpperLeg") then
			player.character.LeftUpperLeg.AngularVelocity.Enabled = true
			player.character.RightUpperLeg.AngularVelocity.Enabled = true
		end
	end

	function Ragdoll:deactivateVelocity(player)
		player.character.HumanoidRootPart.AngularVelocity.Enabled = false
		player.character.HumanoidRootPart.LinearVelocity.Enabled = false
		if player.character:FindFirstChild("LeftUpperLeg") and player.character:FindFirstChild("RightUpperLeg") then
			player.character.LeftUpperLeg.AngularVelocity.Enabled = false
			player.character.RightUpperLeg.AngularVelocity.Enabled = false
		end
	end

	function Ragdoll:ragdollFreeze(character, state)
		if character and RagdollCounterSys.ragdollFreezeEnable.Value and state == Enum.HumanoidStateType.Dead then
			local upperTorso = character:WaitForChild("UpperTorso")
			repeat 
				local lastPos = upperTorso.Position
				wait(RagdollCounterSys.ragdollFreezeTime.Value) --Time left before it checks body.
				local newPos = upperTorso.Position
				local distanceDiff = (lastPos - newPos).magnitude 		--print("DistanceDiff", distanceDiff)
			until distanceDiff < 2 		--Distance a body must be close from its original check to be anchored

			for i,v in pairs(character:GetChildren()) do
				if v:IsA("MeshPart") then
					v.Anchored = true
				end
			end
			character.HumanoidRootPart.Anchored = true
		end
		if RagdollCounterSys.ragdollsExisted.Value ~= 0  then
			RagdollCounterSys.ragdollsExisted.Value -= 1
		end
	end

	function Ragdoll:resyncClothes(player)
		for i,v in pairs(player.character:GetChildren()) do --Hack. Refreshes and resyncs layered clothing.
			if v:IsA("Accessory") then
				for i2,v2 in pairs(v.Handle:GetChildren()) do 
					if v2:IsA("WrapLayer") then
						local refWT = Instance.new("WrapTarget")
						refWT.Parent = v2.Parent
						refWT:Destroy()
						refWT.Parent = nil
					end
				end
			end
		end
	end

	function Ragdoll:stopAnims(humanoid)
		local AnimTrack = humanoid:GetPlayingAnimationTracks()
		for i, track in pairs (AnimTrack) do
			track:Stop()
		end
	end
	
	function Ragdoll.DiedCountDecay()
		if RagdollCounterSys.charactersDied.Value > 0 and diedCounted == false then
			diedCounted = true
			repeat
				task.wait(RagdollCounterSys.characterDiedDecay.Value)
				RagdollCounterSys.charactersDied.Value -= 1
			until RagdollCounterSys.charactersDied.Value <= 0 
			diedCounted = false
		end
	end
	
	function Ragdoll.RagdollExistedReset()
		if RagdollCounterSys.ragdollsExisted.Value ~= 0 and diedCounted == false then
			diedCounted = true
			repeat
				RagdollCounterSys.ragdollsExisted.Value -= 1
				task.wait(2)
			until RagdollCounterSys.ragdollsExisted.Value <= 0 
			task.wait(120)
			diedCounted = false
		end
	end
	
	RagdollCounterSys.charactersDied:GetPropertyChangedSignal("Value"):Connect(Ragdoll.DiedCountDecay)
	
	if RagdollCounterSys.ragdollsLdEnable.Value == true  then
		RagdollCounterSys.ragdollsExisted:GetPropertyChangedSignal("Value"):Connect(Ragdoll.RagdollExistedReset)
	end
	
	Players.PlayerAdded:Connect(function(player)
		player.CharacterAdded:Connect(function(character)
			local humanoid = character:WaitForChild("Humanoid")

			humanoid.Died:Connect(function()
				Ragdoll:activateVelocity(player)
				humanoid:UnequipTools()
				for i, tool in pairs(player.Backpack:GetChildren()) do
					tool:Destroy()
				end	
				
				RagdollCounterSys.charactersDied.Value += 1
				Ragdoll:stopAnims(humanoid)
				Ragdoll:activateVelocity(player)
				Ragdoll:ragdollMe(player.character)
				Ragdoll:resyncClothes(player)
				task.wait()
				Ragdoll:deactivateVelocity(player)
				Ragdoll:ragdollFreeze(player.character, humanoid:GetState())
			end)
		end)
	end)
end

return Ragdoll
6 Likes

I was lookign for a replacement ragdoll that didn’t need events. And all-in-one but i noticed some bugs.

Going in water makes the character spam the swim emote and they just fall down in the water with no ability to swim or control the character.

3 Likes

Are you able to ragdoll a player without killing them?

4 Likes

Is it possible to toggle the ragdoll on and off. Your example only shows how to turn on ragdoll on death.

3 Likes

I have exactly the same problem in water, any solution?

1 Like

Hi I got an idea about the freeze ragdoll, you could maybe just use AssemblyLinearVelocity (but be aware, I removed the variables)

local function ragdollFreeze(character, state)
	local upperTorso = character:WaitForChild("UpperTorso")
	
	task.wait(1) --Time left before it checks body.

	while upperTorso.AssemblyLinearVelocity.Magnitude > 5 do -- it should stop moving before anchoring
		task.wait(0.5)
	end

	task.wait(0.5)

	for i,v in pairs(character:GetChildren()) do
		if v:IsA("MeshPart") then
			v.Anchored = true
		end
	end
	character.HumanoidRootPart.Anchored = true
end

theres weird jumping physics if you are falling, turning midair, in shiftlock, and holding spacebar at the same time. for some reason you jump 2 times higher

I went through some of the code and removed this one line that changes the Custom Physical Properties for the character parts. I’ll leave it for the players to decide if they want to modify it, because it seems to be causing undetectable issues. Plus it made warnings in the output.

Right out the box though, the character ragdolls may feel a lot more “airy” now, but maybe this could fix that problem. Otherwise, maybe it’s a typical humanoid:() issue.

i was wrong, your system is fine. it seems to be a roblox bug. if the workspace gravity is lowered, as well as jumppower, youll jump higher while turning. im working on reporting this

1 Like

Hello, I am trying to port this to R6. I’ve replaced all of the R15 limb names with the R6 ones, but the ragdoll doesn’t work properly, because R6 and R15 rigs are vastly different from each other in ways that I do not really understand. It seems too stiff, and it also freezes in place once it hits the ground. Would anybody be able to help me on this? I’ve commented out the LinearVelocity and AngularVelocity parts of the code due to them making the player fly:


Here are my slightly edited versions of the scripts:

HumanoidStarterSet (Replace this with the one in StarterCharacterScripts)
local SOCKET_SETTINGS_R6 = {
	head = {MaxFrictionTorque = 150, UpperAngle = 15, TwistLowerAngle = -15, TwistUpperAngle = 15},
	torso = {MaxFrictionTorque = 50, UpperAngle = 20, TwistLowerAngle = 0, TwistUpperAngle = 30},
	arms = {MaxFrictionTorque = 150, UpperAngle = 90, TwistLowerAngle = -45, TwistUpperAngle = 45},
	legs = {MaxFrictionTorque = 150, UpperAngle = 40, TwistLowerAngle = -5, TwistUpperAngle = 20},
}

local function limbManager(limbName)
	if limbName == "Head" then
		return "head"
	elseif limbName == "Torso" then
		return "torso"
	elseif limbName == "Right Arm" or limbName == "Left Arm" then
		return "arms"
	elseif limbName == "Right Leg" or limbName == "Left Leg" then
		return "legs"
	else
		return nil
	end
end

local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")

local a0 = Instance.new("Attachment")
local s0 = Instance.new("BallSocketConstraint")
local nc = Instance.new("NoCollisionConstraint")

local function noCollideR6()
	local nc1 = nc:Clone()
	local nc2 = nc:Clone()
	local nc3 = nc:Clone()
	local nc4 = nc:Clone()

	nc1.Part0 = character["Right Leg"]
	nc1.Part1 = character.Torso
	nc1.Parent = character.Torso

	nc2.Part0 = character["Left Leg"]
	nc2.Part1 = character.Torso
	nc2.Parent = character.Torso

	nc3.Part0 = character["Right Arm"]
	nc3.Part1 = character.Torso
	nc3.Parent = character.Torso

	nc4.Part0 = character["Left Arm"]
	nc4.Part1 = character.Torso
	nc4.Parent = character.Torso
end

humanoid.AutomaticScalingEnabled = false
humanoid.BreakJointsOnDeath = false
character.HumanoidRootPart.CanCollide = false
character.HumanoidRootPart.CollisionGroup = "Players"

--local lv = Instance.new("LinearVelocity")
--lv.Attachment0 = character.HumanoidRootPart.RootAttachment
--lv.VectorVelocity = Vector3.new(0, 50, -8000)
--lv.MaxForce = 8000
--lv.RelativeTo = "Attachment0"
--lv.Parent = character.HumanoidRootPart
--lv.Enabled = false

--local av = Instance.new("AngularVelocity")
--av.Attachment0 = character["Left Leg"].LeftFootAttachment
--av.AngularVelocity = Vector3.new(0, 10, 0)
--av.MaxTorque = 1000
--av.RelativeTo = "Attachment0"
--av.ReactionTorqueEnabled = false
--av.Parent = character["Left Leg"]
--av.Enabled = false

--local av2 = Instance.new("AngularVelocity")
--av2.Attachment0 = character["Right Leg"].RightFootAttachment
--av2.AngularVelocity = Vector3.new(0, 10, 0)
--av2.MaxTorque = 1000
--av2.RelativeTo = "Attachment0"
--av2.ReactionTorqueEnabled = false
--av2.Parent = character["Right Leg"]
--av2.Enabled = false

--local av3 = Instance.new("AngularVelocity")
--av3.Attachment0 = character.HumanoidRootPart.RootAttachment
--av3.AngularVelocity = Vector3.new(0,math.random(-10,10),math.random(-10,10))
--av3.MaxTorque = 2000
--av3.RelativeTo = "Attachment0"
--av3.ReactionTorqueEnabled = false
--av3.Parent = character.HumanoidRootPart
--av3.Enabled = false

for i, limb in pairs(character:GetChildren()) do
	if limb:IsA("Accessory") then
		limb.Handle.CanCollide = false
		limb.Handle.CollisionGroup = "Players"
		limb.Handle.CanTouch = false
		limb.Handle.Massless = true
	end

	for i, motor6d in pairs(limb:GetChildren()) do
		if motor6d:IsA("Motor6D") then
			local nc0 = nc:Clone()
			local socket = s0:Clone()
			local a1 = a0:Clone()
			local a2 = a0:Clone()

			a1.Parent = motor6d.Part0
			a2.Parent = motor6d.Part1
			a1.CFrame = motor6d.C0
			a2.CFrame = motor6d.C1

			motor6d.Parent.CollisionGroup = "Players"
			nc0.Part0 = motor6d.Part0
			nc0.Part1 = motor6d.Part1
			nc0.Parent = motor6d.Parent

			socket.Attachment0 = a1
			socket.Attachment1 = a2
			socket.LimitsEnabled = true
			socket.TwistLimitsEnabled = true
			socket.Parent = motor6d.Parent

			local limbMgr = limbManager(motor6d.Parent.Name)
			if limbMgr ~= nil then
				local limbDir = SOCKET_SETTINGS_R6[limbMgr]
				for key, value in pairs(limbDir) do
					if socket[key] then
						socket[key] = value
					end
				end
			end
		end
	end
end

noCollideR6()

script:Destroy()
RookstunPlayerRagdoll (Replace this with the one in ServerScriptService)
-- See my work @Rookstun for more stuff! You can support me by buying my UGC on the catalog. Thanks!

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local clientDiedEvent = ReplicatedStorage:WaitForChild("onClientCharDied")
local ServerStorage = game:GetService("ServerStorage")
local RagdollCounterSys = ServerStorage.RagdollCounterSys
local character = script.Parent
local lDModeOn

local function ragdollMe(character)	
	local humanoid = character:WaitForChild("Humanoid")
	humanoid.AutoRotate = false	
	character.HumanoidRootPart.CollisionGroup = "Body"
	character.HumanoidRootPart.CanCollide = false

	if RagdollCounterSys.ragdollsExisted.Value > RagdollCounterSys.ragdollsLdMax.Value then
		lDModeOn = true
	else
		lDModeOn = false
	end

	if RagdollCounterSys.charactersDied.Value >= RagdollCounterSys.charactersDiedMax.Value then
		task.wait(0.1 * RagdollCounterSys.charactersDied.Value) -- Activate ragdoll delay, tenth x per character
	end

	if RagdollCounterSys.ragdollsLdEnable.Value == true and lDModeOn == true then -- If LD mode is on
		for _, partName in pairs({"Torso", "Head", "Left Arm", "Right Arm", "Left Leg", "Right Leg"}) do
			local part = character:FindFirstChild(partName)
			if part then
				for _, motor6d in pairs(part:GetChildren()) do
					if motor6d:IsA("Motor6D") then
						motor6d.Parent.CollisionGroup = "Body"
						motor6d.Parent.CanTouch = true
						motor6d:Destroy()
					end
				end
			end
		end
	else -- If LD mode is off
		RagdollCounterSys.ragdollsExisted.Value += 1
		for _, limb in pairs(character:GetChildren()) do
			if limb:IsA("BasePart") then
				for _, motor6d in pairs(limb:GetChildren()) do
					if motor6d:IsA("Motor6D") then
						motor6d.Parent.CollisionGroup = "Body"
						motor6d.Parent.CanCollide = true
						motor6d:Destroy()
					end
				end
			end
		end
	end
end

local function activateVelocity(player)
	--player.character.HumanoidRootPart.AngularVelocity.Enabled = true
	--player.character.HumanoidRootPart.LinearVelocity.Enabled = true
end

local function deactivateVelocity(player)
	--player.character.HumanoidRootPart.AngularVelocity.Enabled = false
	--player.character.HumanoidRootPart.LinearVelocity.Enabled = false
end

local function ragdollFreeze(character, state)
	if character and RagdollCounterSys.ragdollFreezeEnable.Value and state == Enum.HumanoidStateType.Dead then
		local torso = character:FindFirstChild("Torso")
		repeat 
			local lastPos = torso.Position
			wait(RagdollCounterSys.ragdollFreezeTime.Value) -- Time left before it checks body
			local newPos = torso.Position
			local distanceDiff = (lastPos - newPos).magnitude -- Calculate distance difference
		until distanceDiff < 2 -- Distance a body must be close to its original check to be anchored

		for _, v in pairs(character:GetChildren()) do
			if v:IsA("BasePart") then
				v.Anchored = true
			end
		end
		character.HumanoidRootPart.Anchored = true
	end
	if RagdollCounterSys.ragdollsExisted.Value ~= 0 then
		RagdollCounterSys.ragdollsExisted.Value -= 1
	end
end

local function resyncClothes(player)
	for _, v in pairs(player.character:GetChildren()) do -- Hack to refresh and resync layered clothing
		if v:IsA("Accessory") then
			for _, v2 in pairs(v.Handle:GetChildren()) do
				if v2:IsA("WrapLayer") then
					local refWT = Instance.new("WrapTarget")
					refWT.Parent = v2.Parent
					refWT:Destroy()
					refWT.Parent = nil
				end
			end
		end
	end
end

local function stopAnims(humanoid)
	local animTracks = humanoid:GetPlayingAnimationTracks()
	for _, track in pairs(animTracks) do
		track:Stop()
	end
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:WaitForChild("Humanoid")

		clientDiedEvent.OnServerEvent:Connect(activateVelocity)

		humanoid.Died:Once(function()
			humanoid:UnequipTools() -- Allow for ragdoll and any tool to sync serverside
			for _, tool in pairs(player.Backpack:GetChildren()) do -- Destroy tools in backpack
				tool:Destroy()
			end	
			RagdollCounterSys.charactersDied.Value += 1
			stopAnims(humanoid)
			activateVelocity(player)
			ragdollMe(player.character)
			resyncClothes(player)
			task.wait() -- Without this, physics may not activate on platform stand
			deactivateVelocity(player)
			ragdollFreeze(player.character, humanoid:GetState())
		end)
	end)
end)


Thanks for any/all help! Amazing module, sorry to bother you with something you (probably) intentionally didn’t include.

The sockets might not be correctly forming. See if the limbManager() parameter is getting the correct names from the Motor6Ds in the HumanoidStarterSetR6. They might be using a different naming schema under R6.

		local limbMgr = limbManager(motor6d.Parent.Name)
1 Like

Thanks for the fast reply!

And yeah, that was it! The reason why is because each motor6d is stored in the torso on r6, but on r15 the motor6ds are stored in the corresponding limb, so I needed to edit that code a little bit. It ended up looking pretty good, but I think I’ll need to edit the socket settings a bit because the limbs move a bit funny. Also, I was able to add the linear and angular velocities back, which is good.


1 Like

Are you able to share your reimplementation?