[Updated] Perfect R6 Ragdoll - Easiest Ragdoll System for R6 Avatars

Intro

Its surprisingly hard to get a good Ragdoll system for your game especially because of how popular Ragdolls are on Roblox.

I decided to release the Ragdoll System I use for my game so no-one else has to suffer like I did.

If you already forgot the title this is for R6 only.

And yes if you were wondering you can Un-Ragdoll with this.


Download or View

Roblox Model
Roblox Model - NPC Version

Test Place (Uncopylocked): Ragdoll - Roblox

View Scripts

RagdollR6: Script (Parent under StarterCharacterScripts)

--> Perfect R6 Ragdoll by CompletedLoop
--> June 6, 2023

--> Make sure this script is located in StarterPlayer.StarterCharacterScripts
local Character: Model = script.Parent
local Torso: BasePart = Character:WaitForChild("Torso")
local Humanoid: Humanoid = Character:FindFirstChildOfClass("Humanoid")

--> Necessary for Ragdolling to function properly
Character.Humanoid.BreakJointsOnDeath = false
Character.Humanoid.RequiresNeck = false

--> Specific CFrame's I made for the best looking Ragdoll
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,
}

--> Used to trigger Ragdoll
local RagdollValue = Instance.new("BoolValue")
RagdollValue.Name = "IsRagdoll"
RagdollValue.Parent = Character

--> Used for anticheats to prevent flying Ragdolls from getting flagged, might be useful for you.
Character:SetAttribute("LastRag")
-------------------------------------------------------------------------------------------------

--> Allows for proper limb collisions
local function createColliderPart(part: BasePart)
	if not part then return end
	local rp = Instance.new("Part")
	rp.Name = "ColliderPart"
	rp.Size = part.Size/1.7
	rp.Massless = true			
	rp.CFrame = part.CFrame
	rp.Transparency = 1
	
	local wc = Instance.new("WeldConstraint")
	wc.Part0 = rp
	wc.Part1 = part
	
	wc.Parent = rp
	rp.Parent = part
end

--> Converts Motor6D's into BallSocketConstraints
function replaceJoints()
	for _, motor: Motor6D in pairs(Character:GetDescendants()) do
		if motor:IsA("Motor6D") then
			if not attachmentCFrames[motor.Name] then return end
			motor.Enabled = false;
			local a0, a1 = Instance.new("Attachment"), Instance.new("Attachment")
			a0.CFrame = attachmentCFrames[motor.Name][1]
			a1.CFrame = attachmentCFrames[motor.Name][2]

			a0.Name = "RagdollAttachment"
			a1.Name = "RagdollAttachment"

			createColliderPart(motor.Part1)

			local b = Instance.new("BallSocketConstraint")
			b.Attachment0 = a0
			b.Attachment1 = a1
			b.Name = "RagdollConstraint"
			
			b.Radius = 0.15
			b.LimitsEnabled = true
			b.TwistLimitsEnabled = false
			b.MaxFrictionTorque = 0
			b.Restitution = 0
			b.UpperAngle = 90
			b.TwistLowerAngle = -45
			b.TwistUpperAngle = 45

			if motor.Name == "Neck" then
				b.TwistLimitsEnabled = true
				b.UpperAngle = 45
				b.TwistLowerAngle = -70
				b.TwistUpperAngle = 70
			end
			
			a0.Parent = motor.Part0
			a1.Parent = motor.Part1
			b.Parent = motor.Parent
		end
	end
	
	Humanoid.AutoRotate = false --> Disabling AutoRotate prevents the Character rotating in first person or Shift-Lock
	Character:SetAttribute("LastRag", tick())
end

--> Destroys all Ragdoll made instances and re-enables the Motor6D's
function resetJoints()
	if Humanoid.Health < 1 then return end
	for _, instance in pairs(Character:GetDescendants()) do
		if ragdollInstanceNames[instance.Name] then
			instance:Destroy()
		end

		if instance:IsA("Motor6D") then
			instance.Enabled = true;
		end
	end
	
	Humanoid.AutoRotate = true
end

function Ragdoll(value: boolean)
	if value then replaceJoints() else resetJoints() end
end

-------------------------------------------------------------------------------------------------
--> Connect the Events
RagdollValue.Changed:Connect(Ragdoll)
Humanoid.Died:Once(function()
	RagdollValue.Value = true
end)

