Better, less scuffed way of creating ragdolls?

I made a ragdoll module recently and it feels really scuffed because it takes advantage of the r15 player characters naming convention is. I wanna know if there was a more reliable way of copying Motor6Ds into BallSocketConstraints.
Oh, I also want info on whether any other parts of my code could be further optimized.

MODULE

local RagSys = {}

local Players = game:GetService("Players")


local function BuildRagdollJoints(char:Model)
	char:SetAttribute("Ragdoll", false)
	
	print("Building Joints")
	for _,v in pairs(char:GetChildren()) do
		if v:IsA("MeshPart") then
			print(v)
			local CollisionPart = Instance.new("Part")
			CollisionPart.Parent = v
			CollisionPart.CFrame = v.CFrame 
			CollisionPart.Size = v.Size/2
			CollisionPart.Massless = true 				
			CollisionPart.Transparency = 0
			CollisionPart.Name = "COLLISION_PART"
			CollisionPart.CanCollide = false
			local Weld = Instance.new("WeldConstraint",CollisionPart)
			Weld.Part0 = CollisionPart 
			Weld.Part1 = v 
		end
	end
	
	for _,v in pairs(char:GetDescendants()) do		
		if v:IsA("Motor6D") then
			local Joint = v
			if Joint.Name == "Neck" or Joint.Name == "Root" then continue end
			--print("Building Joints for: ")
			--print(Joint)
			local BallJoint = Instance.new("BallSocketConstraint")
			local Part0 = nil
			local Part1 = nil
			for _,v in pairs(Joint.Part0:GetChildren()) do
				local Attachment = v
				if string.match(v.Name, Joint.Name) ~= nil then
					print(v)
					Part0 = v
				end
			end
			for _,v in pairs(Joint.Part1:GetChildren()) do
				local Attachment = v
				if v:IsA("Attachment") and string.match(v.Name, Joint.Name) ~= nil then
					print(v)
					Part1 = v
				end
			end
			BallJoint.Name = "RAGDOLL_CONSTRAINT"
			BallJoint.Parent = Joint.Parent
			BallJoint.Attachment0 = Part0
			BallJoint.Attachment1 = Part1
			BallJoint.LimitsEnabled = true
			BallJoint.TwistLimitsEnabled = true
					
		end
	end
end

local function EnableMotor6Ds(char:Model, enabled:boolean)
	for _,Motor6D in pairs(char:GetDescendants()) do
		if Motor6D:IsA("Motor6D") == false then continue end
		if Motor6D.Name == "Neck" or Motor6D.Name == "Root" then continue end
		Motor6D.Enabled = enabled
	end
end
local function EnableBallConstraints(char:Model, enabled:boolean)
	for _,BallConstraint in pairs(char:GetDescendants()) do
		if BallConstraint:IsA("BallSocketConstraint") == false then continue end
		BallConstraint.Enabled = enabled
	end
end
local function EnableCollisionParts(char:Model, enabled:boolean)
	for _,v in pairs(char:GetDescendants()) do
		if v.Name == "COLLISION_PART" then
			v.CanCollide = enabled
		end
	end	
end


function RagSys.Init()
	print("Initializing")
	Players.PlayerAdded:Connect(function(plr)
		print("Player was added")
		plr.CharacterAdded:Connect(function(char)
			print("Char added")
			local Humanoid: Humanoid = char:FindFirstChild("Humanoid")
			Humanoid.BreakJointsOnDeath = false
			Humanoid.RequiresNeck = false
			BuildRagdollJoints(char)
		end)
	end)
end



function RagSys.RagdollChar(char:Model, duration:number)
	EnableMotor6Ds(char, false)
	EnableBallConstraints(char, true)
	EnableCollisionParts(char, true)
	char:FindFirstChild("HumanoidRootPart").CanCollide = false
	char:FindFirstChildOfClass("Humanoid").PlatformStand = true
	
	if duration ~= nil then
		task.spawn(function()
			task.wait(duration)
			RagSys.UnragdollChar(char)
		end)
	end
end

function RagSys.UnragdollChar(char:Model)
	EnableMotor6Ds(char, true)
	EnableBallConstraints(char, false)
	EnableCollisionParts(char, false)
	char:FindFirstChild("HumanoidRootPart").CanCollide = true
	char:FindFirstChildOfClass("Humanoid").PlatformStand = false
end

return RagSys

SERVER SCRIPT

local RepStorage = game:GetService("ReplicatedStorage")

local Remotes = RepStorage.Remotes

RagSys = require(script.Parent.RagdollSystem)

-- INITALIZATION =========================================
RagSys.Init()

-- REMOTES ===============================================
Remotes.Ragdoll.OnServerEvent:Connect(function(plr)
	local char = plr.Character
	if char:GetAttribute("Ragdolled") == true then
		RagSys.UnragdollChar(char)
	else
		RagSys.RagdollChar(char)
	end
	char:SetAttribute("Ragdolled", not char:GetAttribute("Ragdolled"))	
end)

-- MISCELLANEOUS SETUP ===================================
wait(5)
RagSys.RagdollChar(game:GetService("Players"):WaitForChild("Renoitas").Character, 3)

