Active ragdoll for r6, easy setup

I havent been on this forum for years, but I saw someone thinking they can BEST ME??? NEVER.
You need Active Ragdoll for your r6 game? here. No rigs, no setup, js call .new with the character, yes it supports npcs.

API:

  • RagdollSystem.new(char: Character, player?: Player, useDoubleRig?: boolean = false)
  • thats it, everything else handled internally, just use the module right, no i wont reply

Enjoy the beautiful types too, cause why not

local Players = game:GetService("Players")
local PhysicsService = game:GetService("PhysicsService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")

local RagdollSystem = {}
RagdollSystem.__index = RagdollSystem

export type AnimationData = {
	track: AnimationTrack,
	isPlaying: boolean,
}

export type ConnectionTable = {
	[string]: RBXScriptConnection,
}

export type self = {
	character: Model,
	player: Player?,
	useDoubleRig: boolean,
	isDead: boolean,
	ragdollState: boolean,
	activeRagdollState: string,
	connections: ConnectionTable,
	rigFolder: Folder,
	doubleFolder: Folder?,
	animations: { [string]: AnimationData }?,
}

export type RagdollSystem = typeof(setmetatable({} :: self, RagdollSystem))

-- Collision groups setup
local function setupCollisionGroups()
	local groups = { "NC", "DRChar" }
	for _, group in ipairs(groups) do
		pcall(function()
			PhysicsService:RegisterCollisionGroup(group)
		end)
	end

	PhysicsService:CollisionGroupSetCollidable("Default", "NC", false)
	PhysicsService:CollisionGroupSetCollidable("NC", "NC", true)
	PhysicsService:CollisionGroupSetCollidable("DRChar", "Default", false)
end

setupCollisionGroups()

-- Sound IDs for body impacts
local BODY_IMPACTS =
	{ 7446607140, 7446609976, 7446606976, 7446606796, 7446606925, 7446607091, 7446607037, 7446609932, 7446610013 }

-- Joint attachment positions (simplified)
local ATTACHMENT_CFRAMES = {
	["Neck"] = { CFrame.new(0, 1, 0), CFrame.new(0, -0.5, 0) },
	["Left Shoulder"] = { CFrame.new(-1.5, 0.5, 0), CFrame.new(0.5, 0.5, 0) },
	["Right Shoulder"] = { CFrame.new(1.5, 0.5, 0), CFrame.new(-0.5, 0.5, 0) },
	["Left Hip"] = { CFrame.new(-0.5, -1, 0), CFrame.new(0, 1, 0) },
	["Right Hip"] = { CFrame.new(0.5, -1, 0), CFrame.new(0, 1, 0) },
	["RootJoint"] = { CFrame.new(0, 0, 0), CFrame.new(0, 0, 0) },
}

-- Muscle positions for double rig
local MUSCLE_CFRAMES = {
	Head = CFrame.new(0, 0, 0),
	Torso = CFrame.new(0, 0, 0),
	["Left Arm"] = CFrame.new(0, 0, 0),
	["Right Arm"] = CFrame.new(0, 0, 0),
	["Left Leg"] = CFrame.new(0, 0, 0),
	["Right Leg"] = CFrame.new(0, 0, 0),
}

-- Animation IDs for double rig states
local ANIMATION_IDS = {
	Head = 14884198606,
	Torso = 14884229692,
	["Left Arm"] = 14884240711,
	["Right Arm"] = 14884237652,
	["Left Leg"] = 14884244257,
	["Right Leg"] = 14884229692,
}

-- Character limb names
local CHAR_LIMBS = {
	"Head",
	"Torso",
	"Left Arm",
	"Right Arm",
	"Left Leg",
	"Right Leg",
	"HumanoidRootPart",
}

local function isCharLimb(name)
	for _, limb in ipairs(CHAR_LIMBS) do
		if limb == name then
			return true
		end
	end
	return false
end

function RagdollSystem.new(character: Model, player: Player?, useDoubleRig: boolean?): RagdollSystem
	local self = setmetatable({} :: self, RagdollSystem) :: RagdollSystem

	self.character = character
	self.player = player
	self.useDoubleRig = useDoubleRig or false
	self.isDead = false
	self.ragdollState = false
	self.activeRagdollState = "None"
	self.connections = {}

	-- Create folders
	self.rigFolder = Instance.new("Folder")
	self.rigFolder.Name = "Rig"
	self.rigFolder.Parent = character

	if self.useDoubleRig then
		self.doubleFolder = Instance.new("Folder")
		self.doubleFolder.Name = "Double"
		self.doubleFolder.Parent = character
		self.animations = {}
	end

	self:setup()
	return self
end