RagdollClient: LocalScript (Parent under RagdollR6)

local Character: Model = script.Parent.Parent
local Torso: BasePart = Character:WaitForChild("Torso")
local Humanoid: Humanoid = Character:WaitForChild("Humanoid")
local RagdollValue: BoolValue = Character:WaitForChild("IsRagdoll")

local function push()
	Torso:ApplyImpulse(Torso.CFrame.LookVector * 100)
end

RagdollValue:GetPropertyChangedSignal("Value"):Connect(function()
	if (Humanoid.Health == 0) then --> Prevents OOF sound from playing twice thanks to @robloxdestroyer1035
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false)
		push()
		return
	end
	
	if RagdollValue.Value then
		Humanoid:ChangeState(Enum.HumanoidStateType.Ragdoll)
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false)
		push()
	elseif not RagdollValue.Value then
		Humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
	end
end)

Humanoid.Died:Connect(push)

RagdollR6NPC: Script (Parent under the Character Model)

--> Perfect R6 Ragdoll (NPC Version) by CompletedLoop
--> June 11, 2023

--> Make sure this script is located under the Character Model
local Character: Model = script.Parent
local Torso: BasePart = Character:WaitForChild("Torso")
local Humanoid: Humanoid = Character:FindFirstChildOfClass("Humanoid")

--> Necessary for Ragdolling to function properly
Character.Humanoid.BreakJointsOnDeath = false
Character.Humanoid.RequiresNeck = false

--> Specific CFrame's I made for the best looking Ragdoll
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,
}

--> Used to trigger Ragdoll
local RagdollValue = Instance.new("BoolValue")
RagdollValue.Name = "IsRagdoll"
RagdollValue.Parent = Character
-------------------------------------------------------------------------------------------------

--> push :)
local function push()
	Torso:ApplyImpulse(Torso.CFrame.LookVector * 100)
end

--> Allows for proper limb collisions
local function createColliderPart(part: BasePart)
	if not part then return end
	local rp = Instance.new("Part")
	rp.Name = "ColliderPart"
	rp.Size = part.Size/1.7
	rp.Massless = true			
	rp.CFrame = part.CFrame
	rp.Transparency = 1

	local wc = Instance.new("WeldConstraint")
	wc.Part0 = rp
	wc.Part1 = part

	wc.Parent = rp
	rp.Parent = part
end

--> Converts Motor6D's into BallSocketConstraints
function replaceJoints()
	for _, motor: Motor6D in pairs(Character:GetDescendants()) do
		if motor:IsA("Motor6D") then
			if not attachmentCFrames[motor.Name] then return end
			motor.Enabled = false;
			local a0, a1 = Instance.new("Attachment"), Instance.new("Attachment")
			a0.CFrame = attachmentCFrames[motor.Name][1]
			a1.CFrame = attachmentCFrames[motor.Name][2]

			a0.Name = "RagdollAttachment"
			a1.Name = "RagdollAttachment"

			createColliderPart(motor.Part1)

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

			b.Radius = 0.15
			b.LimitsEnabled = true
			b.TwistLimitsEnabled = false
			b.MaxFrictionTorque = 0
			b.Restitution = 0
			b.UpperAngle = 90
			b.TwistLowerAngle = -45
			b.TwistUpperAngle = 45

			if motor.Name == "Neck" then
				b.TwistLimitsEnabled = true
				b.UpperAngle = 45
				b.TwistLowerAngle = -70
				b.TwistUpperAngle = 70
			end

			a0.Parent = motor.Part0
			a1.Parent = motor.Part1
			b.Parent = motor.Parent
		end
	end
	
	
end

--> Destroys all Ragdoll made instances and re-enables the Motor6D's
function resetJoints()
	if Humanoid.Health < 1 then return end
	for _, instance in pairs(Character:GetDescendants()) do
		if ragdollInstanceNames[instance.Name] then
			instance:Destroy()
		end

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

function Ragdoll(value: boolean)
	if value then
		Humanoid:ChangeState(Enum.HumanoidStateType.Ragdoll)
		Humanoid:SetStateEnabled(Enum.HumanoidStateType.GettingUp, false)
		replaceJoints()
		push()
	else 
		resetJoints()
		Humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
	end
end

