PhysicsRigidBodyGodot 45 min read

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°.

ragdoll_setup.gd
# 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.8

Fix 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_layers.gd
# 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)")
⚡ Pro tip

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.

ragdoll_damping.gd
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.0

Putting 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:

ragdoll_activator.gd
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

[01]Set joint angle limits (don't let elbows bend backwards)
[02]Put ragdoll limbs on their own collision layer
[03]Make limbs collide with world only, NOT each other
[04]Add linear_damp (3-5) and angular_damp (8-12)
[05]Give torso more mass than extremities
[06]Enable continuous_cd for fast-moving bodies
[07]Freeze limbs until ragdoll activation

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 →