Of course I’ve looked at the Javadocs. And it’s all the elaborate examples and clarifications that have even gotten me this far…
Here’s my code:
package nl.pebkac.inthegame;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.BetterCharacterControl;
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.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.plugins.blender.BlenderModelLoader;
import com.jme3.scene.shape.Box;
import nl.pebkac.inthegame.terrain.SimpleTerrainGenerator;
/**
* test
* @author normenhansen
*/
public class MainHub extends SimpleApplication
{
public static void main(String[] args)
{
MainHub app = new MainHub();
app.start();
}
// app vars
private BulletAppState bas;
private Node terrain = new Node("Terrain");
private CameraNode camNode;
private Person person;
private boolean escapePressed = false;
@Override
public void simpleInitApp()
{
// Setting up defaults
assetManager.registerLoader(BlenderModelLoader.class, ".blend");
// Setting up appstate / physics
bas = new BulletAppState();
stateManager.attach(bas);
// Loading terrain
SimpleTerrainGenerator.generateTerrain(assetManager, bas, terrain);
DirectionalLight dl = new DirectionalLight();
dl.setColor(ColorRGBA.White);
dl.setDirection(Vector3f.UNIT_XYZ);
rootNode.addLight(dl);
// init person
camNode = new CameraNode("Camera", cam);
person = new Person(assetManager, bas, cam);
person.warp(new Vector3f(0, 10f, 0));
// Bringing it all together
rootNode.attachChild(terrain);
rootNode.attachChild(person);
//setting up camera
flyCam.setEnabled(false);
//create the camera Node
camNode.setControlDir(ControlDirection.SpatialToCamera);
person.getPlayerNode().attachChild(camNode);
camNode.setLocalTranslation(new Vector3f(-0.2f, 2.7f, -6));
camNode.lookAt(person.getLookAtOffset(), Vector3f.UNIT_Y);
this.bindKeysForState_INGAME();
}
@Override
public void simpleUpdate(float tpf)
{
super.simpleUpdate(tpf);
person.update(tpf);
camNode.lookAt(person.getBirdieNode().getLocalTranslation(), Vector3f.UNIT_Y);
if(escapePressed) { System.exit(0); }
}
// input handling...
private final String MV_LEFT = "move_left";
private final String MV_RIGHT = "move_right";
private final String MV_FORWARD = "move_forward";
private final String MV_BACK = "move_back";
private final String RT_CAM_XAXIS = "rotate_x_axis";
private final String RT_CAM_YAXIS = "rotate_y_axis";
private final String RT_CAM_XAXIS_NEG = "rotate_x_axis_neg";
private final String RT_CAM_YAXIS_NEG = "rotate_y_axis_neg";
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf)
{
if(name.equals("ESCAPE")) {escapePressed = true; }
else if(MV_BACK.equals(name)) {person.back = isPressed; }
else if(MV_FORWARD.equals(name)) {person.forward = isPressed;}
else if(MV_RIGHT.equals(name)) {person.right = isPressed;}
else if(MV_LEFT.equals(name)) {person.left = isPressed;}
}
};
private AnalogListener analogListener = new AnalogListener()
{
public void onAnalog(String name, float value, float tpf)
{
if(RT_CAM_XAXIS.equals(name)) {person.rotateLookat("X", value * tpf); }
else if(RT_CAM_XAXIS_NEG.equals(name)) {person.rotateLookat("X", -value * tpf); }
else if(RT_CAM_YAXIS.equals(name)) {person.rotateLookat("Y", value * tpf); }
else if(RT_CAM_YAXIS_NEG.equals(name)) {person.rotateLookat("Y", -value * tpf); }
}
};
private void bindKeysForState_INGAME()
{
inputManager.clearMappings();
inputManager.addMapping(MV_LEFT, new KeyTrigger(KeyInput.KEY_LEFT));
inputManager.addMapping(MV_RIGHT, new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addMapping(MV_FORWARD, new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping(MV_BACK, new KeyTrigger(KeyInput.KEY_DOWN));
inputManager.addMapping("ESCAPE", new KeyTrigger(KeyInput.KEY_ESCAPE));
inputManager.addListener(this.actionListener, MV_LEFT, MV_RIGHT, MV_FORWARD, MV_BACK);
inputManager.addListener(this.actionListener, "ESCAPE");
inputManager.addMapping(RT_CAM_XAXIS, new MouseAxisTrigger(MouseInput.AXIS_X, false));
inputManager.addMapping(RT_CAM_XAXIS_NEG, new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping(RT_CAM_YAXIS, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
inputManager.addMapping(RT_CAM_YAXIS_NEG, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
inputManager.addListener(this.analogListener, RT_CAM_XAXIS, RT_CAM_YAXIS, RT_CAM_XAXIS_NEG, RT_CAM_YAXIS_NEG);
}
}
class Person extends Node
{
private final String ANIM_WALK = "Walk";
private final String ANIM_IDLE = "Idle";
public enum Actions
{
NULL(-1)
, IDLE(0)
, WALKING(1)
, RUNNING(2)
;
private final int order;
private Actions(int order) {this.order = order;}
public int getOrder() { return this.order; }
}
private Node playerNode;
private Node camBirdie = new Node("LookAtTheBirdie!");
private float camBirdieRadius = 50f;
private float camBirdieAngleHor = -90f;
private float camBirdieAngleVer = 0.0f;
private Actions actionLower = Actions.NULL;
private Actions actionUpper = Actions.NULL;
// movement helpers
private Vector3f direction = new Vector3f();
private Vector3f dir_forward = new Vector3f();
private Vector3f dir_left = new Vector3f();
private Camera cam;
// mobvement controllers
public boolean back = false;
public boolean forward = false;
public boolean right = false;
public boolean left = false;
// Santanimations
private AnimChannel channel_top;
private AnimChannel channel_bottom;
private AnimControl animControl;
private AnimEventListener animListener;
private BetterCharacterControl playerControl;
private float walkSpeed = 5f;
public float getWalkSpeed() { return this.walkSpeed; }
public void setWalkSpeed(float v) { this.walkSpeed = v; }
private float turnSpeed = 180f * 1000f; // degrees / second
public float getTurnSpeed() { return this.turnSpeed; }
public void setTurnSpeed(float v) { this.turnSpeed = v; }
public Person(AssetManager assetManager, BulletAppState bas, Camera cam)
{
// Creating a node and loading model
this.cam = cam;
playerNode = (Node) assetManager.loadModel("Models/kerstman/Cube.001.mesh.xml");
Material mat_default = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat_default.setTexture("ColorMap", assetManager.loadTexture("Textures/kerstman.png"));
playerNode.setMaterial(mat_default);
// enabling physics
playerControl = new BetterCharacterControl(1f, 2.1f, 80f);
playerControl.setGravity(new Vector3f(0f, -10f, 0f));
playerNode.addControl(playerControl);
bas.getPhysicsSpace().add(playerControl);
// Setting up animations
setupAnimChannels();
channel_top.setAnim(ANIM_IDLE);
channel_top.setLoopMode(LoopMode.Loop);
channel_bottom.setAnim(ANIM_IDLE);
channel_bottom.setLoopMode(LoopMode.Loop);
setBirdie();
this.attachChild(playerNode);
this.attachChild(camBirdie);
debugBirdie(assetManager);
}
public Node getNode() { return this; }
public Node getPlayerNode() { return this.playerNode; }
public Node getBirdieNode() { return this.camBirdie; }
public Vector3f getLookAtOffset()
{
return this.camBirdie.getLocalTranslation().subtract( this.playerNode.getLocalTranslation());
}
public void update(float milli)
{
setBirdie();
direction.set(0, 0, 0);
Vector3f camDir = cam.getDirection().clone();
Vector3f camLeft = cam.getLeft().clone();
dir_forward = camDir.normalize();
dir_left = camLeft.normalize();
if(back) { direction.addLocal(dir_forward.negate());}
if(forward) { direction.addLocal(dir_forward);}
if(right) { direction.addLocal(dir_left.negate());}
if(left) { direction.addLocal(dir_left);}
direction = direction.multLocal(5f);
playerControl.setWalkDirection(direction);
}
// initialisers
private void setupAnimChannels()
{
// inner helper listener for anim events
this.animListener = new AnimEventListener()
{
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName)
{
if(channel == channel_top)
{
if(channel.getLoopMode() != LoopMode.Loop)
switchAction(actionLower);
}
if(channel == channel_bottom)
{
if(channel.getLoopMode() != LoopMode.Loop)
switchAction(Actions.IDLE);
}
}
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {}
};
animControl = playerNode.getControl(AnimControl.class);
animControl.addListener(this.animListener);
channel_top = animControl.createChannel();
channel_top.addBone("neck");
channel_top.addBone("chest");
channel_top.addBone("shoulder.r");
channel_top.addBone("shoulder.l");
channel_top.addBone("arm.upper.r");
channel_top.addBone("arm.upper.l");
channel_top.addBone("arm.lower.r");
channel_top.addBone("arm.lower.l");
channel_top.addBone("abdomen");
channel_bottom = animControl.createChannel();
channel_bottom.addBone("root");
channel_bottom.addBone("leg.right");
channel_bottom.addBone("leg.left");
}
public void warp(Vector3f vector3f) { playerControl.warp(new Vector3f(0, 15f, 0)); }
public void rotateLookat(String axis, float amtPerSec)
{
if(axis.equals("X"))
{
camBirdieAngleHor += amtPerSec * turnSpeed;
if(camBirdieAngleHor < 0f) { camBirdieAngleHor += 360f; }
if(camBirdieAngleHor > 360f){ camBirdieAngleHor -= 360f; }
}
if(axis.equals("Y"))
{
camBirdieAngleVer += amtPerSec * turnSpeed;
if(camBirdieAngleVer < 0f) { camBirdieAngleVer += 360f; }
if(camBirdieAngleVer > 360f){ camBirdieAngleVer -= 360f; }
}
setBirdie();
}
public void switchAction(Actions newAction)
{
switchAction(newAction, false);
}
public void switchAction(Actions newAction, boolean override)
{
boolean upchanged = false;
boolean lowchanged = false;
if(actionUpper.getOrder() < newAction.getOrder() || override)
{
actionUpper = newAction;
upchanged = true;
}
if(actionLower.getOrder() < newAction.getOrder() || override)
{
actionLower = newAction;
lowchanged = true;
}
if(!upchanged && !lowchanged)
return;
if(upchanged)
{
switch(actionUpper)
{
default:
channel_top.setAnim(ANIM_IDLE);
channel_top.setLoopMode(LoopMode.Loop);
break;
}
}
if(lowchanged)
{
switch(actionLower)
{
default:
channel_bottom.setAnim(ANIM_IDLE);
channel_bottom.setLoopMode(LoopMode.Loop);
break;
}
}
}
private void setBirdie()
{
float my_x = playerNode.getLocalTranslation().x;
float my_y = playerNode.getLocalTranslation().y;
float my_z = playerNode.getLocalTranslation().z;
float bird_x_a = (float) (my_x + (camBirdieRadius * Math.sin(FastMath.DEG_TO_RAD * camBirdieAngleHor)));
float bird_x_b = (float) (my_x + (camBirdieRadius * Math.sin(FastMath.DEG_TO_RAD * camBirdieAngleVer)));
float bird_y = (float) (my_y + (camBirdieRadius * Math.sin(FastMath.DEG_TO_RAD * camBirdieAngleVer)));
float bird_z = (float) (my_z + (camBirdieRadius * Math.cos(FastMath.DEG_TO_RAD * camBirdieAngleHor)));
Vector3f r = new Vector3f(bird_x_a, 0, bird_z).addLocal(0, bird_y, 0);
camBirdie.setLocalTranslation(r);
playerControl.setWalkDirection(camBirdie.getLocalTranslation());
}
private void debugBirdie(AssetManager assetManager)
{
Box b = new Box(1, 1, 1);
Geometry geom = new Geometry("Box", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Red);
geom.setMaterial(mat);
camBirdie.attachChild(geom);
}
}