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

careful using this module guys, but its the only module which ragdoll i like
image

Update!

1. Addressing the flinging away problem

Ding dong! The wicked velocity constraints is dead.

What does this mean?

In the HumanoidStarterSet scripts, I have removed the instancing of all linear and angle velocity constraints. This existed for the purpose of ā€œwaking upā€ physics incase the character was stuck in a static pose that was way too still.

However, there was a better way to do this with ApplyAngularImpulse() which is a part method. So whenever a character ragdolls, they will have an angle force be applied to them (as before), but through the part itself, not a constraint. This means that there will be less instances to deal with.

As of right now it is set to a modest 50 * the forward facing vector of the UpperTorso. You can change ApplyAngularImpulse() to ApplyImpulse() if you want a vector force instead of an angular one.

This also means deactivateVelocity() is no longer needed, as these methods only fire once.


HumanoidRootPart.Anchored = true and PlatformStand = true will prevent respawning, as recognizing death requires the character to wake up. New measures have been added to prevent this.

If an anchored HumanoidRootPart is too upright (such as 0, -90, 0), it will tilt forwards via CFrame to allow gravity to wake up physics incase the prior fails. It will also unanchor, then promptly anchor them again, if it’s found to be anchored in the first place.

PlatformStand will now be made false on death.


You can find these change in the ragdoll scripts themselves, for function activateVelocity()

2. More no-collide constraints and some socket setting value changes

I’m not particularly robophobic so I asked ChatGPT to add 20 total no-collide constraints I initially was too un-arsed to figure out. I don’t expect this to hit performance for the ragdolls since it’s for spawning anyway. This means now that there will be less self collision on a ragdoll’s self, leading to more natural physics.

Do note, your own socket settings may need some adjusting if they are too unclamped.

As of right now, I have loosened up the constrains on the LowerTorso, so if you froze a humanoidRootPart in the air, the ragdoll will now go upside down.

3. Some extras

clientDiedEvent.OnServerEvent:Connect(activateVelocity) replaces activateVelocity() line on the player ragdoll script. Initially, they both existed and double fired.

Commented out some active prints.

1.1.6
  • Removed all instancing of linear and angular velocity constraints

  • Replaced their use with ApplyAngularImpulse() on the UpperTorso

  • If anchored HumanoidRootPart, it will unanchor then reanchor for a moment on death and will tilt a little forwards to help activate physics. If PlatformStand true, PlatformStand will be set to false on death. Having both be true on death will prevent your character from respawning.

  • More no-collide constraints instances to a ragdoll’s self

  • clientDiedEvent.OnServerEvent:Connect(activateVelocity)* replaces activateVelocity() line on the player ragdoll script.

  • Commented out some active prints.

2 Likes

Hey not sure if it’s intended but whenever the character gets ragdoll it feels like there’s a lag spike and the game freezes

Not sure if that’s what’s actually happening but that’s how it feels

SO I have made a lightweight ragdoll system, the way to make ragdolls work with layered clothing is to not disable the waist motor. Is that how yours is for use with layered clothing? I don’t see why I would change it.

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

local RigTypes = require(script.RigTypes)
local ragdolls={}
local RAGDOLLED_TAG = "__Ragdoll_Active"
local dynamicfeet={RightAnkle = {
	
},
LeftAnkle = {
	

},}


local MAX_FRICTION_TORQUE = 10

