It's outlined here: http://www.ogre3d.org/wiki/index.php/OgreOde_Walking_Character
This code was directly ripped from my "CharacterEntity" class which I am using for a new project. Do note that it was designed for a 2.5D platformer and could require some changes to fit in a full 3D environment.
The basic idea is this, you have a physics sphere, which is used for the feet, and a cylinder on top of it which is attached with a joint. The cylinder is kept on top of the sphere all the time and is not allowed to fall over- this part is handled by user code. The feet have an additional joint with axis attached, the axis is used as a motor to cause the character to move forward & backward (e.g the sphere rolls). Jumping is done by simply applying an upward force.
This is the character material. Density should be balanced to prevent character from flying too far, but not too high at the same time to prevent large forces. Bouncing should be disabled in most cases. Mu should be low enough to prevent character from sliding, but not high enough to prevent walking on walls.
protected static Material characterMaterial = null;
static {
characterMaterial = new Material( "Character Material" );
characterMaterial.setDensity( 1f );
MutableContactInfo contactDetails = new MutableContactInfo();
contactDetails.setBounce(0);
contactDetails.setMu(1);
contactDetails.setMuOrthogonal(1);
contactDetails.setDampingCoefficient(10);
characterMaterial.putContactHandlingDetails( Material.DEFAULT, contactDetails );
}
Do note that some of this code may require changes to fit your application.
As you can see, this method detects large forces applied on the character if you want to cause damage to it in that case. It also detects when the character enters the ground; make sure to set onGround to false before updating the physics state.
public void createCharacterPhysics(){
// here we compute the radius of a virtual cylinder
// that would totally contain the character
BoundingBox bbox = (BoundingBox) model.getWorldBound();
float cRadius = bbox.xExtent > bbox.zExtent ? bbox.zExtent : bbox.xExtent;
float cHeight = bbox.yExtent * 2f;
logger.info("ENTITY created. " + charNode.getName() + "R: "+cRadius+", H: "+cHeight);
PhysicsSpace physicsSpace = Level.getPhysSpace();
// create the feet physics object
// it's a sphere on the floor
feetNode = physicsSpace.createDynamicNode();
PhysicsSphere feetGeom = feetNode.createSphere("Feet");
feetGeom.setLocalScale(cRadius);
feetNode.setMaterial(characterMaterial);
feetNode.computeMass();
// I don't know what this does
model.setLocalTranslation(0, -cRadius / model.getLocalScale().y, 0);
charNode.attachChild(feetNode);
feetNode.setUserData("Entity", new EntitySavable(this));
physNode = physicsSpace.createDynamicNode();
physNode.setAffectedByGravity(false);
PhysicsCapsule torsoGeom = physNode.createCapsule("Torso");
torsoGeom.setLocalScale(new Vector3f(cRadius, cHeight-4*cRadius, cRadius));
torsoGeom.setLocalTranslation(0, cHeight - ((torsoGeom.getLocalScale().y)/2f+2f*cRadius), 0);
Quaternion rot = new Quaternion();
rot.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X);
torsoGeom.setLocalRotation(rot);
physNode.computeMass();
// NOTE: the model is attached to the torso physics, not feet
physNode.attachChild(model);
charNode.attachChild(physNode);
physNode.setUserData("Entity", new EntitySavable(this));
// create a joint to keep the capsule locked to the rolling sphere
Joint joint = physicsSpace.createJoint();
JointAxis axis = joint.createRotationalAxis();
axis.setDirection(Vector3f.UNIT_X);
joint.attach(physNode, feetNode);
feetWalkAxis = joint.createRotationalAxis();
feetWalkAxis.setDirection(new Vector3f( 0, 0, 1 ));
feetWalkAxis.setAvailableAcceleration(10);
feetWalkAxis.setRelativeToSecondObject(true);
SyntheticButton collButton = feetNode.getCollisionEventHandler();
contactDetect.addAction( new InputAction() {
@Override
public void performAction( InputActionEvent evt ) {
ContactInfo contactInfo = (ContactInfo) evt.getTriggerData();
Vector3f vec = contactInfo.getContactNormal(null);
if (vec.dot(Vector3f.UNIT_Y) > 0.5f){
onGround = true;
}
Vector3f vel = contactInfo.getContactVelocity(null);
if (vel.length() > 10){
System.out.println("POWERFUL CONTACT: "+vel.length());
}
}
}, collButton, false );
}
This method should be called every frame to prevent the character from tripping over.
private void preventFall(){
Quaternion q = physNode.getLocalRotation();
Vector3f[] axes = new Vector3f[3];
q.toAxes(axes);
q.fromAxes(axes[0], Vector3f.UNIT_Y, axes[2]);
physNode.setLocalRotation(q);
physNode.setAngularVelocity(Vector3f.ZERO);
physNode.updateWorldVectors();
}
Don't remember what this does. It's possible its for 2D physics only.
private void resetFeetRotation(){
Quaternion q = feetNode.getLocalRotation();
Vector3f[] axes = new Vector3f[3];
q.toAxes(axes);
q.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, axes[2]);
feetNode.setLocalRotation(q);
}
Here's a good update method
public void update(float tpf){
if (!allowFall){
preventFall();
}
resetFeetRotation();
super.update(tpf);
onGround = false;
contactDetect.update(tpf);
}
Jumping is dead simple. You may want to allow the character to nudge left or right during the jump for platforming-style physics.
if (onGround && scale > 0f){
feetNode.addForce(new Vector3f(0, 500f * scale, 0f));
}
Finally this is how you would make your character walk.
if (scale == 0){
feetWalkAxis.setDesiredVelocity(0);
}else{
float desired = scale * maxRun;
float accel = ((2f * desired) / walkupTime);
feetWalkAxis.setAvailableAcceleration( accel );
feetWalkAxis.setDesiredVelocity( desired * direction.getDirection().x );
}