-------------------------------------------------------------------------------------------------
--> Connect the Events
RagdollValue.Changed:Connect(Ragdoll)
Humanoid.Died:Once(function()
	RagdollValue.Value = true
	push()
end)

Showcase

Test Place on Mobile:


How to Use

As soon as you import the Script your character will automatically Ragdoll when you die so if that’s what you’re looking for then your good.

Whenever the Character spawns a BoolValue is created under the model Called β€œIsRagdoll”.

image

Setting this BoolValue to true will Ragdoll the player. And setting it to false will Un-Ragdoll the player.


Dead simple hope you find this useful.

Update 6/11/2023


  • Created a NPC version of the script.
  • Fixed a couple bugs and cleaned up the code.
  • Added a β€œLastRag” Attribute for AnitCheats

Update 6/10/2024


  • Set the RequriesNeck property to true by default to fix not respawning when in falling in the void.
174 Likes

Pretty cool! Just a tip with Instance.new(), it’s considered bad practice to set the parent when creating the instance like you do here:

--> Used to trigger Ragdoll
local RagdollValue = Instance.new("BoolValue", Character)
RagdollValue.Name = "IsRagdoll"

You should do this instead:

--> Used to trigger Ragdoll
local RagdollValue = Instance.new("BoolValue")
RagdollValue.Name = "IsRagdoll"
RagdollValue.Parent = Character
8 Likes

what if the code is very messy??? that announcement was 7 years old and it might be outdated

5 Likes

If the code is messy, just use the Find All / Replace All.

But actually, I think the bug is fixed, though there is a slight difference between the two ways of setting the parent.

print(tick()) -- 0286806

local newPart = Instance.new("Part")
newPart.Parent = workspace

print(tick()) -- 0288227

Difference: 1421

print(tick()) -- 3059478

local newPart = Instance.new("Part", workspace)

print(tick()) -- 3061128

Difference: 1650

There’s only a very slight difference between the two methods but setting the parent in a separate line is faster by a tiny little bit.

4 Likes

The issue you’re talking about is not about how long it takes with the parent parameter. It’s that more information and requests are transmitted to clients when the parent is set first. It’s the best practice to set the parent property after everything else.


The ragdoll system looks nice, good job! Is this client-sided or server-sided (or both)?

7 Likes

so in other words, you’ve just provided a more detailed explanation on it being about how long it takes with the parent parameter…

2 Likes

Sure it takes longer, but that’s not the big issue. It’s that the clients may see the instance before any of the properties are set because of their ping. It takes more requests to tell the client the part because it’s parent is visible to the client (not nil), and all property changes are replicated instantly instead of when the part is actually finished being created.

2 Likes

You are right but not in the example you showed. Creating a single Instance one time wont matter at all.

But since this script does create a whole bunch of attachments and constraints every time the Ragdoll is triggered, I will update this to have all Instances parented after having their properties set.

5 Likes

Yes, your detailed explanation is what is summed up into β€œit takes longer if you use the parent parameter”

2 Likes

I saw the bug you commented on where you wrote about the death sound playing twice. My guess is because the humanoid state is set to ragdoll when the player dies, which somehow overrides the human state of Dead…but if you don’t set it to ragdoll then it wont have the proper ragdoll effect upon death; it’ll look wonky. So idk how it would be fixed.

Fixed the death sound playing twice by setting Humanoid.PlatformStand = true when Humanoid.Health == 0 and then return from it to prevent the code which changes the Humanoid state from running

1 Like

alright ill try this and update the script, thanks.

1 Like

yo make this a module so I can abuse dummies with your avatar

3 Likes

Just what I needed Ill definitely use this for my project.

4 Likes

This should be a module in my opinion. Would be best if we had

module:Ragdoll(character : Model, bool : boolean)
    if bool then
        ragdoll(character)
    else
        unragdoll(character)
    end
end
1 Like

This is a very nice ragdoll system, Thanks for putting it out here as i’ve been struggling to find one.

1 Like

Something doesnt seem to be right here

External Media
1 Like

setting the value to true just makes the character fall down. (it has animation)
https://gyazo.com/73257609290b82b55ba2a19bdde2baea

2 Likes

Because you’re enabling it on the client… Not the server.

1 Like

You can just add it yourself. β€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Žβ€β€β€Ž β€Ž

1 Like