local jointSettings = {
	Neck = {
		ragdollConstraintSettings = {
			TwistLowerAngle = 80,
			TwistUpperAngle = 60,
			MaxFrictionTorque = 3 * MAX_FRICTION_TORQUE
		},
	},
	Waist = {
		ragdollConstraintSettings = {
			UpperAngle = 22.5,
			TwistLowerAngle = -90,
			TwistUpperAngle = 11.25,
			MaxFrictionTorque = 3 * MAX_FRICTION_TORQUE
		},
	},
	LeftShoulder = {
		ragdollConstraintSettings = {
			UpperAngle = 180,
			TwistLowerAngle = 0,-- -180,
			TwistUpperAngle = 0-- 90
			,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	RightShoulder = {
		ragdollConstraintSettings = {
			UpperAngle = 180,
			TwistLowerAngle = 0,-- -180,
			TwistUpperAngle = 0-- 90
			,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	RightElbow = {
		ragdollConstraintSettings = {
			UpperAngle = 11.25,
			TwistLowerAngle = 0,
			TwistUpperAngle = 170
			,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	LeftElbow = {
		ragdollConstraintSettings = {
			UpperAngle = 11.25,
			TwistLowerAngle = 0,
			TwistUpperAngle = 170,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	LeftWrist = {
		ragdollConstraintSettings = {
			UpperAngle = 11.25,
			TwistLowerAngle = -80,
			TwistUpperAngle = 80,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	RightWrist = {
		ragdollConstraintSettings = {
			UpperAngle = 11.25,
			TwistLowerAngle = -80,
			TwistUpperAngle = 80,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	RightHip = {
		ragdollConstraintSettings = {
			UpperAngle = 22.5,
			TwistLowerAngle = -11.25,
			TwistUpperAngle = 135,
			MaxFrictionTorque = 3 * MAX_FRICTION_TORQUE
		},
	},
	LeftHip = {
		ragdollConstraintSettings = {
			UpperAngle = 22.5,
			TwistLowerAngle = -11.25,
			TwistUpperAngle = 135,
			MaxFrictionTorque = 3 * MAX_FRICTION_TORQUE

		},
	},
	LeftKnee = {
		ragdollConstraintSettings = {
			UpperAngle = 0,
			TwistLowerAngle = -170,
			TwistUpperAngle = 0
			,MaxFrictionTorque =  MAX_FRICTION_TORQUE
		},
	},
	RightKnee = {
		ragdollConstraintSettings = {
			UpperAngle = 0,
			TwistLowerAngle = -170,
			TwistUpperAngle = 0
		},
	},
	RightAnkle = {
		ragdollConstraintSettings = {
			UpperAngle = 22.5,
			TwistLowerAngle = -45,
			TwistUpperAngle = 11.25
		},
	},
	LeftAnkle = {
		ragdollConstraintSettings = {
			UpperAngle = 22.5,
			TwistLowerAngle = -45,
			TwistUpperAngle = 11.25
		},
	},
}

local R15Bodyparts={
	["Head"] = "Neck",
	["LeftFoot"] = "LeftAnkle",
	["LeftHand"] = "LeftWrist",
	["LeftLowerArm"] = "LeftElbow",
	["LeftLowerLeg"] = "LeftKnee",
	["LeftUpperArm"] = "LeftShoulder",
	["LeftUpperLeg"] = "LeftHip",
	["LowerTorso"] = "Root",
	["RightFoot"] = "RightAnkle",
	["RightHand"] = "RightWrist",
	["RightLowerArm"] = "RightElbow",
	["RightLowerLeg"] = "RightKnee",
	["RightUpperArm"] = "RightShoulder",
	["RightUpperLeg"] = "RightHip",
	["UpperTorso"] = "Waist"
}

local function unragdollnodata(model,humanoid)
	if CollectionService:HasTag(model, RAGDOLLED_TAG) then
		model.PrimaryPart.Massless=false
		if not humanoid then
			humanoid=model:FindFirstChildOfClass("Humanoid")
		end
		model.PrimaryPart.Anchored=true
		humanoid.PlatformStand=false
		local player=game.Players:GetPlayerFromCharacter(model)
		local data={Enable={},Delete={}}
		for i,key in R15Bodyparts do 
			if model:FindFirstChild(i) then
				model[i].CanCollide=false
				model[i][key].Enabled=true
				model[i].Anchored=false
				table.insert(data.Enable,model[i][key])
			else 
				print("Cannot find "..i)
			end
		end
		model.PrimaryPart.CanCollide=true
		local attachments = RigTypes.getAttachments(model, humanoid.RigType)
		for name, objects in pairs(attachments) do
			local parent = model:FindFirstChild(name)
			if parent then
				local key= name.."RagdollBallSocketConstraint"

				local motor=parent:FindFirstChildOfClass("Motor6D")
				if motor and jointSettings[motor.Name] then					
					motor.Enabled=true
				end
				local ballsocket=parent:FindFirstChild(key)
				if ballsocket then
					ballsocket:Destroy()						
				end
			end
		end
		local parts = RigTypes.getNoCollisions(model, humanoid.RigType)
		for i, objects in pairs(parts) do
			local constraint = objects[1]:FindFirstChild(objects[1].Name.."BallSocket"..i)
			if constraint then
				constraint:Destroy()
			end						
		end
		local clones={}
		--for i,v in model:GetChildren() do 
		--	if v:IsA("Accessory") and v:FindFirstChild("Handle") and v.Handle:FindFirstChildOfClass("WrapLayer") then
		--		table.insert(clones,v:Clone())
		--		v:Destroy()
		--	end
		--end
		--task.delay(1,function()
		--for i,v in clones do 
		--	humanoid:AddAccessory(v)
		--end
		model.PrimaryPart.Anchored=false
		humanoid.EvaluateStateMachine=true
		humanoid.PlatformStand=false
		
		humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
		model:RemoveTag(RAGDOLLED_TAG)
	end	
end

function ragdolls.unragdoll(model,humanoid,data)
if data==nil then
	unragdollnodata(model,humanoid)
else 
if CollectionService:HasTag(model, RAGDOLLED_TAG) then
		
		CollectionService:RemoveTag(model, RAGDOLLED_TAG)
		model.PrimaryPart.Massless=false
		model.PrimaryPart.CanCollide=true
		model.PrimaryPart.Anchored=true
		if model:FindFirstChild("HumanoidRootPart") then
			model.HumanoidRootPart.CanCollide=true
		end
		if not humanoid then
			humanoid=model:FindFirstChildOfClass("Humanoid")
		end
		for i,v in data.Delete do 	
			v:Destroy()
		end
		for i,v in data.Enable do 
			v.Enabled=true
		end

		
		humanoid:BuildRigFromAttachments()
	end

	local clones={}
	for i,v in model:GetChildren() do 
		if v:IsA("Accessory") and v:FindFirstChild("Handle") and v.Handle:FindFirstChildOfClass("WrapLayer") then
			table.insert(clones,v:Clone())
			v:Destroy()
		end
	end
	--task.delay(1,function()
	for i,v in clones do 
		humanoid:AddAccessory(v)
	end
	humanoid.EvaluateStateMachine=true
	humanoid.PlatformStand=false

	--humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
	model.PrimaryPart.Anchored=false
--task.wait(.3)

humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
--	humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
	--end)
end
end
function  ragdolls.ragdoll(model, humanoid,duration)
	--assert(humanoid:IsDescendantOf(model))
	if CollectionService:HasTag(model, RAGDOLLED_TAG) then
		return
	end
	if not humanoid then
		humanoid=model:FindFirstChildOfClass("Humanoid")
	end
	if humanoid then
	CollectionService:AddTag(model, RAGDOLLED_TAG)
	model.PrimaryPart.Massless=true
	-- Turn into loose body:
	--if humanoid then
	--humanoid:ChangeState(Enum.HumanoidStateType.Physics)
	humanoid.PlatformStand=true
	
	local player=game.Players:GetPlayerFromCharacter(model)
	local data={Enable={},Delete={}}
	for i,key in R15Bodyparts do 
		if model:FindFirstChild(i) then
			model[i].CanCollide=true
			model[i].Anchored=true
			model[i][key].Enabled=false		
			table.insert(data.Enable,model[i][key])
		else 
			--print("Cannot find "..i)
		end
	end
	
	-- Instantiate BallSocketConstraints:
	local attachments = RigTypes.getAttachments(model, humanoid.RigType)
		local velocities={}
		for name, objects in pairs(attachments) do
		local parent = model:FindFirstChild(name)
		if parent then
			
			local constraint = Instance.new("BallSocketConstraint")
			
			constraint.Name = name.."RagdollBallSocketConstraint"
		
		
			constraint.Attachment0 = objects.attachment0
			constraint.Attachment1 = objects.attachment1
			constraint.LimitsEnabled = true
			constraint.UpperAngle = objects.limits.UpperAngle
			constraint.TwistLimitsEnabled = true
			constraint.TwistLowerAngle = objects.limits.TwistLowerAngle
			constraint.TwistUpperAngle = objects.limits.TwistUpperAngle
			constraint.Parent = parent
			local motor=parent:FindFirstChildOfClass("Motor6D")
			if motor and jointSettings[motor.Name] then
				for t,o in  jointSettings[motor.Name].ragdollConstraintSettings do 
					constraint[t]=o
				end
				local c=Instance.new("RopeConstraint")
			c.Length=.15
			c.Name=name.."RopeConstraint"
			c.Restitution=1
			c.Visible=false
			c.WinchForce=math.huge
			c.WinchSpeed=math.huge
			c.WinchEnabled=true
		--	c.WinchTarget=.25
			c.WinchResponsiveness=math.huge
			c.Attachment0= objects.attachment0
			c.Attachment1= objects.attachment1
			c.Parent=parent		
			c.WinchTarget=math.max(.15,(motor.C0.Position-motor.C1.Position).Magnitude)
			table.insert(data.Delete,c)
			table.insert(data.Enable,motor)
				motor.Enabled=false
				
			constraint.Restitution=0.05
			
			end
			table.insert(velocities,parent)	
			table.insert(data.Delete,constraint)
		end
	end
	model.UpperTorso.Waist.Enabled=true--Layered Clothing Fix 
	-- Instantiate NoCollisionConstraints:
	local parts = RigTypes.getNoCollisions(model, humanoid.RigType)
	for i, objects in pairs(parts) do
		local constraint = objects[1]:FindFirstChild(objects[1].Name.."BallSocket"..i) or Instance.new("NoCollisionConstraint")
		constraint.Name =  objects[1].Name.."BallSocket"..i
		constraint.Part0 = objects[1]
		constraint.Part1 = objects[2]
		constraint.Parent = objects[1]
		table.insert(data.Delete,constraint)
	end
	--model
	
	
	for t,o in jointSettings do 
		if o.ragdollConstraintSettings.MaxFrictionTorque==nil then
			o.ragdollConstraintSettings.MaxFrictionTorque =  MAX_FRICTION_TORQUE
		end
	end
	-- Destroy all regular joints:

	for _, motor in pairs(model:GetDescendants()) do
		if motor:IsA("Motor6D") then		
		table.insert(velocities,motor.Parent)	
			
		end
	end
	for i,key in R15Bodyparts do 
		if model:FindFirstChild(i) then
			model[i].CanCollide=true
			model[i].Anchored=false
			model[i].AssemblyLinearVelocity=Vector3.new(0,0,0)--vec--Vector3.new(0,0,0)
			--model[i].AssemblyAngularVelocity=Vector3.new(0,0,0)
			if player then
			pcall(function() model[i]:SetNetworkOwner(player) end)
			end
		else 
			print("Cannot find "..i)
		end
	end
	local function weldmotor(Root1,Root2)
		local w=Instance.new("Motor6D")
		w.Part0,w.Part1 = Root1,Root2
		w.C0 = Root2.CFrame:toObjectSpace(Root1.CFrame):inverse()
		w.Parent = Root1
		w.Name=Root2.Name.."Joint"
		return w
	end
	table.insert(data.Delete,weldmotor(model.HumanoidRootPart,model:FindFirstChild("UpperTorso") or model:FindFirstChild("Head")))

	if game.Players:GetPlayerFromCharacter(model) then
	task.spawn(function()	
		local vec=Vector3.new(0,0,0)
		local VelocityConstraint=40
		local function constrainsum(v)
			local x,y,z=v.X,v.Y,v.Z
			if x>VelocityConstraint then
				x=VelocityConstraint
			elseif x<-VelocityConstraint then 
				x=-VelocityConstraint
			end
			if y>VelocityConstraint then
				y=VelocityConstraint	
			elseif y<-VelocityConstraint then 
				y=-VelocityConstraint	
			end
			if z>VelocityConstraint then
				z=VelocityConstraint
			elseif z<-VelocityConstraint then 
				z=-VelocityConstraint
			end
			return Vector3.new(x,y,z)
		end
	while CollectionService:HasTag(model, RAGDOLLED_TAG) do --lose velocity
		
		for i,v in velocities do 
			v.AssemblyLinearVelocity=constrainsum(v.AssemblyLinearVelocity)--vec--Vector3.new(0,0,0)
			v.AssemblyAngularVelocity=constrainsum(v.AssemblyAngularVelocity)
			--v.AssemblyLinearVelocity
			--v.AssemblyAngularVelocity*=.95	
		end
		task.wait(.08)
	end
	end)
	end
	humanoid.EvaluateStateMachine=false
	if model:FindFirstChild("HumanoidRootPart") then
	model.HumanoidRootPart.CanCollide=false
	end
	return data
	end
end


return ragdolls

I do have some quips with yours. It seems a bit complicated and invasive to destroy the characters motors when they can be enabled or disabled, in addition there is many lines of repeating code illustrated by this image.

I think it would benefit by iterating through a table to and getting the children of those objects rather than doing all those separate loops that also appears to disable and destroy and motor inside those parts, which could cause bugs.

local R15Bodyparts={
	["Head"] = "Neck",
	["LeftFoot"] = "LeftAnkle",
	["LeftHand"] = "LeftWrist",
	["LeftLowerArm"] = "LeftElbow",
	["LeftLowerLeg"] = "LeftKnee",
	["LeftUpperArm"] = "LeftShoulder",
	["LeftUpperLeg"] = "LeftHip",
	["LowerTorso"] = "Root",
	["RightFoot"] = "RightAnkle",
	["RightHand"] = "RightWrist",
	["RightLowerArm"] = "RightElbow",
	["RightLowerLeg"] = "RightKnee",
	["RightUpperArm"] = "RightShoulder",
	["RightUpperLeg"] = "RightHip",
	["UpperTorso"] = "Waist"
}

1 Like

Update! We’re at 1.2.0 now.


1. Smooth ragdolling again! :confetti_ball:
I like cooking with olive oil. Thus I cooked so hard that I made the ragdolls slippery smooth again…like hot olive oil.

With SetNetworkOwnership() trickery, I was able to fix the freezing effect on clientside views when the ragdoll begins. SetNetworkOwnership() is given to the server at the start of player death, and then given back to the player before the anchoring of ragdolls. Do note, you cannot transfer network ownership on anchored parts nor ones via clientside.

2. Better instance treatments

Taking account into @Magus_ArtStudios’s suggestion, it was a good idea to not delete Motor6D joints indeed. I could’ve disabled it (Why I didn’t know this, I don’t know).

I hope this gives people who make non-dying ragdoll modifications a better time.

3. Simpler function ragdollMe()
Continuing (kinda) with another suggestion by @Magus_ArtStudios, for the ragdoll initiation:

I’ve changed how low definition ragdolls have their parts be selected for eligibility. It should simply be in a table with the rigged part’s name on it, making it very scalable.

For regular ragdoll operations, the new ragdollMe loop accesses a character:GetChildren() instance if they are a BasePart and aren’t a HumanoidRootPart.

Both then take its Motor6D and send it to a new function called partProcessor(), where the parts get modified, and their Motor6Ds disabled (Not destroyed like before).

4. Some extras
Changed pair loops to ipair loops when necessary for performance.
More socket setting changes, since I made it waaay too micro jittery.
HumanoidRootPart no longer has a socket effect (No point in doing so and wastes physics calculation)

1.2.0
  • SetNetworkOwnership() is given to the server at the start of player death, and then given back to the player before the anchoring of ragdolls. This ensures smooth ragdolls on frame.
  • Motor6Ds are now preserved and disabled instead of being destroyed.
  • ragdollMe() now uses a simpler loop and low definition ragdoll parts can now be selected via table
  • Changed pair loops to ipair loops when necessary for performance.
  • More socket setting changes, since I made it waaay too micro jittery.
  • HumanoidRootPart no longer has a socket effect (No point in doing so and wastes physics calculation)

@tibiscus

Hey not sure if it’s intended but whenever the character gets ragdoll it feels like there’s a lag spike and the game freezes

I suspected it was network problems. It was indeed network problems. At some point Roblox must’ve updated the physics handling to where transfer of physics ownership was no longer automatic. The freezing was because of the network transfer of all the physics data, but it was only visual due to it affecting the clientside only.


@Magus_ArtStudios

SO I have made a lightweight ragdoll system, the way to make ragdolls work with layered clothing is to not disable the waist motor. Is that how yours is for use with layered clothing? I don’t see why I would change it.

So the reason why I set it up that way was back when this was released, layered clothing would angle itself in a weird way, and taking it on and off again would repair the visual anomaly. I haven’t checked up on how time has treated the solution though.


2 Likes

Ooh I didn’t realize taking it on and off again would fix the layered clothing. That is much better than sacrificing the waist ball joint. Maybe enabling and disabling the cage mesh would be more performant. I will be modifying mine and ill try toggling the cage mesh first.

please fix, add some check to see if a character exists and if an uppertorso exist in that function


also im not so sure of it but i think this is a memory leak, why not just put it outside the Died event since the player parameter is passed from the remotevent anyways ?
image

Thanks for letting me know. I have published a quick fix.

1.2.1
  • Added a FindFirstChild check on activateVelocity for an UpperTorso.
  • clientDiedEvent is now a connected to a variable and utilizes an anonymous function to activate ragdollMe() then activateVelocity(). It has been placed outside the humanoid.Died() connection.
  • Changed tool removal loop to a simpler backpack:ClearAllChildren()

well you did fix the memory leak, but you also broke the module


you can fix it by verifying the player with this line of code
image
but this way is still inefficient, why connect like 30 events if there are 30 players if you can just do it once for everyone
i worded my reply badly, i meant outside the Died in the global scope, not directly outside
you did it right in the NPC script
image
this is the end result, pass in the character from the client also cause what if its been replaced/destroyed by the time it reached the server
image

Whoops, pardon the oversight. I tested on singleplayer instead of a 2 player local server so that slipped past me. I’ve passed the local player through the connection now.

As for the connection, the reason I set it up that way is because of the structure in which the functions were to be fired. I specifically wanted ragdollMe() and activateVelocity() to run in that order to avoid any async’d race conditions. If you wish to de-nest it and set it globally, that’s fine. It’s open source code.

I also would rather not trust the character details coming in from the client in the connection. If someone does modify the character for a clientside only feature, they know what to do with their code more than me.