Feedback on my Ragdoll System

I’ve recently made myself a Ragdoll Module to practice using ModuleScripts and so I can ragdoll characters in future projects. I’m satisfied with it for now, but I’d like some feedback to see what I can improve on.

My system is all in one ModuleScript, and it has two functions: Ragdoll and UnRagdoll, both taking a Character model as an argument. When someone is ragdolled, the script disables all Motor6Ds in the model and adds a bunch of BallSocketConstraints to various body parts, and I do some other stuff like Humanoid.PlatformStand = true and setting their speed to 0. I also have two CollisionGroups. I discovered that the character’s limbs constantly have their CanCollide property changed (not sure why), so the script also creates copies of the character’s limbs so their body parts don’t go through the floor.

I’d greatly appreciate feedback on what flaws you could find in my code (are there coding practices I should be implementing, am I implementing bad coding practices, are there better ways to write some of my code, etc etc). Definitely tell me if I haven’t clarified anything (in this post or in the code), I’ll be happy to respond!

-- Make sure there are two CollisionGroups named "RagdollPart" and "CharacterPart"
-- "RagdollPart" shouldn't collide with any parts with either tags, and "CharacterPart" shouldn't collide with parts with the "RagdollPart" tag
-- Only works with R6

local RagdollModule = {}

local relevantParts = {
	-- Relevant parts stored here so you don't work with other parts under the character model
	"Head",
	"Left Arm",
	"Right Arm",
	"Left Leg",
	"Right Leg"
}
local attachmentPosData = {
	-- Stores the positions all attachments will need when 
	["Head"] = {["Attach0"] = Vector3.new(0, -0.25, 0), ["Attach1"] = Vector3.new(0, 1.25, 0)},
	["Left Arm"] = {["Attach0"] = Vector3.new(0, 0.75, 0), ["Attach1"] = Vector3.new(-1.5, 0.75, 0)},
	["Right Arm"] = {["Attach0"] = Vector3.new(0, 0.75, 0), ["Attach1"] = Vector3.new(1.5, 0.75, 0)},
	["Left Leg"] = {["Attach0"] = Vector3.new(0, 0.75, 0), ["Attach1"] = Vector3.new(-0.5, -1.25, 0)},
	["Right Leg"] = {["Attach0"] = Vector3.new(0, 0.75, 0), ["Attach1"] = Vector3.new(0.5, -1.25, 0)}
}
local ragdolledPlayers = {} -- Keeps track of currently ragdolled players

local RAGDOLL_COLLISIONGROUP = "RagdollPart"
local CHARACTERPART_COLLISIONGROUP = "CharacterPart"

local function setCollisionGroups(part, collisionGroup)
	if part then
		part.CollisionGroup = collisionGroup
	end
end

local function createAttachment(position, parent, name)
	local attachment = Instance.new("Attachment")
	attachment.Name = name
	attachment.Position = position
	attachment.Parent = parent
	return attachment
end

local function createBallSocketConstraint(parent, name, attachment0, attachment1)
	local ballSocketConstraint = Instance.new("BallSocketConstraint")
	ballSocketConstraint.Name = name
	ballSocketConstraint.Attachment0 = attachment0
	ballSocketConstraint.Attachment1 = attachment1
	ballSocketConstraint.Parent = parent
	return ballSocketConstraint
end

local function createCloneOfPart(character, characterPart)
	local part = Instance.new("Part")
	part.Massless = true
	part.Anchored = false
	part.CanCollide = true
	part.Name = characterPart.Name .. "_Clone"
	part.Transparency = 1
	part.Size = characterPart.Size
	part.CFrame = characterPart.CFrame
	
	local weld = Instance.new("WeldConstraint")
	weld.Part0 = part
	weld.Part1 = characterPart
	weld.Parent = part
	
	local mesh = character[characterPart.Name]:FindFirstChildWhichIsA("SpecialMesh")
	if characterPart.Name == "Head" then
		local copy = mesh:Clone()
		copy.Parent = part
	end
	
	setCollisionGroups(part, RAGDOLL_COLLISIONGROUP)
	return part
end

