I’m having an issue with better character control. The control behaves fine until I go onto an uneven surface. When I travel up a ramp everything is okay until I stop. As soon as I let go of the movement key my character bounces as if it is being slammed into the surface. This only occurs on uneven surfaces. Flat surfaces do not produce this behavior.
I tried adjusting the character weight and gravity but it has no effect on this behavior.
Here is the test code.
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;
import com.jme3.util.TangentBinormalGenerator;
/**
*
* @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));
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("Models/stairwell/stairwell.j3o");
// We set up collision detection for the scene by creating a
// compound collision shape and a static RigidBodyControl with mass zero.
// TangentBinormalGenerator.generate(sceneModel);
// bulletAppState.setDebugEnabled(true);
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,80);
// set basic physical properties:
player.setJumpForce(new Vector3f(0,5f,0));
player.setGravity(new Vector3f(0,1,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));
}
}