Hello!
I had originally posted something similar to this over in this thread:
http://www.jmonkeyengine.com/forum/index.php?topic=14664.0
However, I realized that the place I was posting was probably irrelevant and possibly a thread hijack. So I went and researched my issues a bit more and now I have some solid questions about the PhysicsCharacterNode.
- How exactly does moveDirection work? In the code I've posted below, I noticed that if I cap the fps using setVSync that the movement amount increases. But I thought I had normalized the velocity by the time per frame properly already… So I think I'm using moveDirection improperly. I can't tell though since I don't know how to get into the jBullet jar to look at the actual call! XD
- The jitter I described in my prior post is way more noticable at lower framerates (makes sense). The one big thing that jumps out at me is how there's a very violent jitter after a jump is issued - just as the node begins to fall after the apex of the jump. Will this jitter and the overall jitter be addressed later on down the road? Or is it something that the game dev needs to find a way to handle?
- I had described a method of avoiding 'seeing' the jitter by having the player's model simply get location updates from the physicsnode. This method seems less and less ideal the more I tinker with it. I'm guessing this is not the intended method and that the PhysicsCharacterNode should have all the things associated with the player as a child of it?
- Oh almost forgot. With the box updating it's location based on the local position of the node, why is it the box is always playing catchup? Is the box's updated location happening before the physics node is updated?
Whew long winded post XD
Thank you for reading! Framerate VSync'd code below!
EDIT: Added 4.
import com.jme3.app.SimpleBulletApplication;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.nodes.PhysicsCharacterNode;
import com.jme3.bullet.nodes.PhysicsNode;
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.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
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.system.AppSettings;
/**
* Example 9 - How to make walls and floors solid.
* This version uses Physics and a custom Action Listener.
* @author normen, with edits by Zathras
*/
public class HelloCollisionMod
extends SimpleBulletApplication
implements ActionListener {
private Spatial gameScene;
private LifeformMod player;
private Geometry playerModel;
private Vector3f walkDirection = new Vector3f();
private boolean left = false, right = false, up = false, down = false;
private static AppSettings settings;
public static void main(String[] args) {
HelloCollisionMod app = new HelloCollisionMod();
settings = new AppSettings(true);
settings.setVSync(true);
settings.setTitle("Hello Collision Modified Version");
app.setSettings(settings);
app.start();
}
public void simpleInitApp() {
renderer.setBackgroundColor(ColorRGBA.Cyan);
flyCam.setEnabled(false);
setupKeys();
// We add a light so we see the scene
DirectionalLight dl = new DirectionalLight();
dl.setColor(ColorRGBA.White.clone().multLocal(2));
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalize());
rootNode.addLight(dl);
// We load the scene from the zip file and adjust its size.
assetManager.registerLocator("town.zip", ZipLocator.class.getName());
gameScene = assetManager.loadModel("main.scene");
gameScene.setLocalScale(2f);
// We set up collision detection for the scene by creating a
// compound collision shape and a physics node.
CompoundCollisionShape sceneShape = CollisionShapeFactory.createMeshCompoundShape((Node) gameScene);
PhysicsNode levelNode = new PhysicsNode(gameScene, sceneShape, 0);
Box box = new Box(Vector3f.ZERO, 1, .5f, 1);
playerModel = new Geometry("PlayerBox", box);
Material playerMat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
playerMat.setColor("m_Color", ColorRGBA.Magenta);
playerModel.setMaterial(playerMat);
/** COMMENT THIS OUT TO SEE JITTER **/
rootNode.attachChild(playerModel);
/** COMMENT OUT ABOVE TO SEE JITTER **/
// We set up collision detection for the player by creating
// a capsule collision shape and a physics character node.
// The physics character node offers extra settings for
// size, stepheight, jumping, falling, and gravity.
// We also put the player in its starting position.
player = new LifeformMod(new Vector3f(box.xExtent, box.yExtent, box.zExtent),
0.5f, "PlayerCharacter", playerModel, rootNode);
player.setMovementProperties(100, 150, 100, 2.2f, 60, 40);
Material wireMat = new Material(assetManager, "Common/MatDefs/Misc/WireColor.j3md");
wireMat.setColor("m_Color", ColorRGBA.Red);
player.attachDebugShape(wireMat);
player.setJumpSpeed(20);
player.setFallSpeed(30);
player.setGravity(30);
player.setMaxSlope(FastMath.PI/4);
player.setLocalTranslation(new Vector3f(0, 10, 0));
player.updateGeometricState();
// We attach the scene and the player to the rootNode and the physics space,
// to make them appear in the game world.
rootNode.attachChild(levelNode);
rootNode.attachChild(player);
rootNode.updateGeometricState();
getPhysicsSpace().add(levelNode);
getPhysicsSpace().add(player);
}
/** We over-write some navigational key mappings here, so we can
* add physics-controlled walking and jumping: */
private void setupKeys() {
inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addListener(this, "Lefts");
inputManager.addListener(this, "Rights");
inputManager.addListener(this, "Ups");
inputManager.addListener(this, "Downs");
inputManager.addListener(this, "Jumps");
}
/** 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 value, float tpf) {
if (binding.equals("Lefts")) {
if (value) { left = true; } else { left = false; }
} else if (binding.equals("Rights")) {
if (value) { right = true; } else { right = false; }
} else if (binding.equals("Ups")) {
if (value) { up = true; } else { up = false; }
} else if (binding.equals("Downs")) {
if (value) { down = true; } else { down = false; }
} else if (binding.equals("Jumps")) {
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) {
walkDirection.set(0, 0, 0);
if(left) {player.turn(tpf, 1);}
if(right) {player.turn(tpf, -1);}
if(up) {player.forward(tpf);}
if(down) {player.backpedal(tpf);}
player.update(tpf);
System.out.println("LOC: " + player.getLocalTranslation());
cam.setLocation(player.getLocalTranslation().addLocal(5, 5, 5));
cam.lookAt(player.getWorldTranslation(), Vector3f.UNIT_Y);
}
public class LifeformMod extends PhysicsCharacterNode {
/** Geometry **/
private Geometry model;
/** Movement Physics **/
private float weight;
private float velocity;
private float acceleration;
private float braking;
private float turnRate;
private float maxSpeed;
private float minSpeed;
private boolean isRunning = false;
public LifeformMod(Vector3f collisionExtents, float step,
String nodeName, Geometry model, Node floorRootNode) {
// super(new BoxCollisionShape(collisionExtents), step);
// super(new CapsuleCollisionShape(collisionExtents.x, .1f, 1), step);
// super(new CylinderCollisionShape(collisionExtents, 1), step);
super(new SphereCollisionShape(collisionExtents.x), step);
setModel(model);
this.setGravity(30);
this.setFallSpeed(30);
this.setJumpSpeed(20);
}
public void update(float tpf) {
drift(tpf);
Vector3f nextMove = new Vector3f();
nextMove.set(model.getLocalRotation().getRotationColumn(2, null).mult(velocity * tpf));
setWalkDirection(nextMove);
// System.out.println("Next Move : " + nextMove);
/** COMMENT THIS OUT IF YOU WANT TO SEE JITTER **/
model.setLocalTranslation(this.getLocalTranslation());
/** COMMENT ABOVE OUT IF YOU WANT TO SEE JITTER **/
System.out.println("Velocity : " + velocity);
// System.out.println("Pos : " + this.getLocalTranslation());
System.out.println("
");
}
public void resetPlayer() {
this.setLocalTranslation(Vector3f.ZERO);
}
public void setModel(Geometry model) {
if(this.model != null) {
this.detachChild(this.model);
}
this.model = model;
/** UNCOMMENT THIS IF YOU WANT TO SEE JITTER **/
// this.attachChild(this.model);
/** UNCOMMENT ABOVE IF YOU WANT TO SEE JITTER **/
}
public Geometry getModel() {
return model;
}
/**
* Quick method to set all of the movement attributes of this character.
* @param w Weight
* @param v Velocity
* @param a Acceleration Rate
* @param b Braking Rate
* @param tr Turning Rate
* @param maxS Maximum Speed (Moving forwards)
* @param minS Minimum Speed (Moving backwards)
*/
public void setMovementProperties(float w, float a, float b, float tr, float maxS, float minS) {
this.weight = w;
this.acceleration = a;
this.braking = b;
this.turnRate = tr;
this.maxSpeed = maxS;
this.minSpeed = minS;
}
public void forward(float tpf) {
if(onGround()) {
velocity += tpf * acceleration;
if(isRunning && velocity > maxSpeed * 2) {
velocity = maxSpeed * 2;
}
else if(!isRunning && velocity > maxSpeed) {
velocity = maxSpeed;
}
}
}
public void backpedal(float tpf) {
if(onGround()) {
velocity -= tpf * braking;
if(velocity < -minSpeed) {
velocity = -minSpeed;
}
}
}
public void turn(float tpf, int direction) {
model.rotate(0, direction * turnRate * tpf, 0);
}
public void jump(float tpf) {
jump();
}
private void drift(float tpf) {
float driftWeight = weight;
if(!onGround()) {
driftWeight = weight * .05f;
}
else {
driftWeight = weight;
}
if(velocity < -FastMath.FLT_EPSILON) {
velocity += (driftWeight/5) * tpf;
if(velocity > 0) {
velocity = 0;
}
}
else if(velocity > FastMath.FLT_EPSILON) {
velocity -= (driftWeight/5) * tpf;
if(velocity < 0) {
velocity = 0;
}
}
}
}
}