local function disableMotor6Ds(character: Model)
	if character then
		local humanoid = character:FindFirstChildWhichIsA("Humanoid")
		local torso = character:FindFirstChild("Torso")
		if humanoid and torso then
			humanoid.RequiresNeck = false
			for _, child in torso:GetChildren() do
				if child:IsA("Motor6D") then
					child.Enabled = false
				end
			end
		end
	end
end

local function enableMotor6Ds(character: Model)
	if character then
		local humanoid = character:FindFirstChildWhichIsA("Humanoid")
		local torso = character:FindFirstChild("Torso")
		if humanoid and torso then
			for _, child in torso:GetChildren() do
				if child:IsA("Motor6D") then
					child.Enabled = true
				end
			end
			humanoid.RequiresNeck = true
		end
	end
end

function RagdollModule:Ragdoll(character)
	if character:GetAttribute("Ragdolled") == true then
		return
	end
	
	if character then
		local humanoid = character:FindFirstChildWhichIsA("Humanoid")
		if humanoid then
			disableMotor6Ds(character)
			-- RagdollObjects is a list of all objects (attachments, constraints, and parts) created for that specific character
			ragdolledPlayers[character] = {OriginalWalkSpeed = humanoid.WalkSpeed, OriginalJumpPower = humanoid.JumpPower, RagdollObjects = {}}
			humanoid.WalkSpeed = 0
			humanoid.JumpPower = 0
			humanoid.PlatformStand = true
			
			for _, child in character:GetChildren() do
				if child:IsA("BasePart") and table.find(relevantParts, child.Name) then
					setCollisionGroups(child, CHARACTERPART_COLLISIONGROUP)
					local clonedPart = createCloneOfPart(character, child)
					clonedPart.Parent = character
					table.insert(ragdolledPlayers[character].RagdollObjects, clonedPart)
				end
			end
			setCollisionGroups(character.Torso, CHARACTERPART_COLLISIONGROUP)
			
			for i = 1, #relevantParts do
				local attach0 = createAttachment(attachmentPosData[relevantParts[i]]["Attach0"], character[relevantParts[i]], relevantParts[i].."Attachment0")
				local attach1 = createAttachment(attachmentPosData[relevantParts[i]]["Attach1"], character.Torso, relevantParts[i].."Attachment1")
				local ballSocketConstraint = createBallSocketConstraint(character[relevantParts[i]], "BallSocketConstraint_"..relevantParts[i], attach0, attach1)
				table.insert(ragdolledPlayers[character].RagdollObjects, attach0)
				table.insert(ragdolledPlayers[character].RagdollObjects, attach1)
				table.insert(ragdolledPlayers[character].RagdollObjects, ballSocketConstraint)
			end
			character:SetAttribute("Ragdolled", true)
		end
	end
end

function RagdollModule:UnRagdoll(character)
	if not character:GetAttribute("Ragdolled") or character:GetAttribute("Ragdolled") == false then
		return
	end
	
	if character then
		local humanoid = character:FindFirstChildWhichIsA("Humanoid")
		if humanoid then
			if ragdolledPlayers[character] then
				for _, object in ragdolledPlayers[character].RagdollObjects do
					object:Destroy()
				end
				humanoid.PlatformStand = false
				enableMotor6Ds(character)
				humanoid.WalkSpeed = ragdolledPlayers[character].OriginalWalkSpeed
				humanoid.JumpPower = ragdolledPlayers[character].OriginalJumpPower
				ragdolledPlayers[character] = nil
			end
			character:SetAttribute("Ragdolled", false)
		end
	end
end

return RagdollModule

Here’s what it looks like in something else I put it in:

1 Like
  1. First, remove the setCollisionGroups function, its an unnecessary function that happens to have code that is as simple as its name, which makes the code less clean

  2. Avoid nested structures as much as possible, especially the humanoid and character in the if checkings, instead do:

if not character and humanoid and torso then return end
  1. Remove duplicated code, for example instead of checking the character or humanoid in each motor6d function, just have it checked in the ragdoll function or/and the unragdoll function.

When you do all of that, your module script will be clean and optimized!

2 Likes

Thank you for the feedback! I’ll definitely implement these when I update it.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.