hello. Im struggling with BetterCharacterControl jumping for no reason on plain surface (made of tiles 3x3 meters wide in jmonkey, spaced 3 meters from each other so they make a board). here is the code player + camera code:
public class StartGameState extends AbstractAppState{
private final Node rootNode;
private final Node localRootNode = new Node("GameStart");
private final AssetManager assetManager;
private final InputManager inputManager;
Node playerNode;
Node playerLookAtNode;
CameraNode camNode;
public BulletAppState bulletAppState;
private final FlyByCamera flyCam;
private final Camera cam;
private BetterCharacterControl playerCharacterControl;
boolean forward = false;
boolean backward = false;
boolean left = false;
boolean right = false;
private ChaseCamera chaseCam;
private Vector3f playerWalkDirection;
public StartGameState(SimpleApplication app){
rootNode = app.getRootNode();
assetManager = app.getAssetManager();
inputManager = app.getInputManager();
flyCam = app.getFlyByCamera();
cam = app.getCamera();
}
@Override
public void initialize(AppStateManager stateManager, Application app){
super.initialize(stateManager, app);
rootNode.attachChild(localRootNode);
bulletAppState = new BulletAppState();
bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
bulletAppState.setDebugEnabled(true);
stateManager.attach(bulletAppState);
initPlayer();
initKeys();
DirectionalLight sun = new DirectionalLight();
sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
rootNode.addLight(sun);
}
public void initKeys(){
inputManager.addMapping("RotateCameraRight", new MouseAxisTrigger(MouseInput.AXIS_X,true));
inputManager.addMapping("RotateCameraLeft", new MouseAxisTrigger(MouseInput.AXIS_X,false));
inputManager.addMapping("RotateCameraUp", new MouseAxisTrigger(MouseInput.AXIS_Y,true));
inputManager.addMapping("RotateCameraDown", new MouseAxisTrigger(MouseInput.AXIS_Y,false));
inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("Backward", new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("test", new KeyTrigger(KeyInput.KEY_K));
inputManager.addListener(actionListener, "Forward");
inputManager.addListener(actionListener, "Backward");
inputManager.addListener(actionListener, "Left");
inputManager.addListener(actionListener, "Right");
inputManager.addListener(actionListener, "test");
}
public void initPlayer(){
playerCharacterControl = new BetterCharacterControl(0.35f,1.8f,1f);
flyCam.setEnabled(false);
playerNode = new Node();
playerCharacterControl.setJumpForce(new Vector3f(0,2.5f,0));
getPhysicsSpace().add(playerCharacterControl);
getPhysicsSpace().addAll(playerNode);
playerCharacterControl.setGravity(new Vector3f(0,-5,0));
playerNode.addControl(playerCharacterControl);
playerCharacterControl.warp(new Vector3f(0,0,0));
playerLookAtNode = new Node();
playerNode.attachChild(playerLookAtNode);
playerLookAtNode.move(0,1.5f,0);
rootNode.attachChild(playerNode);
flyCam.setEnabled(true);
camNode = new CameraNode("Camera Node", cam);
camNode.setControlDir(ControlDirection.SpatialToCamera);
playerNode.attachChild(camNode);
camNode.setLocalTranslation(new Vector3f(0, 1.5f, 0));
camNode.lookAt(playerLookAtNode.getLocalTranslation(), Vector3f.UNIT_Y);
inputManager.setCursorVisible(false);
}
private final ActionListener actionListener = new ActionListener() {
@Override
public void onAction(String name, boolean keyPressed, float tpf) {
if(name.equals("test")){
System.out.print(inputManager.getCursorPosition().x);
camNode.setEnabled(false);
}
if(name.equals("Forward")){
if(keyPressed){
forward = true;
}else {
forward = false;
}
}
if(name.equals("Backward")){
if(keyPressed){
backward = true;
}else {
backward = false;
}
}
if(name.equals("Left")){
if(keyPressed){
left = true;
}else {
left = false;
}
}
if(name.equals("Right")){
if(keyPressed){
right = true;
}else {
right = false;
}
}
}
};
private final AnalogListener analogListener = new AnalogListener() {
@Override
public void onAnalog(String name, float value, float tpf) {
}
};
@Override
public void cleanup(){
rootNode.detachChild(localRootNode);
super.cleanup();
}
@Override
public void update(float tpf) {
playerCharacterControl.setWalkDirection(new Vector3f(0,0,0));
playerWalkDirection = cam.getDirection();
playerWalkDirection.setY(0);
playerCharacterControl.setViewDirection(new Vector3f(playerWalkDirection));
if(forward){
playerCharacterControl.setWalkDirection(playerWalkDirection.mult(10));
} else if(backward){
playerCharacterControl.setWalkDirection(playerWalkDirection.mult(2).negate());
}
System.out.println(playerNode.getWorldTranslation());}}
and here is the terrain generation code:
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import static com.jme3.bullet.PhysicsSpace.getPhysicsSpace;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.input.ChaseCamera;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;
import java.util.ArrayList;
public class TerrainGenerationState extends AbstractAppState{
private final Node rootNode;
private final Node terrainRootNode = new Node("TerrainRootNode");
private ArrayList<String> tileModels = new ArrayList<String>();
private final AssetManager assetManager;
private BulletAppState bulletAppState;
private Spatial tileSpatial;
private RigidBodyControl tileRigidBody;
private Vector3f tileOffsetVector;
private final AppStateManager stateManager;
public TerrainGenerationState(SimpleApplication StartGameState){
rootNode = StartGameState.getRootNode();
assetManager = StartGameState.getAssetManager();
stateManager = StartGameState.getStateManager();
}
@Override
public void initialize(AppStateManager stateManager, Application app){
super.initialize(stateManager, app);
initTileModels();
initMap();
}
public void initTileModels(){
tileModels.add("Models/Floor1/Floor1.j3o");
}
public void initMap(){
for(int i=-5;i<=4;i++){
int k = 0; //arraylist index (the one containing model paths for terrain)
int r = -15; //row vector
tileOffsetVector = new Vector3f(3*i,-2,r);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+3);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+6);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+9);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+12);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+15);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+18);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+21);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+24);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
tileOffsetVector = new Vector3f(3*i,-2,r+27);
tileSpatial = assetManager.loadModel(tileModels.get(k));
tileSpatial.move(tileOffsetVector);
tileRigidBody = new RigidBodyControl(0);
tileSpatial.addControl(tileRigidBody);
getPhysicsSpace().add(tileSpatial);
rootNode.attachChild(tileSpatial);
}
}
@Override
public void cleanup(){
rootNode.detachChild(terrainRootNode);
super.cleanup();
}
@Override
public void update(float tpf) {}}
i am fairly sure i am missing something here but i am not sure what it might be, as the tiles are perfectly plain (they are 2d planes 3x3 meters in blender which is exactly 3x3 in jmonkey as far as i know), they are spaced 3 meters between each other (so they fit perfectly) and playerWalkDirection vector Y variable is always set to 0 before moving, so there is literally nothing to bounce off. Here is the playerNode (which betterCharacterControl has control over) translation, to show that this is precisely a matter of physics body bouncing:
the middle one is of course Y value.