1 Like

You made this script overcomplicated, you can loop for all Motor6Ds, then create attachments with position relative to both parts , Part1 and Part0 with their position. Then create Ball and socket joint and connect attachments to them, give them some attributes and disable that Motor6D, Simple Logic.

1 Like

Yeah I tried that but the issue with that is that the joints are offset from the middle of the part which is where the attachment would end up, causing the ragdoll joint to be very off. The only I found on locating where the joint is supposed to be is by checking the existing attachments for the Motor6Ds by name(it was either that or comparing positions) and setting the joint to the existing attachment.

Take my script for free:

local CharValidater = require(script.Parent.CharacterValidater)

local RagdollModule = {}

function getRagdollAttachment(part: BasePart, jointName: string)
	local Attachment
	for i, v in pairs(part:GetChildren()) do
		if(v:IsA("Attachment") and v:HasTag("RAGDOLL_"..jointName.."ATTACHMENT")) then
			Attachment = v
		end
	end
	
	if(not (Attachment == Attachment) or Attachment == nil) then
		Attachment = Instance.new("Attachment")
		Attachment:AddTag("RAGDOLL_ATTACHMENT")
	end
	
	return Attachment
end

function getRagdollJoint(part: BasePart)
	local Joint
	for i, v in pairs(part:GetChildren()) do
		if(v:IsA("BallSocketConstraint") and v:HasTag("RAGDOLL_JOINT")) then
			Joint = v
		end
	end
	
	if(not (Joint == Joint) or Joint == nil) then
		Joint = Instance.new("BallSocketConstraint")
		Joint:AddTag("RAGDOLL_JOINT")
	end
	
	return Joint
end

function getRagdollCollisionPart(part: BasePart)
	local Part
	for i, v in pairs(part:GetChildren()) do
		if(v:IsA("BasePart") and v:HasTag("RAGDOLL_COLLISION_PART")) then
			Part = v
		end
	end
	if(not (Part == Part) or Part == nil) then
		Part = Instance.new("Part")
		Part.Size = Vector3.new(0.5,0.5,0.5)
		Part.Transparency = 1
		local Weld = Instance.new("WeldConstraint", Part)
		Weld.Part0 = Part
		Weld.Part1 = part
		Part.Position = part.Position
		Part.BrickColor = BrickColor.Black()
		Part.Parent = part
		Part:AddTag("RAGDOLL_COLLISION_PART")
	end
	return Part
end

function RagdollModule.RagdollCharacter(char: Model, enabled: boolean)
	warn("RAGDOLL!")
	if(not CharValidater.IsValidCharacter(char)) then return end
	
	local CharacterHumanoid = char:FindFirstChildOfClass("Humanoid")
	if enabled then
		CharacterHumanoid:SetAttribute("SavedReuiresNeck", CharacterHumanoid.RequiresNeck)
		CharacterHumanoid.RequiresNeck = false
	else
		local HRP: BasePart = char.HumanoidRootPart
		local _, Y, _ = HRP.CFrame:ToOrientation()
		HRP.CFrame = CFrame.new(HRP.Position.X, 5, HRP.Position.Z) * CFrame.fromOrientation(0, Y, 0)
		CharacterHumanoid.RequiresNeck =  CharacterHumanoid:GetAttribute("SavedReuiresNeck") or true
		CharacterHumanoid:ChangeState(Enum.HumanoidStateType.Running)
	end
	
	for _, v in pairs(char:GetDescendants()) do
		if(v:IsA("Motor6D")) then
			if(v.Parent.Name == "HumanoidRootPart") then continue end
			--Get Attachments And Joint
			local CollisionPart = getRagdollCollisionPart(v.Parent)
			
			local Attachment0: Attachment = getRagdollAttachment(v.Part0, v.Name)
			local Attachment1: Attachment = getRagdollAttachment(v.Part1, v.Name)
			
			local BallJoint: BallSocketConstraint = getRagdollJoint(v.Parent)
			
			--Set properties
			if(enabled) then
				Attachment0.CFrame = v.C0
				Attachment0.Name = v.Name
				Attachment0.Parent = v.Part0
				
				Attachment1.CFrame = v.C1
				Attachment1.Name = v.Name
				Attachment1.Parent = v.Part1
				
				BallJoint.Attachment0 = Attachment0
				BallJoint.Attachment1 = Attachment1
				BallJoint.MaxFrictionTorque = 50
				BallJoint.Name = v.Name
				BallJoint.Parent = v.Parent
				BallJoint.Enabled = true
				
				v.Enabled = false
			else
				BallJoint.Enabled = false
				v.Enabled = true
				if(CollisionPart ~= nil) then
					CollisionPart:Destroy()
				end
			end
			
		end
	end
	
	CharacterHumanoid.PlatformStand = enabled
end

return RagdollModule

1 Like

i once saw a youtube tutorial that could ragdoll any unanchored model in ~50 lines. i wish to find it again because it was the most beautiful thing ever.

There is this thing where the positions of the coordinates in Motot6ds are slightly botched, like in a different axis. Test for the values by playing around with the dimensions in a small example where you hard code the values to discern a pattern