I was frustrated there was no updated example for the BetterCharacterControl in the wiki. After tinkering around with the control for a while I figured it out. I made some updates to the original hello collision examples. This is a very basic example of FPS style controls.
package mygame;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
/**
*
* @author august
* Basic FPS style character control using BetterCharacterControl
*/
public class Main extends SimpleApplication
implements ActionListener {
private Spatial sceneModel;
private BulletAppState bulletAppState;
private RigidBodyControl landscape;
// private CharacterControl player;
private BetterCharacterControl player;
private Vector3f walkDirection = new Vector3f();
private boolean left = false, right = false, up = false, down = false;
private Geometry PlayerModel;
//Temporary vectors used on each frame.
//They here to avoid instanciating new vectors on each frame
private Vector3f camDir = new Vector3f();
private Vector3f camLeft = new Vector3f();
// Our movement speed
private float speed;
private float strafeSpeed;
private float headHeight;
public static void main(String[] args) {
Main app = new Main();
app.start();
}
public void simpleInitApp() {
// set player speed
speed = 6f;
strafeSpeed = 4f;
headHeight = 3f;
/** Create a box to use as our player model */
Box box1 = new Box(1,1,1);
PlayerModel = new Geometry("Box", box1);
Material mat = new Material(assetManager,
"Common/MatDefs/Misc/Unshaded.j3md"); // create a simple material
mat.setColor("Color", ColorRGBA.Blue); // set color of material to blue
PlayerModel.setMaterial(mat);
PlayerModel.setLocalTranslation(new Vector3f(0,6,0));
PlayerModel.setLocalTranslation(new Vector3f(0,6,0));
rootNode.attachChild(PlayerModel);
cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 1000f);
cam.setLocation(new Vector3f(0,6,0));
/** Set up Physics */
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
// We re-use the flyby camera for rotation, while positioning is handled by physics
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
setUpKeys();
setUpLight();
sceneModel = assetManager.loadModel("Scenes/farm.j3o");
// We set up collision detection for the scene by creating a
// compound collision shape and a static RigidBodyControl with mass zero.
CollisionShape sceneShape =
CollisionShapeFactory.createMeshShape((Node) sceneModel);
landscape = new RigidBodyControl(sceneShape, 0);
sceneModel.addControl(landscape);
// create character control parameters (Radius,Height,Weight)
// Radius and Height determine the size of the collision bubble
// Weight determines how much gravity effects the control
player = new BetterCharacterControl(2f,6f,1f);
// set basic physical properties:
player.setJumpForce(new Vector3f(0,5f,0));
player.setGravity(new Vector3f(0,1f,0));
player.warp(new Vector3f(0,6,0));
// We attach the scene and the player to the rootnode and the physics space,
// to make them appear in the game world.
rootNode.attachChild(sceneModel);
bulletAppState.getPhysicsSpace().add(landscape);
bulletAppState.getPhysicsSpace().add(player);
/*
* Add player control to the box. The box will act as our player model
* while the camera follows it
*/
PlayerModel.addControl(player);
}
private void setUpLight() {
// We add light so we see the scene
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(1.3f));
rootNode.addLight(al);
DirectionalLight dl = new DirectionalLight();
dl.setColor(ColorRGBA.White);
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
rootNode.addLight(dl);
}
/** We over-write some navigational key mappings here, so we can
* add physics-controlled walking and jumping: */
private void setUpKeys() {
inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addListener(this, "Left");
inputManager.addListener(this, "Right");
inputManager.addListener(this, "Up");
inputManager.addListener(this, "Down");
inputManager.addListener(this, "Jump");
}
/** These are our custom actions triggered by key presses.
* We do not walk yet, we just keep track of the direction the user pressed. */
public void onAction(String binding, boolean isPressed, float tpf) {
if (binding.equals("Left")) {
left = isPressed;
} else if (binding.equals("Right")) {
right= isPressed;
} else if (binding.equals("Up")) {
up = isPressed;
} else if (binding.equals("Down")) {
down = isPressed;
} else if (binding.equals("Jump")) {
if (isPressed) { player.jump(); }
}
}
/**
* This is the main event loop--walking happens here.
* We check in which direction the player is walking by interpreting
* the camera direction forward (camDir) and to the side (camLeft).
* The setWalkDirection() command is what lets a physics-controlled player walk.
* We also make sure here that the camera moves with player.
*/
@Override
public void simpleUpdate(float tpf) {
/*
* The direction of character is determined by the camera angle
* the Y direction is set to zero to keep our character from
* lifting of terrain. For free flying games simply ad speed
* to Y axis
*/
camDir.set(cam.getDirection()).multLocal(speed, 0.0f, speed);
camLeft.set(cam.getLeft()).multLocal(strafeSpeed);
walkDirection.set(0, 0, 0);
if (left) {
walkDirection.addLocal(camLeft);
}
if (right) {
walkDirection.addLocal(camLeft.negate());
}
if (up) {
walkDirection.addLocal(camDir);
}
if (down) {
walkDirection.addLocal(camDir.negate());
}
player.setWalkDirection(walkDirection);
/*
* By default the location of the box is on the bottom of the terrain
* we make a slight offset to adjust for head height.
*/
cam.setLocation(new Vector3f(PlayerModel.getLocalTranslation().x,PlayerModel.getLocalTranslation().y + headHeight,PlayerModel.getLocalTranslation().z));
}
}