Character falls at different speed

Hey everyone. I’m having a bit of a problem using the CharacterControl class: when I jump I fall at my specified speed, but when I just fall off a ledge I fall at a completely different speed. Here’s a video to show it:
[video]http://youtu.be/scG2QGao0FQ[/video]

This is my code to how I’m setting up the character:
[java]CapsuleCollisionShape collision = new CapsuleCollisionShape(0.5f,1f);
character = new CharacterControl(collision, 0.5f);
character.setJumpSpeed(10);
character.setFallSpeed(1);[/java]

I don’t change any variables after that. Any ideas?

Is it the same with native bullet? Can you make a test case?

I tried it with the native bullet library; it worked correctly at first, but then after I jumped it stopped working correctly.

Here’s my test case (quick modification of TestPhysicsCharacter):
[java]/*

  • Copyright © 2009-2012 jMonkeyEngine All rights reserved.
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are met:
    • Redistributions of source code must retain the above copyright notice,
  • this list of conditions and the following disclaimer. * Redistributions
  • in binary form must reproduce the above copyright notice, this list of
  • conditions and the following disclaimer in the documentation and/or other
  • materials provided with the distribution. * Neither the name of
  • ‘jMonkeyEngine’ nor the names of its contributors may be used to endorse or
  • promote products derived from this software without specific prior written
  • permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  • CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
  • NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  • PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  • OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  • WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  • OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  • ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */
    package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;

