Fix Ragdoll Physics Jank
Ragdolls that explode, clip through floors, or vibrate endlessly. Here are the three settings you're probably missing.
Why ragdolls explode
When you switch from an AnimationPlayer-driven skeleton to ragdoll physics, the RigidBody nodes suddenly inherit positions that might already be overlapping. Godot's physics engine resolves these overlaps by pushing bodies apart — hard. The result: limbs flying in random directions.
The fix comes down to three things: joint limits, collision layers, and damping.
Fix 1: Joint limits
Unconstrained joints are the #1 cause of ragdoll explosions. Each joint needs realistic angle limits so limbs can't rotate 360°.
# Configure joint limits for a humanoid ragdoll
# Call this after building your ragdoll skeleton
func setup_joint_limits(skeleton: Skeleton2D) -> void:
# Typical human joint limits (in radians)
var joint_limits := {
"neck": { "min": deg_to_rad(-40), "max": deg_to_rad(40) },
"shoulder_l": { "min": deg_to_rad(-90), "max": deg_to_rad(160) },
"shoulder_r": { "min": deg_to_rad(-160), "max": deg_to_rad(90) },
"elbow_l": { "min": deg_to_rad(0), "max": deg_to_rad(145) },
"elbow_r": { "min": deg_to_rad(-145), "max": deg_to_rad(0) },
"hip_l": { "min": deg_to_rad(-30), "max": deg_to_rad(120) },
"hip_r": { "min": deg_to_rad(-120), "max": deg_to_rad(30) },
"knee_l": { "min": deg_to_rad(-140), "max": deg_to_rad(0) },
"knee_r": { "min": deg_to_rad(0), "max": deg_to_rad(140) },
}
for joint_name in joint_limits:
var joint := find_joint(skeleton, joint_name)
if joint and joint is PinJoint2D:
# Enable angular limit
joint.angular_limit_enabled = true
joint.angular_limit_lower = joint_limits[joint_name]["min"]
joint.angular_limit_upper = joint_limits[joint_name]["max"]
# Add softness to prevent rigid snapping
joint.angular_limit_softness = 0.8Fix 2: Collision layers
By default, ragdoll limbs collide with each other. The upper arm hits the torso, the torso hits the legs, everything pushes everything, and you get jitter city. The fix: put ragdoll limbs on their own collision layer, and make them collide with the world but not with each other.
# Collision layer setup:
# Layer 1 = World geometry (floors, walls)
# Layer 2 = Player character
# Layer 3 = Ragdoll limbs
# Layer 4 = Enemies
func setup_ragdoll_collision(limbs: Array[RigidBody2D]) -> void:
for limb in limbs:
# Put on ragdoll layer (3)
limb.collision_layer = 0
limb.set_collision_layer_value(3, true)
# Collide with world (1) only — NOT other ragdoll parts
limb.collision_mask = 0
limb.set_collision_mask_value(1, true)
# Optional: also collide with enemies for fun interactions
# limb.set_collision_mask_value(4, true)
print("Ragdoll limbs: layer 3, mask 1 (world only)")If limbs still clip through the floor, increase the contact_monitor count on your RigidBody2D and enable continuous_cd (continuous collision detection). CCD catches fast-moving bodies that would otherwise tunnel through thin geometry.
Fix 3: Damping
Without damping, ragdoll limbs swing forever like pendulums. Adding angular and linear damping gives them the “weight” that makes ragdolls feel physical instead of robotic.
func apply_ragdoll_damping(limbs: Array[RigidBody2D]) -> void:
for limb in limbs:
# Linear damping — slows down movement through space
# 0 = no damping, 5-10 = good for ragdolls
limb.linear_damp = 3.0
# Angular damping — slows down rotation
# Higher = limbs stop spinning sooner
limb.angular_damp = 8.0
# Heavier limbs (torso) need more damping
if "torso" in limb.name.to_lower():
limb.linear_damp = 5.0
limb.angular_damp = 12.0
limb.mass = 3.0
elif "head" in limb.name.to_lower():
limb.mass = 1.5
else:
limb.mass = 1.0Putting it together: the activation script
Here's how to cleanly switch from animated skeleton to ragdoll on death, with a one-frame physics settle to prevent the initial explosion:
extends Node2D
@export var skeleton: Skeleton2D
@export var animation_player: AnimationPlayer
var _ragdoll_limbs: Array[RigidBody2D] = []
var _is_ragdoll := false
func _ready() -> void:
# Collect all RigidBody2D limbs
for child in skeleton.get_children():
if child is RigidBody2D:
_ragdoll_limbs.append(child)
child.freeze = true # Start frozen (animated)
func activate_ragdoll() -> void:
if _is_ragdoll:
return
_is_ragdoll = true
animation_player.stop()
# Apply the three fixes BEFORE unfreezing
setup_joint_limits(skeleton)
setup_ragdoll_collision(_ragdoll_limbs)
apply_ragdoll_damping(_ragdoll_limbs)
# Unfreeze all limbs
for limb in _ragdoll_limbs:
limb.freeze = false
# Apply a small impulse in the hit direction for satisfying feel
limb.apply_central_impulse(Vector2(randf_range(-20, 20), -80))TL;DR checklist
Want the full video walkthrough?
The complete “Fix Physics Ragdoll Jank” course covers 3D ragdolls, PhysicalBone3D setup, hit reactions, death animations blending into ragdoll, and advanced joint configurations. 15 minutes, $4.99.
Get the Full Course →