function RagdollSystem:setup(): ()
	local humanoid = self.character:FindFirstChildOfClass("Humanoid")
	if not humanoid then
		return
	end

	-- Configure humanoid
	humanoid.BreakJointsOnDeath = false
	humanoid.RequiresNeck = false

	-- Connect death events
	self.connections.died = humanoid.Died:Connect(function()
		self.isDead = true
		self:setRagdoll(true)
		self:updateRagdoll()
	end)

	self.connections.stateChanged = humanoid.StateChanged:Connect(function(_, new: Enum.HumanoidStateType)
		if new == Enum.HumanoidStateType.Dead then
			self.isDead = true
			self:updateRagdoll()
		end
	end)

	-- Setup collision detection for impact sounds
	self:setupCollisionDetection()

	if self.useDoubleRig then
		self:setupDoubleRig()
	end

	-- Cleanup when character is removed
	self.connections.ancestryChanged = self.character.AncestryChanged:Connect(function(_, parent: Instance?)
		if parent == nil then
			self:destroy()
		end
	end)
end

function RagdollSystem:setupCollisionDetection(): ()
	local soundCooldown = false

	for _, child in pairs(self.character:GetChildren()) do
		if child:IsA("BasePart") then
			self.connections[child.Name .. "_touched"] = child.Touched:Connect(function(hit: BasePart)
				local velocity = child.AssemblyLinearVelocity.Magnitude

				-- Impact sound
				if
					velocity > 5
					and not soundCooldown
					and self.ragdollState
					and not hit:IsDescendantOf(self.character)
				then
					soundCooldown = true
					task.delay(0.25, function()
						soundCooldown = false
					end)

					-- stub, i originally played a sound here (you can too)
				end
			end)
		end
	end
end

function RagdollSystem:createCollider(part: BasePart): ()
	if part:FindFirstChild("ColliderPart") then
		return
	end

	local collider = Instance.new("Part")
	collider.Name = "ColliderPart"
	collider.Size = part.Size / 1.7
	collider.Massless = true
	collider.CFrame = part.CFrame
	collider.Transparency = 1
	collider.CustomPhysicalProperties = PhysicalProperties.new(2, 5, 0.2, 80, 80)

	local weld = Instance.new("WeldConstraint")
	weld.Part0 = collider
	weld.Part1 = part
	weld.Parent = collider
	collider.Parent = part
end

function RagdollSystem:setupDoubleRig(): ()
	-- Create double rig parts
	for _, part in pairs(self.character:GetChildren()) do
		if part:IsA("BasePart") and isCharLimb(part.Name) then
			local dublePart = Instance.new("Part")
			dublePart.Name = part.Name
			dublePart.Size = part.Size
			dublePart.CanCollide = false
			dublePart.CanQuery = false
			dublePart.CanTouch = false
			dublePart.Transparency = 1
			dublePart.CollisionGroup = "DRChar"
			dublePart.Parent = self.doubleFolder

			-- Set custom physics properties for original part
			part.CustomPhysicalProperties = PhysicalProperties.new(2, 0.45, 0.6, 100, 100)

			-- Find motor6D for this limb
			local motor6d = self:findMotor6DForLimb(part)
			if motor6d then
				-- Create attachments
				local attach1 = Instance.new("Attachment")
				local attach2 = Instance.new("Attachment")
				attach1.Parent = part
				attach2.Parent = dublePart
				attach1.CFrame = MUSCLE_CFRAMES[part.Name] or CFrame.new()
				attach2.CFrame = MUSCLE_CFRAMES[part.Name] or CFrame.new()

				-- Create spring constraint
				local spring = Instance.new("SpringConstraint")
				spring.Name = part.Name .. " Spring"
				spring.Attachment0 = attach1
				spring.Attachment1 = attach2
				spring.Damping = 80
				spring.Stiffness = 20000
				spring.Visible = false
				spring.Enabled = false
				spring.Coils = 1
				spring.MinLength = 0
				spring.MaxLength = 5
				spring.MaxForce = 500000
				spring.LimitsEnabled = true
				spring.Parent = self.doubleFolder

				-- Clone motor6d for double rig
				local clonedMotor = motor6d:Clone()
				clonedMotor.Parent = self.doubleFolder
				clonedMotor.Enabled = true
			end
		end
	end

	-- Update motor6d connections for double rig
	for _, motor in pairs(self.doubleFolder:GetChildren()) do
		if motor:IsA("Motor6D") then
			if motor.Part1 and self.doubleFolder:FindFirstChild(motor.Part1.Name) then
				motor.Part1 = self.doubleFolder:FindFirstChild(motor.Part1.Name)
			end
			if motor.Part0 and self.doubleFolder:FindFirstChild(motor.Part0.Name) then
				motor.Part0 = self.doubleFolder:FindFirstChild(motor.Part0.Name)
			end
		end
	end

	-- Create root weld
	local weld = Instance.new("Weld")
	weld.Name = "DoubleWeld"
	weld.Part1 = self.character:FindFirstChild("Torso")
	weld.Part0 = self.doubleFolder:FindFirstChild("Torso")
	weld.C0 = CFrame.new()
	weld.Parent = self.character

	-- Load animations
	self:loadAnimations()

	-- Start update loop
	self.connections.heartbeat = RunService.Heartbeat:Connect(function(dt: number)
		self:updateDoubleRig(dt)
	end)