/**

  • A walking physical character followed by a 3rd person camera. (No animation.)
  • @author normenhansen, zathras
    */
    public class TestCase extends SimpleApplication implements ActionListener {

private BulletAppState bulletAppState;
private CharacterControl physicsCharacter;
private Node characterNode;
private CameraNode camNode;
boolean rotate = false;
private Vector3f walkDirection = new Vector3f(0,0,0);
private Vector3f viewDirection = new Vector3f(0,0,0);
boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false,
leftRotate = false, rightRotate = false;

public static void main(String[] args) {
TestCase app = new TestCase();
app.start();
}

private void setupKeys() {
    inputManager.addMapping("Strafe Left",
            new KeyTrigger(KeyInput.KEY_Q),
            new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addMapping("Strafe Right",
            new KeyTrigger(KeyInput.KEY_E),
            new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping("Rotate Left",
            new KeyTrigger(KeyInput.KEY_A),
            new KeyTrigger(KeyInput.KEY_LEFT));
    inputManager.addMapping("Rotate Right",
            new KeyTrigger(KeyInput.KEY_D),
            new KeyTrigger(KeyInput.KEY_RIGHT));
    inputManager.addMapping("Walk Forward",
            new KeyTrigger(KeyInput.KEY_W),
            new KeyTrigger(KeyInput.KEY_UP));
    inputManager.addMapping("Walk Backward",
            new KeyTrigger(KeyInput.KEY_S),
            new KeyTrigger(KeyInput.KEY_DOWN));
    inputManager.addMapping("Reset",
            new KeyTrigger(KeyInput.KEY_RETURN));
    inputManager.addMapping("Jump",
            new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Shoot",
            new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(this, "Strafe Left", "Strafe Right");
    inputManager.addListener(this, "Rotate Left", "Rotate Right");
    inputManager.addListener(this, "Walk Forward", "Walk Backward");
    inputManager.addListener(this, "Jump", "Reset");
}

@Override
public void simpleInitApp() {
// activate physics
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);

setupWorld();
setupKeys();

// Add a physics character to the world
physicsCharacter = new CharacterControl(new CapsuleCollisionShape(0.5f, 1.8f), 0.5f);
physicsCharacter.setFallSpeed(1);
physicsCharacter.setJumpSpeed(10);
characterNode = new Node("character node");
Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
model.scale(0.25f);
characterNode.addControl(physicsCharacter);
getPhysicsSpace().add(physicsCharacter);
rootNode.attachChild(characterNode);
characterNode.attachChild(model);

physicsCharacter.setPhysicsLocation(new Vector3f(0, 25, 0));
// set forward camera node that follows the character
ChaseCamera chase = new ChaseCamera(cam, model, inputManager);

//disable the default 1st-person flyCam (don't forget this!!)
flyCam.setEnabled(false);

}

private void setupWorld(){
Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal();
DirectionalLight dl = new DirectionalLight();
dl.setDirection(direction);
dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
rootNode.addLight(dl);

    Material stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
    key2.setGenerateMips(true);
    Texture tex2 = assetManager.loadTexture(key2);
    stone_mat.setTexture("ColorMap", tex2);
    
    Box b = new Box(5,1, 5);
    Geometry world = new Geometry("world", b);
    world.setMaterial(stone_mat);
    RigidBodyControl rbc = new RigidBodyControl(CollisionShapeFactory.createMeshShape(world), 0);
    rbc.setPhysicsLocation(new Vector3f(0,-25,0));
    world.addControl(rbc);
    rootNode.attachChild(world);
    getPhysicsSpace().add(rbc);

}

@Override
public void simpleUpdate(float tpf) {
Vector3f camDir = cam.getDirection().mult(0.2f);
Vector3f camLeft = cam.getLeft().mult(0.2f);
camDir.y = 0;
camLeft.y = 0;
viewDirection.set(camDir);
walkDirection.set(0, 0, 0);
if (leftStrafe) {
walkDirection.addLocal(camLeft);
} else
if (rightStrafe) {
walkDirection.addLocal(camLeft.negate());
}
if (leftRotate) {
viewDirection.addLocal(camLeft.mult(0.02f));
} else
if (rightRotate) {
viewDirection.addLocal(camLeft.mult(0.02f).negate());
}
if (forward) {
walkDirection.addLocal(camDir);
} else
if (backward) {
walkDirection.addLocal(camDir.negate());
}
physicsCharacter.setWalkDirection(walkDirection);
physicsCharacter.setViewDirection(viewDirection);
}

public void onAction(String binding, boolean value, float tpf) {
    if (binding.equals("Strafe Left")) {
        if (value) {
            leftStrafe = true;
        } else {
            leftStrafe = false;
        }
    } else if (binding.equals("Strafe Right")) {
        if (value) {
            rightStrafe = true;
        } else {
            rightStrafe = false;
        }
    } else if (binding.equals("Rotate Left")) {
        if (value) {
            leftRotate = true;
        } else {
            leftRotate = false;
        }
    } else if (binding.equals("Rotate Right")) {
        if (value) {
            rightRotate = true;
        } else {
            rightRotate = false;
        }
    } else if (binding.equals("Walk Forward")) {
        if (value) {
            forward = true;
        } else {
            forward = false;
        }
    } else if (binding.equals("Walk Backward")) {
        if (value) {
            backward = true;
        } else {
            backward = false;
        }
    } else if (binding.equals("Reset")) {
        physicsCharacter.warp(new Vector3f(0,25,0));
    }else if (binding.equals("Jump")) {
        physicsCharacter.jump();
    }
}

private PhysicsSpace getPhysicsSpace() {
return bulletAppState.getPhysicsSpace();
}

@Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
}
[/java]
(Compare normal fall speed, jump fall speed, then fall speed of just walking off platform)
Hope that helps. Thanks normen!

Thanks, I can’t run tests atm (Mac / lwjgl / shit) but from what you say it sounds like this issue is “just there” in bullet, so you might want to look into workaround or how the bug actually articulates itself. Meaning play with the values and see if theres a set that is working (after all some programmer thought it works) and/or try to set the values when you actually jump/change states based on the other info (is flying etc).

1 Like

It seems the stepHeight in the constructor seems to be the major factor here… The lower it is, the closer it is to the set fall speed. However, the lower it is also means the less of a connection/more jumpyness of the character (it gives a kind of vibrating effect that can move it through the platform rigid body… Also, because it’s vibrating, the onGround boolean never really returns true). I think I might be able to survive with a lower stepheight (takes out on the smoothness of my movement though :frowning: )… Thanks normen

Oh, the stepHeight should now be much above say 3 or 4 centimeters but also not much below 1 centimeter cause it basically defines the quantization size of your characters up/down movement. Could definitely be a factor here yeah. The bullet forums have a lot of talk about how exactly the character works. I intend to do a character implementation that is completely not based on the bullet character because it applies completely uncontrollable and inconsistent physics laws to the character.

1 Like

Alrighty, thanks normen! I’m sure your character design shall be much better…

also, maybe try playing around with :
physicsCharacter.setGravity(float value);
(if it is even considered in a CharacterControl?)