The jMonkeyEngine3 has built-in support for jBullet physics via the com.jme3.bullet
package. Physics are not only responsible for handing collisions, but they also make hinges and joints possible. One special example of physical joints are ragdoll physics, shown here.
The ragdoll is a simple dummy that we build out of cylinder collision shapes. It has 11 limbs: shoulders, a body, and hips; plus 2 arms and 2 legs are made up of two limbs each. In your game, you replace the cylinders with your own limb models.
Since we're just creating the ragdoll for this example, all the limbs have the same shape, and we can write a simple helper method to create them. The function returns a PhysicsNode with CollisionShape with the width, height, location, and rotation (vertical or horizontal) that we specify. We choose a CapsuleCollisionShape (a cylinder with rounded top and bottom) so the limbs collide smoothly against one another.
private Node createLimb(float width, float height, Vector3f location, boolean rotate) { int axis = rotate ? PhysicsSpace.AXIS_X : PhysicsSpace.AXIS_Y; CapsuleCollisionShape shape = new CapsuleCollisionShape(width, height, axis); Node node = new Node("Limb"); RigidBodyControl rigidBodyControl = new RigidBodyControl(shape, 1); node.setLocalTranslation(location); node.addControl(rigidBodyControl); return node; }
We use this helper method to initialize the 11 limbs. Look at the screenshot above for orientation.
Node shoulders = createLimb(0.2f, 1.0f, new Vector3f( 0.00f, 1.5f, 0), true); Node uArmL = createLimb(0.2f, 0.5f, new Vector3f(-0.75f, 0.8f, 0), false); Node uArmR = createLimb(0.2f, 0.5f, new Vector3f( 0.75f, 0.8f, 0), false); Node lArmL = createLimb(0.2f, 0.5f, new Vector3f(-0.75f,-0.2f, 0), false); Node lArmR = createLimb(0.2f, 0.5f, new Vector3f( 0.75f,-0.2f, 0), false); Node body = createLimb(0.2f, 1.0f, new Vector3f( 0.00f, 0.5f, 0), false); Node hips = createLimb(0.2f, 0.5f, new Vector3f( 0.00f,-0.5f, 0), true); Node uLegL = createLimb(0.2f, 0.5f, new Vector3f(-0.25f,-1.2f, 0), false); Node uLegR = createLimb(0.2f, 0.5f, new Vector3f( 0.25f,-1.2f, 0), false); Node lLegL = createLimb(0.2f, 0.5f, new Vector3f(-0.25f,-2.2f, 0), false); Node lLegR = createLimb(0.2f, 0.5f, new Vector3f( 0.25f,-2.2f, 0), false);
We now have the outline of a person. But if we ran the application now, the individual limbs would fall down independently of one another – the ragdoll is still lacking joints.
As before, we write a small helper method. This time its purpose is to quickly join two limbs A and B at the connection point that we specify.
private PhysicsJoint join(Node A, Node B, Vector3f connectionPoint) { Vector3f pivotA = A.worldToLocal(connectionPoint, new Vector3f()); Vector3f pivotB = B.worldToLocal(connectionPoint, new Vector3f()); ConeJoint joint = new ConeJoint(A.getControl(RigidBodyControl.class), B.getControl(RigidBodyControl.class), pivotA, pivotB); joint.setLimit(1f, 1f, 0); return joint; }
We use the helper method to connect all limbs with joints where they belong, at one end of the limb.
join(body, shoulders, new Vector3f( 0.00f, 1.4f, 0)); join(body, hips, new Vector3f( 0.00f, -0.5f, 0)); join(uArmL, shoulders, new Vector3f(-0.75f, 1.4f, 0)); join(uArmR, shoulders, new Vector3f( 0.75f, 1.4f, 0)); join(uArmL, lArmL, new Vector3f(-0.75f, 0.4f, 0)); join(uArmR, lArmR, new Vector3f( 0.75f, 0.4f, 0)); join(uLegL, hips, new Vector3f(-0.25f, -0.5f, 0)); join(uLegR, hips, new Vector3f( 0.25f, -0.5f, 0)); join(uLegL, lLegL, new Vector3f(-0.25f, -1.7f, 0)); join(uLegR, lLegR, new Vector3f( 0.25f, -1.7f, 0));
Now the ragdoll is connected. If we ran the app now, the doll would collapse, but the limbs would stay together.
We create one (non-physical) Node named ragDoll, and attach all other nodes to it.
ragDoll.attachChild(shoulders); ragDoll.attachChild(body); ragDoll.attachChild(hips); ragDoll.attachChild(uArmL); ragDoll.attachChild(uArmR); ragDoll.attachChild(lArmL); ragDoll.attachChild(lArmR); ragDoll.attachChild(uLegL); ragDoll.attachChild(uLegR); ragDoll.attachChild(lLegL); ragDoll.attachChild(lLegR);
To use the ragdoll in a scene, we attach its main node to the rootNode, and to the PhysicsSpace.
rootNode.attachChild(ragDoll); bulletAppState.getPhysicsSpace().addAll(ragDoll);
To pull the doll up, you could add an input handler that triggers the following action:
Vector3f upforce = new Vector3f(0, 200, 0); shoulders.applyContinuousForce(true, upforce);
We can use the action to pick the doll up and put it back on its feet, or what ever. Read more about Forces here.
Read the Responding to a PhysicsCollisionEvent chapter in the general physics documentation on how to detect collisions.
If you are seeing weird behaviour in a ragdoll – such as exploding into pieces and then reassembling – check your collision shapes. Verify you did not position the limbs too close to one another when assmebling the ragdoll. You typically see physical nodes being ejected when their collision shapes intersect, which puts physics in an impossible state.