end

function RagdollSystem:findMotor6DForLimb(limb: BasePart): Motor6D?
	if limb.Name == "HumanoidRootPart" then
		return nil
	end

	local torso = self.character:FindFirstChild("Torso")
	if not torso then
		return nil
	end

	for _, descendant in pairs(torso:GetDescendants()) do
		if descendant:IsA("Motor6D") and descendant.Part1 == limb then
			return descendant
		end
	end
	return nil
end

function RagdollSystem:loadAnimations(): ()
	local humanoid = self.character:FindFirstChildOfClass("Humanoid")
	if not humanoid then
		return
	end

	for limbName, animId in pairs(ANIMATION_IDS) do
		local animation = Instance.new("Animation")
		animation.AnimationId = "rbxassetid://" .. animId

		local track = humanoid:LoadAnimation(animation)
		self.animations[limbName] = {
			track = track,
			isPlaying = false,
		}

		track.Stopped:Connect(function()
			if self.activeRagdollState == limbName then
				self.activeRagdollState = "None"
			end
			self.animations[limbName].isPlaying = false
		end)
	end
end

function RagdollSystem:updateDoubleRig(): ()
	if self.character.Parent == nil then
		return
	end

	local anyAnimationPlaying = false

	-- Update animation states and check if any are playing
	for limbName, animData in pairs(self.animations) do
		if self.activeRagdollState == limbName then
			if not animData.track.IsPlaying then
				animData.track:Play()
				animData.isPlaying = true
			end
		else
			if animData.track.IsPlaying then
				animData.track:Stop()
				animData.isPlaying = false
			end
		end

		-- Check if this animation is currently playing
		if animData.track.IsPlaying then
			anyAnimationPlaying = true
		end
	end

	-- Only enable active ragdoll when animations are playing AND character is ragdolled
	local activeRagdollEnabled = self.ragdollState and anyAnimationPlaying

	-- Update spring constraints - only active when animations are playing
	for _, item in pairs(self.doubleFolder:GetChildren()) do
		if item:IsA("SpringConstraint") then
			item.Enabled = activeRagdollEnabled
		elseif item:IsA("Motor6D") then
			-- Double rig motor6ds are always enabled for the double rig to work
			item.Enabled = true
		end
	end

	-- Handle root joint - connect to double rig only when active ragdoll is enabled
	local rootJoint = self.character.HumanoidRootPart:FindFirstChildOfClass("Motor6D")
	if rootJoint then
		if activeRagdollEnabled and not self.isDead then
			-- Connect to double rig for active ragdoll
			rootJoint.Part1 = self.doubleFolder:FindFirstChild("Torso")
		else
			-- Connect to normal torso for free ragdoll
			rootJoint.Part1 = self.character:FindFirstChild("Torso")
		end
	end

	-- Network ownership for NPCs
	if not Players:GetPlayerFromCharacter(self.character) and not self.isDead then
		pcall(function()
			self.character.HumanoidRootPart:SetNetworkOwner(nil)
		end)
	end
end

