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: