My scene has a main character who is a ninja and an enemy which is a goblin and one monster but the monster has no animation so we can wait with him:

How can I make the goblin actually move instead of just walking (animating) on the spot? I asked in the jme3 forum but I could not understand the answer: http://jmonkeyengine.org/groups/graphics/forum/topic/making-a-computer-controlled-character-move My code is
package adventure;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Image;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
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.BlenderKey;
import com.jme3.asset.plugins.HttpZipLocator;
import com.jme3.asset.plugins.ZipLocator;
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.objects.PhysicsCharacter;
import com.jme3.input.ChaseCamera;
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.MaterialList;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.ogre.OgreMeshKey;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.MouseInput;
public class Q3World extends SimpleApplication implements ActionListener,
AnimEventListener, Playable {
private Node gameLevel;
private PhysicsCharacter player;
private static boolean useHttp = false;
private static World world;
private static Person person;
private static Player dplayer;
private BulletAppState bulletAppState;
private AnimChannel channel;
private AnimControl control;
// character
CharacterControl character;
CharacterControl goblincharacter;
Node model;
// temp vectors
Vector3f walkDirection = new Vector3f();
Spatial goblin;
RigidBodyControl terrainPhysicsNode;
// animation
AnimChannel animationChannel;
AnimChannel shootingChannel;
AnimControl animationControl;
float airTime = 0;
// camera
boolean left = false, right = false, up = false, down = false, attack=false;
ChaseCamera chaseCam;
FilterPostProcessor fpp;
private Spatial sceneModel;
private RigidBodyControl landscape;
public static void main(String[] args) {
File file = new File("quake3level.zip");
if (!file.exists()) {
useHttp = true;
}
Q3World app = new Q3World();
app.start();
}
@Override
public void simpleInitApp() {
bulletAppState = new BulletAppState();
bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
stateManager.attach(bulletAppState);
setupKeys();
DirectionalLight dl = new DirectionalLight();
dl.setColor(ColorRGBA.White.clone().multLocal(2));
dl.setDirection(new Vector3f(-1, -1, -1).normalize());
rootNode.addLight(dl);
AmbientLight am = new AmbientLight();
am.setColor(ColorRGBA.White.mult(2));
rootNode.addLight(am);
if (useHttp) {
assetManager
.registerLocator(
"http://jmonkeyengine.googlecode.com/files/quake3level.zip",
HttpZipLocator.class);
} else {
assetManager.registerLocator("quake3level.zip", ZipLocator.class);
}
// create the geometry and attach it
MaterialList matList = (MaterialList) assetManager
.loadAsset("Scene.material");
OgreMeshKey key = new OgreMeshKey("main.meshxml", matList);
gameLevel = (Node) assetManager.loadAsset(key);
gameLevel.setLocalScale(0.1f);
gameLevel.addControl(new RigidBodyControl(0));
getPhysicsSpace().addAll(gameLevel);
rootNode.attachChild(gameLevel);
getPhysicsSpace().addAll(gameLevel);
createCharacters();
setupChaseCamera();
setupAnimationController();
setupFilter();
}
private void setupFilter() {
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);
fpp.addFilter(bloom);
viewPort.addProcessor(fpp);
}
private PhysicsSpace getPhysicsSpace() {
return bulletAppState.getPhysicsSpace();
}
private void setupKeys() {
inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
inputManager.addListener(this, "wireframe");
inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("CharSpace",
new KeyTrigger(KeyInput.KEY_SPACE));
inputManager
.addMapping("CharShoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(this, "CharLeft");
inputManager.addListener(this, "CharRight");
inputManager.addListener(this, "CharUp");
inputManager.addListener(this, "CharDown");
inputManager.addListener(this, "CharSpace");
inputManager.addListener(this, "CharShoot");
}
private void createCharacters() {
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.05f, 0.05f);
character = new CharacterControl(capsule, 2);
character.setJumpSpeed(20f);
model = (Node) assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
model.scale(0.05f, 0.05f, 0.05f);
model.addControl(character);
character.setPhysicsLocation(new Vector3f(55, 15, -60));
model.setShadowMode(ShadowMode.CastAndReceive);
character.setViewDirection(new Vector3f(1, 0, 0));
rootNode.attachChild(model);
getPhysicsSpace().add(character);
BlenderKey blenderKey = new BlenderKey("Models/Oto/Oto.mesh.xml");
Spatial man = (Spatial) assetManager.loadModel(blenderKey);
man.setLocalTranslation(new Vector3f(67, 15, -60));
man.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(man);
goblin = assetManager.loadModel("objects/goblin.j3o");
goblin.scale(4f, 4f, 4f);
//goblin.setLocalTranslation(60, 3.5f, -60);
goblincharacter = new CharacterControl(capsule, 2);
goblin.addControl(goblincharacter);
goblincharacter.setPhysicsLocation(new Vector3f(60, 3.5f, -60));
//goblincharacter.setViewDirection(new Vector3f(1, 0, 0));
//character.setWalkDirection(new Vector3f(1, 0, 0));
control = goblin.getControl(AnimControl.class);
control.addListener(this);
channel = control.createChannel();
for (String anim : control.getAnimationNames())
System.out.println("goblin can:"+anim);
channel.setAnim("walk");
rootNode.attachChild(goblin);
getPhysicsSpace().add(goblincharacter);
Spatial monster = assetManager.loadModel("objects/creatures/monster/monster.packed.j3o");
monster.scale(2f, 2f, 2f);
monster.setLocalTranslation(new Vector3f(53, 3, -55));
rootNode.attachChild(monster);
}
private void setupChaseCamera() {
flyCam.setEnabled(false);
chaseCam = new ChaseCamera(cam, model, inputManager);
chaseCam.setDefaultDistance(27);
}
private void setupAnimationController() {
animationControl = model.getControl(AnimControl.class);
animationControl.addListener(this);
animationChannel = animationControl.createChannel();
//shootingChannel = animationControl.createChannel();
}
@Override
public void simpleUpdate(float tpf) {
Vector3f modelDirection = goblin.getLocalRotation().toRotationMatrix().getColumn(2);
//goblin.setLocalTranslation(modelDirection.add(new Vector3f(1, 0, -0)));
Vector3f camDir = cam.getDirection().clone().multLocal(0.1f);
Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f);
camDir.y = 0;
camLeft.y = 0;
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());
}
//if (attack) {
//animationChannel.setAnim("Attack1");
//animationChannel.setLoopMode(LoopMode.DontLoop);
//}
if (!character.onGround()) {
airTime = airTime + tpf;
} else {
airTime = 0;
}
if (walkDirection.length() == 0) {
if (!"Idle1".equals(animationChannel.getAnimationName())) {
animationChannel.setAnim("Idle1", 1f);
}
} else {
character.setViewDirection(walkDirection.negate());
if (airTime > .3f) {
if (!"stand".equals(animationChannel.getAnimationName())) {
animationChannel.setAnim("Idle1");
}
} else if (!"Walk".equals(animationChannel.getAnimationName())) {
animationChannel.setAnim("Walk", 1f);
}
}
character.setWalkDirection(walkDirection);
}
/*Ninja can: Walk
Ninja can: Kick
Ninja can: JumpNoHeight
Ninja can: Jump
Ninja can: Spin
Ninja can: Attack1
Ninja can: Idle1
Ninja can: Attack3
Ninja can: Idle2
Ninja can: Attack2
Ninja can: Idle3
Ninja can: Stealth
Ninja can: Death2
Ninja can: Death1
Ninja can: HighJump
Ninja can: SideKick
Ninja can: Backflip
Ninja can: Block
Ninja can: Climb
Ninja can: Crouch*/
public void onAction(String binding, boolean value, float tpf) {
if (binding.equals("CharLeft")) {
if (value) {
left = true;
} else {
left = false;
}
} else if (binding.equals("CharRight")) {
if (value) {
right = true;
} else {
right = false;
}
} else if (binding.equals("CharUp")) {
if (value) {
up = true;
} else {
up = false;
}
} else if (binding.equals("CharDown")) {
if (value) {
down = true;
} else {
down = false;
}
} else if (binding.equals("CharSpace")) {
character.jump();
} else if (binding.equals("CharShoot") && value) {
//bulletControl();
Vector3f origin = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f);
Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f);
//direction.subtractLocal(origin).normalizeLocal();
//character.setWalkDirection(location);
System.out.println("origin"+origin);
System.out.println("direction"+direction);
character.setViewDirection(direction);
animationChannel.setAnim("Attack3");
animationChannel.setLoopMode(LoopMode.DontLoop);
}
}
public void onAnimCycleDone(AnimControl control, AnimChannel channel,
String animName) {
if (channel == shootingChannel) {
channel.setAnim("Idle1");
}
}
public void onAnimChange(AnimControl control, AnimChannel channel,
String animName) {
}
// Load an image from the net, making sure it has already been
// loaded when the method returns
public Image loadPicture(String imageName) {
return null;
}
// Load and play a sound from /usr/local/hacks/sounds/
public void playSound(String name) {
URL u = null;
try {
u = new URL("file:" + "/usr/local/hacks/sounds/" + name + ".au");
} catch (MalformedURLException e) {
}
AudioClip a = Applet.newAudioClip(u);
a.play();
}
}
I want the goblin to move around and then I want it to be able to fight the ninja. The monster should be able to kill but that can wait until later.
Update
I narrowed down the problem so that I only need the directions for the relative vectors in the goblin's direction now and forward is working and I don't understand why the others don't work, I'm getting unexpected behavior from the goblin in all directions except forward:
goblinWalkDirection.set(0, 0, 0);
if (goblinleft) {
System.out.println("goblin left");
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_X).multLocal(0.09f));
}
if (goblinright) {
System.out.println("goblin right");
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_X).multLocal(0.09f));
}
if (goblinup) {
System.out.println("goblin forward");
// move goblin forward
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_Z).multLocal(0.09f));
}
if (goblindown) {
System.out.println("goblin backwards");
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_Z).multLocal(0.09f).negate());
}
if (goblinWalkDirection.length() == 0) {
if (!"idleA".equals(animationChannel.getAnimationName())) {
goblinChannel.setAnim("idleA", 1f);
}
} else {
goblincharacter.setViewDirection(goblinWalkDirection);
if (!"walk".equals(goblinChannel.getAnimationName())) {
goblinChannel.setAnim("walk", 1f);
}
}
goblincharacter.setWalkDirection(goblinWalkDirection);
Update 2
Now the goblin can walk around if I press the corresponding keys and the animations are also correct. I just think that the Vectors that set the directions are only correct in the orward case and that I need research for which vectors to put in the other cases for the other directions the goblin can take. But anyhow it is now basically possible to manage movement of the goblin which is a step farther than comments, my coding effort could control both the ninja and the goblin.

goblinWalkDirection.set(0, 0, 0);
if (goblinleft) {
System.out.println("goblin left");
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_X).multLocal(0.09f));
}
if (goblinright) {
System.out.println("goblin right");
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_X).multLocal(0.09f));
}
if (goblinup) {
System.out.println("goblin forward");
// move goblin forward
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_Z).multLocal(0.09f));
}
if (goblindown) {
System.out.println("goblin backwards");
goblinWalkDirection.addLocal(goblin.getLocalRotation()
.mult(Vector3f.UNIT_Z).multLocal(0.09f).negate());
}
if (goblinWalkDirection.length() == 0) {
if (!"idleA".equals(goblinChannel.getAnimationName())) {
goblinChannel.setAnim("idleA", 1f);
}
} else {
goblincharacter.setViewDirection(goblinWalkDirection);
if (airTime > .3f) {
if (!"stand".equals(goblinChannel.getAnimationName())) {
goblinChannel.setAnim("Idle1");
}
} else if (!"walk".equals(goblinChannel.getAnimationName())) {
goblinChannel.setAnim("walk", 1f);
}
}
Update 3
I've been rewriting and refactoring and I've upgraded the player control to the new class bettercharactercontrol and put it on codereview so that I can get help there. I still have an issue trying to make an NPC do something more intelligent than just looping.
goblincharacter.setWalkDirection(goblin.getLocalRotation().mult(Vector3f.UNIT_Z).multLocal(0.4f));which moves the goblin forward. I was not sure whether to use the spatial or the character object. Now I can move the gobline forawrd but I need to make a thread or something to stop him from infinitely going forward. I'm working on making the implementation more MVC since now it is exactly like a C program with no seperation of model, view and controller. I'm now researching how to make the goblin "move around" preferrably in a random way and make turns at walls,tricky. \$\endgroup\$