function RagdollSystem:updateRagdoll(): ()
	-- Create ball socket constraints if needed
	for _, descendant in pairs(self.character:GetDescendants()) do
		if descendant:IsA("Motor6D") then
			local attachData = ATTACHMENT_CFRAMES[descendant.Name]
			if
				attachData
				and descendant.Part0
				and descendant.Part1
				and not self.rigFolder:FindFirstChild(descendant.Name .. " Socket")
			then
				local attach0 = Instance.new("Attachment")
				local attach1 = Instance.new("Attachment")
				attach0.CFrame = attachData[1]
				attach1.CFrame = attachData[2]

				self:createCollider(descendant.Part1)

				local socket = Instance.new("BallSocketConstraint")
				socket.Attachment0 = attach0
				socket.Attachment1 = attach1
				socket.Radius = 0.15
				socket.LimitsEnabled = true
				socket.TwistLimitsEnabled = true
				socket.MaxFrictionTorque = 0
				socket.Restitution = 0
				socket.UpperAngle = 90
				socket.TwistLowerAngle = -45
				socket.TwistUpperAngle = 45

				-- Special settings for neck
				if descendant.Name == "Neck" then
					socket.UpperAngle = 45
					socket.TwistLowerAngle = -70
					socket.TwistUpperAngle = 70
				end

				attach0.Parent = descendant.Part0
				attach1.Parent = descendant.Part1
				socket.Parent = self.rigFolder
				socket.Name = descendant.Name .. " Socket"
				socket.Enabled = self.ragdollState
			end
		end
	end

	-- Update constraint states
	for _, socket in pairs(self.rigFolder:GetChildren()) do
		if socket:IsA("BallSocketConstraint") then
			socket.Enabled = self.ragdollState
		end
	end

	-- Update motor6d states
	for _, descendant in pairs(self.character:GetDescendants()) do
		if descendant:IsA("Motor6D") then
			descendant.Enabled = not self.ragdollState
		end
	end

	-- Update collision groups and physics
	for _, part in pairs(self.character:GetChildren()) do
		if part:IsA("BasePart") then
			local collider = part:FindFirstChild("ColliderPart")
			if collider and collider:IsA("BasePart") then
				collider.CollisionGroup = self.ragdollState and "Default" or "NC"
				part.CollisionGroup = self.ragdollState and "NC" or "Default"
			end
			part.CustomPhysicalProperties = PhysicalProperties.new(2, 5, 0.2, 80, 80)
		end
	end

	-- Update humanoid
	local humanoid = self.character:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid.PlatformStand = self.ragdollState
	end
end

function RagdollSystem:setRagdoll(enabled: boolean): ()
	if self.ragdollState == enabled then
		return
	end

	self.ragdollState = enabled
	if self.isDead then
		self.ragdollState = true
	end

	self:updateRagdoll()
end

function RagdollSystem:setActiveRagdollState(state: string?): ()
	if not self.useDoubleRig then
		return
	end

	self.activeRagdollState = state or "None"
end

-- New helper function to check if active ragdoll is currently engaged
function RagdollSystem:isActiveRagdollEngaged(): boolean
	if not self.useDoubleRig or not self.ragdollState then
		return false
	end

	-- Check if any animation is currently playing
	for _, animData in pairs(self.animations or {}) do
		if animData.track and animData.track.IsPlaying then
			return true
		end
	end

	return false
end

function RagdollSystem:destroy(): ()
	-- Disconnect all connections
	for _, connection in pairs(self.connections) do
		if connection then
			connection:Disconnect()
		end
	end

	-- Clean up animations
	if self.animations then
		for _, animData in pairs(self.animations) do
			if animData.track then
				animData.track:Stop()
				animData.track:Destroy()
			end
		end
	end

	-- Clean up folders
	if self.rigFolder then
		self.rigFolder:Destroy()
	end
	if self.doubleFolder then
		self.doubleFolder:Destroy()
	end
end

return RagdollSystem
15 Likes

no video examples or anything??

3 Likes

I :skull: aint doing :x: :sob: allat boy :grimacing: :ok_hand: :stuck_out_tongue_winking_eye: :skull: :fire:

4 Likes

be prepared :skull: for barely anyone :x: to use your module then :grimacing: :ok_hand: :stuck_out_tongue_winking_eye: :skull: :fire:

16 Likes

I really dont care some guy might see this in a few months and know how to use active ragdoll and thats all i care about cuz i spent years learning how to do this in roblox

2 Likes

looks like you forgot to remove duplicates of service requires :upside_down_face:

3 Likes

whoopssssssssssssssssssssssssssssssss

1 Like

ts module real cute. giving it a try as we speak!

1 Like

Next up, balancing/stumble system for r6 active ragdoll :money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face:

dont tempt me
thirtyCHARSSSSSSSSfcbdgfdbregerfgarfega

1 Like

Yes, please​:money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face::money_mouth_face:

Its gonna be very cool!

This ragdoll is buns
thirtycharssss

For anyone interested in using this resource, I made a wrapper which you can get here:
Ragdoll.rbxm (7.9 KB)
image

This is what it looks like when:

  • ragdolling & unragdolling from a non-dead state
  • ragdolling from a dead state

I understand this module is intended for just ragdoll deaths, but the wrapper serves as a way to explore how the module would perform in a non-dead state.

Some issues when ragdolling from a non-dead state:

  • there is a chance you can still move and jump while ragdolled
  • the camera can sway loosely while ragdolled
  • unragdolling can cause the player to spin around

The up-to-date syntax and typing are a plus, but upon testing the ragdoll on reset there is a noticeable delay/lag before the player is ragdolled when standing still.

1 Like

are you sure you know what active ragdolls are???

1 Like

I dont know if i am stupid but I tried both local scripts and server scripts and R6 and R15 and none work