Blending Ragdoll Physics with Keyframes in Matter.js
Combining keyframed bone animation with real-time physics creates dynamic, lifelike 2D character reactions such as hit impacts, stumbles, and natural transitions into death states. In Matter.js, this is achieved by pairing a skeletal hierarchy with an interconnected network of rigid bodies and constraints, then interpolating between animated keyframe targets and physics simulation using linear interpolation (LERP) and torque forces.
The Dual-Rig Architecture
To blend physics with keyframes, you must maintain two representations of your character simultaneously:
- The Kinematic Rig: An animated hierarchy of bones driven purely by your animation engine (e.g., Spine, DragonBones, or a custom matrix hierarchy) containing target positions, rotations, and scale values.
- The Physics Rig: A collection of
Matter.Bodiesrepresenting limbs, torso, and head, held together byMatter.Constraintjoints with defined angular and distance limits.
A global blend weight—typically called alpha, ranging
from 0.0 (fully keyframed) to 1.0 (fully
physics-driven)—determines which system controls the final rendered
character.
Step 1: Mapping Bones to Rigid Bodies
Model each bone as a simple geometric shape (typically
Matter.Bodies.rectangle) centered along the bone's axis.
Connect the bodies at their joint locations using
Matter.Constraint.create with a stiffness approaching
1.0 to preserve the skeletal structure without
stretching.
Ensure collision categories and masks are configured to prevent internal self-collision between adjacent limbs (such as an upper arm colliding with the chest), which can introduce severe instability into the simulation.
Step 2: Synchronizing States (Alpha = 0)
When the character is purely animated, the physics bodies must follow
the animation rather than dynamic forces. Do not simply teleport bodies
by setting body.position directly, as this breaks
Matter.js's velocity calculations. Instead, use:
Matter.Body.setPosition(body, targetPosition);
Matter.Body.setAngle(body, targetAngle);
Matter.Body.setVelocity(body, calculatedVelocity);
Matter.Body.setAngularVelocity(body, calculatedAngularVelocity);Set body.isSensor = true or selectively disable
collision filtering during pure animation if you do not want the
character interacting with world obstacles until an impact occurs.
Step 3: Active Ragdoll via Spring Forces (0 < Alpha < 1)
For partial impacts or "active ragdoll" behaviors, Matter.js bodies remain dynamic while forces pull them toward keyframed orientations. This is typically achieved using a Proportional-Derivative (PD) controller to compute torque:
// Calculate angular error between physics body and keyframe target
let angleError = targetBoneAngle - body.angle;
// Normalize angle error to the range [-PI, PI]
angleError = Math.atan2(Math.sin(angleError), Math.cos(angleError));
// PD controller parameters
const kP = 0.05 * (1 - blendWeight); // Proportional gain (stiffness)
const kD = 0.005; // Derivative gain (damping)
// Apply corrective torque
const torque = (angleError * kP) - (body.angularVelocity * kD);
Matter.Body.setAngularVelocity(body, body.angularVelocity + torque);By scaling kP with your blend factor, limbs will loosely
struggle to maintain their keyframed posture while still deflecting
realistically when hit by external forces.
Step 4: Full Ragdoll and Velocity Transfer (Alpha = 1)
When transitioning to a full ragdoll (such as a knockout or death), the keyframe animation stops updating the physics rig. Before releasing the rigid bodies to full gravity:
- Transfer the current animated linear and angular velocities to the physics bodies so momentum is preserved seamlessly.
- Remove any corrective spring torques.
- Invert the control direction: read the
positionandangleof eachMatter.Bodyand apply them directly to the visual skeleton's transformation matrices for rendering.
Step 5: Returning from Ragdoll to Keyframes
To stand the character back up, smoothly interpolate the visual bones from their resting physics positions toward a "get up" keyframe animation over a fixed duration (e.g., 0.5 seconds). Once the visual rig aligns with the starting frame of the get-up animation, snap the Matter.js bodies back to the bone transforms and resume kinematic tracking.