--[[
Improved Roblox Character Balancing Script
Inspired by Euphoria physics for more human-like reactions.
Features:
- Active ragdoll state toggled with 'R' key.
- Dynamic torso stabilization using AlignOrientation.
- Predictive stepping based on Center of Mass (COM) offset and velocity.
- Active arm balancing using AngularVelocity.
- Foot planting to keep feet grounded when not stepping.
- Smoother motion using Lerp and dynamic parameter adjustments.
]]
local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")
local upperTorso = character:FindFirstChild("UpperTorso") -- Store for frequent access
local lowerTorso = character:FindFirstChild("LowerTorso") -- Optional, but good for COM
local userInputService = game:GetService("UserInputService")
local runService = game:GetService("RunService")
local physicsService = game:GetService("PhysicsService")
humanoid.BreakJointsOnDeath = false
humanoid.RequiresNeck = false -- Often disabled for custom ragdolls
-- ============================================================
-- STATE VARIABLES & CONSTANTS
-- ============================================================
local isRagdolled = false
local jointConstraints = {} -- Stores BallSocketConstraints replacing Motor6Ds
local physicsConstraints = {} -- Stores AlignPosition, AlignOrientation, VectorForce etc.
local originalCollisions = {} -- Stores original collision settings of parts
local ragdollAttachments = {} -- Stores attachments created for constraints
local stepConnection = nil -- Connection for the RunService.Stepped event
-- Active Balancing State
local steppingFootName = nil -- "LeftFoot", "RightFoot", or nil
local stepTargetPosition = nil -- Where the stepping foot is aiming
local stepCooldown = 0 -- Prevents rapid, jittery stepping
local stepProgress = 0 -- [0,1] normalized progress of the current step
local lastSteppedFoot = "RightFoot" -- Start by assuming the right foot was last planted
local isStumbling = false -- Flag for quick follow-up steps
-- Timing & Distance Constants (Adjust these for feel)
local STEP_COOLDOWN_TIME = 0.4 -- Base time between steps
local STUMBLE_COOLDOWN_TIME = 0.15 -- Faster cooldown if stumbling
local STEP_DURATION = 0.35 -- Time to complete a step animation
local STEP_COMPLETION_DISTANCE = 0.3 -- How close foot needs to be to target to finish step
local STEP_MAX_REACH = 3.5 -- Maximum distance a step will target
-- Balancing Thresholds & Parameters (Crucial for tuning the feel)
local BALANCE_LeanThreshold_FB = 0.25 -- Forward/backward lean distance before stepping
local BALANCE_LeanThreshold_LR = 0.20 -- Left/right lean distance before stepping
local BALANCE_VelocityPredictionFactor = 0.2 -- How much velocity influences predicted lean (higher = more predictive)
local BALANCE_StepDistanceFactor = 1.1 -- Multiplier for how far to step based on lean
local BALANCE_StepForce = 8000 -- Force applied to move the stepping foot
local BALANCE_StepResponsiveness = 80 -- How quickly the foot moves to the target (lower = softer)
local BALANCE_StanceWidth = 1.0 -- Desired distance between feet laterally
local BALANCE_LiftHeight = 0.4 -- How high the foot lifts during a step arc
-- Smoothing & Damping Factors (Lower = smoother/slower response)
local TORSO_LERP_BASE = 0.08 -- Base interpolation factor for torso upright correction
local TORSO_LERP_AGGRESSION = 0.3 -- How much the Lerp factor increases when heavily leaned
local ARM_LERP_FACTOR = 0.05 -- Interpolation for arm balancing movements
local ARM_SWING_INTENSITY = 15 -- How strongly arms swing out to counterbalance lean
local FOOT_PLANT_RESPONSIVENESS = 25 -- How quickly planted feet stick to the ground
local FOOT_PLANT_MAX_FORCE = 5000 -- Force keeping planted feet down
local COM_SMOOTHING = 0.1 -- Damping factor for Center of Mass calculation (reduces jitter)
-- Anti-Gravity Force Multiplier (Slightly less than 1 to keep feet on ground)
local ANTI_GRAVITY_MULTIPLIER = 0.985
-- ============================================================
-- SAVE ORIGINAL COLLISION SETTINGS
-- ============================================================
local function saveOriginalCollisions()
originalCollisions = {}
for _, part in pairs(character:GetDescendants()) do
if part:IsA("BasePart") then
originalCollisions[part] = {
CanCollide = part.CanCollide,
CollisionGroupId = part.CollisionGroupId,
Massless = part.Massless,
CustomPhysicalProperties = part.CustomPhysicalProperties
}
part.Massless = false -- Ensure parts have mass for physics
end
end
end
saveOriginalCollisions() -- Save initially
-- ============================================================
-- UTILITY FUNCTIONS
-- ============================================================
-- Cleans up all created constraints and attachments
local function clearRagdollPhysics()
if stepConnection then
stepConnection:Disconnect()
stepConnection = nil
end
for _, constraint in pairs(physicsConstraints) do
if constraint and constraint.Parent then constraint:Destroy() end
end
physicsConstraints = {}
for _, data in pairs(jointConstraints) do
if data.constraint and data.constraint.Parent then data.constraint:Destroy() end
if data.a1 and data.a1.Parent then data.a1:Destroy() end
if data.a2 and data.a2.Parent then data.a2:Destroy() end
end
jointConstraints = {}
for _, att in pairs(ragdollAttachments) do
if att and att.Parent then att:Destroy() end
end
ragdollAttachments = {}
steppingFootName = nil
stepTargetPosition = nil
stepCooldown = 0
stepProgress = 0
isStumbling = false
end
-- Creates an attachment safely and stores it for cleanup
local function createConstraintAttachment(parentPart, nameSuffix)
local att = Instance.new("Attachment")
att.Name = "RagdollAttachment_" .. (nameSuffix or parentPart.Name)
att.Parent = parentPart
table.insert(ragdollAttachments, att)
return att
end
-- Calculates a smoothed Center of Mass (COM)
local lastCOM = rootPart.Position
local function calculateSmoothedCenterOfMass()
local totalMass = 0
local weightedSum = Vector3.new(0, 0, 0)
-- Include major body parts for COM calculation
local partsToConsider = { upperTorso, lowerTorso, character:FindFirstChild("Head"), rootPart }
for _, part in pairs(partsToConsider) do
if part and part:IsA("BasePart") and not part.Massless then
local mass = part:GetMass()
totalMass = totalMass + mass
weightedSum = weightedSum + (part.Position * mass)
end
end
local currentCOM = rootPart.Position -- Fallback
if totalMass > 0 then
currentCOM = weightedSum / totalMass
end
-- Smooth the COM using Lerp to reduce jitter
lastCOM = lastCOM:Lerp(currentCOM, COM_SMOOTHING)
return lastCOM
end
-- Raycast downwards to find ground position, ignoring the character
local groundRaycastParams = RaycastParams.new()
groundRaycastParams.FilterType = Enum.RaycastFilterType.Exclude
groundRaycastParams.FilterDescendantsInstances = {character}
local function findGroundY(position)
local result = workspace:Raycast(position + Vector3.new(0, 1, 0), Vector3.new(0, -10, 0), groundRaycastParams)
return result and result.Position.Y
end
-- ============================================================
-- FOOT PLANTING (Keeps non-stepping foot grounded)
-- ============================================================
local function updateFootPlanting()
for _, footName in pairs({"LeftFoot", "RightFoot"}) do
local footPart = character:FindFirstChild(footName)
if footPart then
local plantConstraintName = footName .. "_Plant"
local alignPos = physicsConstraints[plantConstraintName]
if not alignPos then
alignPos = Instance.new("AlignPosition")
alignPos.Name = plantConstraintName
alignPos.Mode = Enum.PositionAlignmentMode.OneAttachment
alignPos.Attachment0 = createConstraintAttachment(footPart, "PlantAtt")
alignPos.MaxForce = FOOT_PLANT_MAX_FORCE
alignPos.Responsiveness = FOOT_PLANT_RESPONSIVENESS
alignPos.ApplyAtCenterOfMass = true -- Apply force smoothly
alignPos.Parent = footPart
physicsConstraints[plantConstraintName] = alignPos
end
-- Enable planting only if the foot is NOT the one currently stepping
if steppingFootName == footName then
alignPos.Enabled = false
else
local groundY = findGroundY(footPart.Position)
if groundY then
-- Target slightly above ground to prevent sinking
local targetPos = Vector3.new(footPart.Position.X, groundY + 0.1, footPart.Position.Z)
-- Smoothly move towards the target position
alignPos.Position = alignPos.Position:Lerp(targetPos, 0.5) -- Lerp for smoother planting
alignPos.Enabled = true
else
-- No ground detected, disable planting
alignPos.Enabled = false
end
end
end
end
end
-- ============================================================
-- DYNAMIC TORSO CORRECTION (More adaptive upright force)
-- ============================================================
local function updateTorsoCorrection(dt)
if not upperTorso then return end
local torsoAlign = physicsConstraints["UpperTorso_Align"]
if torsoAlign then
-- Calculate lean angle (dot product with world up vector)
local currentUp = upperTorso.CFrame.UpVector
local uprightDot = currentUp:Dot(Vector3.new(0, 1, 0)) -- 1 = perfectly upright, -1 = upside down
local leanAmount = 1.0 - math.clamp(uprightDot, 0, 1) -- 0 = upright, 1 = horizontal or more
-- Dynamically adjust responsiveness and interpolation based on lean
local aggressionFactor = leanAmount * leanAmount -- Square it to make correction stronger when heavily leaned
local currentLerpFactor = math.clamp(TORSO_LERP_BASE + aggressionFactor * TORSO_LERP_AGGRESSION, TORSO_LERP_BASE, 0.6)
local currentResponsiveness = math.clamp(30 + aggressionFactor * 100, 30, 130) -- Increase responsiveness when needed
-- Target orientation: Upright, facing the same direction as the root part
local targetLook = rootPart.CFrame.LookVector
local targetUp = Vector3.new(0, 1, 0)
local targetRight = targetLook:Cross(targetUp).Unit
targetLook = targetUp:Cross(targetRight).Unit -- Ensure orthogonality
-- Use CFrame.new(pos, lookAt) for potentially better stability if needed
-- local targetCFrame = CFrame.new(upperTorso.Position, upperTorso.Position + targetLook)
local targetCFrame = CFrame.fromMatrix(upperTorso.Position, targetRight, targetUp, -targetLook) -- More explicit construction
-- Apply smoothed orientation target
torsoAlign.CFrame = torsoAlign.CFrame:Lerp(targetCFrame, currentLerpFactor)
torsoAlign.Responsiveness = currentResponsiveness
-- Optional: Slightly increase MaxTorque when heavily leaned too
torsoAlign.MaxTorque = math.clamp(20000 + aggressionFactor * 30000, 20000, 50000)
end
end
-- ============================================================
-- ACTIVE ARM BALANCING (Swing arms to counter lean)
-- ============================================================
local function updateArmBalancing(leanVectorRight, leanVectorForward, dt)
local rightArm = character:FindFirstChild("RightUpperArm")
local leftArm = character:FindFirstChild("LeftUpperArm")
if not rightArm or not leftArm then return end
local rightAV = physicsConstraints["RightUpperArm_Swing"]
local leftAV = physicsConstraints["LeftUpperArm_Swing"]
if not rightAV or not leftAV then return end
-- Calculate target angular velocity for arms based on lean
-- Swing arms outwards/upwards relative to the torso's current orientation
local torsoRight = upperTorso.CFrame.RightVector
local torsoForward = upperTorso.CFrame.LookVector
local torsoUp = upperTorso.CFrame.UpVector
-- Target swing: proportional to lean, opposing the lean direction
-- Lean right -> swing left arm out/up, right arm slightly in/down (or less out)
-- Lean forward -> swing arms slightly back/down
local targetRightArmVel = (torsoForward * leanVectorRight * -ARM_SWING_INTENSITY) + (torsoRight * leanVectorForward * -ARM_SWING_INTENSITY * 0.5)
local targetLeftArmVel = (torsoForward * leanVectorRight * ARM_SWING_INTENSITY) + (torsoRight * leanVectorForward * -ARM_SWING_INTENSITY * 0.5)
-- If stepping, add a swing component related to the step
if steppingFootName then
local swingDir = (steppingFootName == "LeftFoot") and 1 or -1 -- Swing opposite arm forward
targetRightArmVel = targetRightArmVel + torsoRight * swingDir * -ARM_SWING_INTENSITY * 0.7
targetLeftArmVel = targetLeftArmVel + torsoRight * swingDir * ARM_SWING_INTENSITY * 0.7
end
-- Smoothly interpolate towards the target velocity
rightAV.AngularVelocity = rightAV.AngularVelocity:Lerp(targetRightArmVel, ARM_LERP_FACTOR)
leftAV.AngularVelocity = leftAV.AngularVelocity:Lerp(targetLeftArmVel, ARM_LERP_FACTOR)
end
-- ============================================================
-- ADVANCED ACTIVE BALANCING LOGIC (Predictive Stepping)
-- ============================================================
local function updateActiveBalance(dt)
stepCooldown = math.max(0, stepCooldown - dt)
local leftFoot = character:FindFirstChild("LeftFoot")
local rightFoot = character:FindFirstChild("RightFoot")
if not leftFoot or not rightFoot or not rootPart or not upperTorso then return end
-- Calculate key vectors and positions
local com = calculateSmoothedCenterOfMass()
local leftPos = leftFoot.Position
local rightPos = rightFoot.Position
local avgFootPos = (leftPos + rightPos) / 2
local groundY = math.min(leftPos.Y, rightPos.Y) -- Use lowest foot as ground reference
-- Character's local XZ plane directions
local rootCF = rootPart.CFrame
local forwardXZ = (rootCF.LookVector * Vector3.new(1, 0, 1)).Unit
local rightXZ = (rootCF.RightVector * Vector3.new(1, 0, 1)).Unit
-- Calculate COM offset relative to average foot position (support base center)
local comXZ = Vector3.new(com.X, 0, com.Z)
local avgFootXZ = Vector3.new(avgFootPos.X, 0, avgFootPos.Z)
local offsetVector = comXZ - avgFootXZ
-- Predict future offset based on current velocity
local velocityXZ = rootPart.AssemblyLinearVelocity * Vector3.new(1, 0, 1)
local predictedOffset = offsetVector + velocityXZ * BALANCE_VelocityPredictionFactor
-- Project predicted offset onto character's forward/right axes
local leanForward = predictedOffset:Dot(forwardXZ)
local leanRight = predictedOffset:Dot(rightXZ)
-- Update active arm balancing based on lean
updateArmBalancing(leanRight, leanForward, dt)
-- === STEP EXECUTION (If currently stepping) ===
if steppingFootName then
local footPart = character:FindFirstChild(steppingFootName)
if footPart and stepTargetPosition then
stepProgress = math.min(stepProgress + dt / STEP_DURATION, 1)
-- Use SmoothStep for easing (starts slow, accelerates, ends slow)
local easeAlpha = stepProgress * stepProgress * (3 - 2 * stepProgress)
-- Calculate foot position along the step arc
local startPos = physicsConstraints[steppingFootName .. "_BalanceStep"].Attachment0.WorldPosition -- Where the step started
local currentTargetPos = startPos:Lerp(stepTargetPosition, easeAlpha)
local arcHeight = math.sin(stepProgress * math.pi) * BALANCE_LiftHeight -- Sine curve for lift
currentTargetPos = currentTargetPos + Vector3.new(0, arcHeight, 0)
-- Update the AlignPosition constraint target
local stepConstraint = physicsConstraints[steppingFootName .. "_BalanceStep"]
if stepConstraint then
stepConstraint.Position = currentTargetPos
-- Optional: Reduce responsiveness towards the end of the step for softer landing
stepConstraint.Responsiveness = BALANCE_StepResponsiveness * (1 - easeAlpha * 0.5)
end
-- Check for step completion
local distToTarget = (footPart.Position * Vector3.new(1, 0, 1) - stepTargetPosition * Vector3.new(1, 0, 1)).Magnitude
local isBalancedEnough = math.abs(leanForward) < (BALANCE_LeanThreshold_FB * 0.7) and math.abs(leanRight) < (BALANCE_LeanThreshold_LR * 0.7)
if stepProgress >= 1 or distToTarget < STEP_COMPLETION_DISTANCE then
-- Step finished
if stepConstraint then stepConstraint.Enabled = false end
lastSteppedFoot = steppingFootName
steppingFootName = nil
stepTargetPosition = nil
stepProgress = 0
stepCooldown = isStumbling and STUMBLE_COOLDOWN_TIME or STEP_COOLDOWN_TIME -- Use normal cooldown after successful step
isStumbling = false -- Reset stumble flag
-- Gradually reset arm swing after step completion
local rightArmAV = physicsConstraints["RightUpperArm_Swing"]
local leftArmAV = physicsConstraints["LeftUpperArm_Swing"]
if rightArmAV then rightArmAV.AngularVelocity = rightArmAV.AngularVelocity * 0.5 end -- Dampen quickly
if leftArmAV then leftArmAV.AngularVelocity = leftArmAV.AngularVelocity * 0.5 end
end
else
-- Something went wrong (foot missing?), cancel step
steppingFootName = nil
stepTargetPosition = nil
stepProgress = 0
isStumbling = false
end
updateTorsoCorrection(dt) -- Continue torso correction during step
return -- Don't evaluate new step while executing one
end
-- === STEP DECISION (If not currently stepping and cooldown is over) ===
if stepCooldown > 0 then
updateTorsoCorrection(dt)
return
end
local needsStepFB = math.abs(leanForward) > BALANCE_LeanThreshold_FB
local needsStepLR = math.abs(leanRight) > BALANCE_LeanThreshold_LR
if needsStepFB or needsStepLR then
-- Determine which foot to step with (usually the one opposite the last step)
local footToStepName = (lastSteppedFoot == "LeftFoot") and "RightFoot" or "LeftFoot"
local footToStepPart = character:FindFirstChild(footToStepName)
local stationaryFootPart = (footToStepName == "LeftFoot") and rightFoot or leftFoot
if not footToStepPart or not stationaryFootPart then return end -- Safety check
steppingFootName = footToStepName
stepProgress = 0
-- Calculate step direction and distance based on predicted lean
local stepDir = (forwardXZ * leanForward + rightXZ * leanRight).Unit
local stepDist = math.sqrt(leanForward^2 + leanRight^2) -- Magnitude of lean
local requiredStepLength = math.clamp((stepDist - math.min(BALANCE_LeanThreshold_FB, BALANCE_LeanThreshold_LR)) * BALANCE_StepDistanceFactor, 0.1, STEP_MAX_REACH)
-- Calculate base target position relative to the stationary foot
local targetXZ = (Vector3.new(stationaryFootPart.Position.X, 0, stationaryFootPart.Position.Z)
+ stepDir * requiredStepLength
+ rightXZ * ((footToStepName == "LeftFoot") and -1 or 1) * (BALANCE_StanceWidth / 2)) -- Adjust for stance width
-- Find ground height at target location
local targetGroundY = findGroundY(Vector3.new(targetXZ.X, com.Y, targetXZ.Z)) or groundY -- Use current ground if ray fails
stepTargetPosition = Vector3.new(targetXZ.X, targetGroundY + 0.1, targetXZ.Z) -- Target slightly above ground
-- Activate/Update the AlignPosition constraint for the step
local constraintName = steppingFootName .. "_BalanceStep"
local alignPos = physicsConstraints[constraintName]
if not alignPos then
alignPos = Instance.new("AlignPosition")
alignPos.Name = constraintName
alignPos.Mode = Enum.PositionAlignmentMode.OneAttachment
-- Create attachment at the foot's center for applying force
local stepAtt = createConstraintAttachment(footToStepPart, "BalanceStepAtt")
stepAtt.Position = Vector3.new(0, -footToStepPart.Size.Y/2, 0) -- Apply force lower? Experiment needed.
alignPos.Attachment0 = stepAtt
alignPos.MaxForce = BALANCE_StepForce
alignPos.ApplyAtCenterOfMass = false -- Apply force at attachment point
alignPos.Parent = footToStepPart
physicsConstraints[constraintName] = alignPos
end
alignPos.Responsiveness = BALANCE_StepResponsiveness -- Reset responsiveness at step start
alignPos.Position = footToStepPart.Position -- Start lerping from current pos
alignPos.Enabled = true
-- Set cooldown and potentially mark as stumbling if the lean was large
local leanMagnitude = predictedOffset.Magnitude
if leanMagnitude > (BALANCE_LeanThreshold_FB + BALANCE_LeanThreshold_LR) * 0.8 then -- Heuristic for large lean
isStumbling = true
stepCooldown = STUMBLE_COOLDOWN_TIME -- Shorter cooldown for potential follow-up step
else
isStumbling = false
stepCooldown = STEP_COOLDOWN_TIME
end
end
-- Always update torso correction, even when stable
updateTorsoCorrection(dt)
end
-- ============================================================
-- INITIALIZE RAGDOLL & BALANCING SYSTEM
-- ============================================================
local function makeRagdoll()
if isRagdolled then return end
isRagdolled = true
clearRagdollPhysics() -- Clear any previous physics state
saveOriginalCollisions() -- Re-save in case properties changed
local totalMass = 0
for _, part in pairs(character:GetDescendants()) do
if part:IsA("BasePart") and not originalCollisions[part].Massless then
totalMass = totalMass + part:GetMass()
-- Apply slightly higher density for stability? (Optional)
-- part.CustomPhysicalProperties = PhysicalProperties.new(1.2, 0.5, 0.5, 1, 1) -- Density, Friction, Elasticity, FrictionWeight, ElasticityWeight
end
end
totalMass = math.max(totalMass, 1) -- Avoid division by zero
-- Replace Motor6Ds with BallSocketConstraints
for _, joint in pairs(character:GetDescendants()) do
if joint:IsA("Motor6D") then
local part0 = joint.Part0
local part1 = joint.Part1
if part0 and part1 and part0.Parent and part1.Parent then -- Ensure parts exist
local a1 = createConstraintAttachment(part0, part0.Name .. "_" .. part1.Name .. "_A1")
a1.CFrame = joint.C0
local a2 = createConstraintAttachment(part1, part0.Name .. "_" .. part1.Name .. "_A2")
a2.CFrame = joint.C1
local constraint = Instance.new("BallSocketConstraint")
constraint.Name = joint.Name .. "_BSC"
constraint.Attachment0 = a1
constraint.Attachment1 = a2
constraint.LimitsEnabled = true
constraint.TwistLimitsEnabled = true
-- === Refined Joint Limits (Example - Adjust as needed!) ===
local partName = part1.Name:lower()
if partName:find("foot") or partName:find("hand") then
constraint.UpperAngle = 45
constraint.TwistLowerAngle = -15
constraint.TwistUpperAngle = 15
elseif partName:find("lower") and partName:find("leg") then -- Lower Leg (Knee)
constraint.UpperAngle = 80 -- More bend backward
constraint.TwistLowerAngle = -5
constraint.TwistUpperAngle = 5
elseif partName:find("upper") and partName:find("leg") then -- Upper Leg (Hip)
constraint.UpperAngle = 90 -- Wide range
constraint.TwistLowerAngle = -35
constraint.TwistUpperAngle = 35
elseif partName:find("lower") and partName:find("arm") then -- Lower Arm (Elbow)
constraint.UpperAngle = 85
constraint.TwistLowerAngle = -10
constraint.TwistUpperAngle = 10
elseif partName:find("upper") and partName:find("arm") then -- Upper Arm (Shoulder)
constraint.UpperAngle = 110 -- Very flexible
constraint.TwistLowerAngle = -80
constraint.TwistUpperAngle = 80
elseif partName:find("head") then
constraint.UpperAngle = 40
constraint.TwistLowerAngle = -30
constraint.TwistUpperAngle = 30
elseif partName:find("torso") then -- Connection between lower/upper torso
constraint.UpperAngle = 25
constraint.TwistLowerAngle = -20
constraint.TwistUpperAngle = 20
else -- Default fallback
constraint.UpperAngle = 45
constraint.TwistLowerAngle = -30
constraint.TwistUpperAngle = 30
end
constraint.Parent = part0 -- Parent constraint to one of the parts
table.insert(jointConstraints, { constraint = constraint, a1 = a1, a2 = a2 })
joint.Enabled = false -- Disable Motor6D instead of destroying, allows easier reset
end
end
end
-- Set physical properties for ragdoll parts
for part, data in pairs(originalCollisions) do
if part and part.Parent then
part.CanCollide = true
part.Massless = false -- Ensure all parts have mass
-- Set collision group to avoid self-collision if needed
-- physicsService:SetPartCollisionGroup(part, "Ragdoll")
if part == rootPart then
part.CanCollide = false -- HRP usually doesn't collide
end
end
end
-- physicsService:CreateCollisionGroup("Ragdoll")
-- physicsService:CollisionGroupSetCollidable("Ragdoll", "Ragdoll", false)
humanoid:ChangeState(Enum.HumanoidStateType.Physics) -- Enable physics control
-- === Create Physics Constraints for Balancing ===
-- Anti-Gravity Force (applied at COM for stability)
local antiGravAtt = createConstraintAttachment(rootPart, "AntiGravityAtt")
antiGravAtt.WorldPosition = calculateSmoothedCenterOfMass() -- Apply at COM
local standForce = Instance.new("VectorForce")
standForce.Name = "AntiGravityForce"
standForce.Attachment0 = antiGravAtt
standForce.RelativeTo = Enum.ActuatorRelativeTo.World
standForce.ApplyAtCenterOfMass = false -- Apply at the attachment point (COM)
standForce.Force = Vector3.new(0, totalMass * workspace.Gravity * ANTI_GRAVITY_MULTIPLIER, 0)
standForce.Parent = rootPart
physicsConstraints[standForce.Name] = standForce
-- Torso Alignment
if upperTorso then
local torsoAlignAtt = createConstraintAttachment(upperTorso, "TorsoAlignAtt")
local align = Instance.new("AlignOrientation")
align.Name = "UpperTorso_Align"
align.Attachment0 = torsoAlignAtt
align.Mode = Enum.OrientationAlignmentMode.OneAttachment
align.MaxTorque = 30000 -- Start with a reasonable torque
align.Responsiveness = 50 -- Start responsiveness
align.CFrame = CFrame.lookAt(Vector3.zero, rootPart.CFrame.LookVector, Vector3.yAxis) -- Initial target
align.Parent = upperTorso
physicsConstraints[align.Name] = align
end
-- Arm Swing Angular Velocity constraints
for _, armName in pairs({"LeftUpperArm", "RightUpperArm"}) do
local arm = character:FindFirstChild(armName)
if arm then
local swingAtt = createConstraintAttachment(arm, "SwingAtt")
local av = Instance.new("AngularVelocity")
av.Name = armName .. "_Swing"
av.Attachment0 = swingAtt
av.MaxTorque = 1500 -- Increased torque for more effective swings
av.RelativeTo = Enum.ActuatorRelativeTo.World -- Swing relative to world space based on lean
av.AngularVelocity = Vector3.new(0, 0, 0) -- Start still
av.Parent = arm
physicsConstraints[av.Name] = av
end
end
-- Initialize foot planting constraints (they will be enabled/disabled in the update loop)
updateFootPlanting()
-- Connect the main update loop
stepConnection = runService.Stepped:Connect(function(_, dt) -- Use the second argument (delta time)
if not isRagdolled then return end -- Exit if ragdoll was disabled mid-frame
-- Update attachment positions if needed (e.g., anti-gravity at COM)
antiGravAtt.WorldPosition = calculateSmoothedCenterOfMass()
standForce.Force = Vector3.new(0, totalMass * workspace.Gravity * ANTI_GRAVITY_MULTIPLIER, 0) -- Update force if mass changes (unlikely but safe)
updateFootPlanting()
updateActiveBalance(dt) -- Pass delta time
end)
end
-- ============================================================
-- RESET CHARACTER (Restore original state)
-- ============================================================
local function resetCharacter()
if not isRagdolled then return end
isRagdolled = false
clearRagdollPhysics() -- Destroy all constraints and attachments
-- Restore original Motor6D state
for _, joint in pairs(character:GetDescendants()) do
if joint:IsA("Motor6D") then
joint.Enabled = true
end
end
-- Restore original collision properties
for part, data in pairs(originalCollisions) do
if part and part.Parent then
part.CanCollide = data.CanCollide
part.Massless = data.Massless
part.CustomPhysicalProperties = data.CustomPhysicalProperties
-- physicsService:SetPartCollisionGroup(part, "Default") -- Reset collision group
-- Reset velocity/angular velocity
part.AssemblyLinearVelocity = Vector3.zero
part.AssemblyAngularVelocity = Vector3.zero
end
end
humanoid:ChangeState(Enum.HumanoidStateType.GettingUp) -- Trigger Roblox's standard recovery state
task.wait(0.1) -- Short delay to allow GettingUp state
if humanoid:GetState() ~= Enum.HumanoidStateType.Dead then
humanoid:ChangeState(Enum.HumanoidStateType.Running) -- Or Idle, depending on context
end
end
-- ============================================================
-- EVENT HANDLERS
-- ============================================================
humanoid.Died:Connect(function()
if not isRagdolled then
task.wait(0.05) -- Short delay before ragdolling on death
makeRagdoll()
end
-- Optionally disable balancing updates on death
-- if stepConnection then stepConnection:Disconnect() stepConnection = nil end
end)
-- Toggle Ragdoll/Balancing with 'R' key
userInputService.InputBegan:Connect(function(input, gameProcessed)
if not gameProcessed and input.UserInputType == Enum.UserInputType.Keyboard then
if input.KeyCode == Enum.KeyCode.R and humanoid.Health > 0 then
if isRagdolled then
resetCharacter()
else
makeRagdoll()
end
end
end
end)
-- Clean up when the script is removed
script.Destroying:Connect(function()
resetCharacter() -- Attempt to reset state on removal
end)
-- Initial state (optional - start ragdolled or standing)
-- makeRagdoll() -- Uncomment to start in ragdoll mode
woah dude, thats really good!, if you fix so it doesnt step on its other leg then it would be really good