How to make ninja act properly in 3rd person?

My basic problem is now how to handle movements of the ninja in third person now that I got him walking properly and he even can walk the stairs of the Q3 example map.









I’m using the Ninja with the Quake scene to make it a third person walking character scene.

http://i.imgur.com/ad6ZZ.png

It works but I had to negate a vector to make the Ninja work from the example that used Oto:

[java]character.setViewDirection(walkDirection.negate());[/java]



So I want you to say if you think I’m doing the right thing since it was an improvement and quite perfect, it’s also a rotation I use for the initiaition of the Ninja model in the complete program listed below.



The difference might be that I took the code from WalkingChar that was in the example for Oto (the robot) in third person and now making it with the ninja I had to rotate the Ninja when he is created since it seems that Ninja and Oto have opposite directions in their models(?)



[java] CapsuleCollisionShape capsule = new CapsuleCollisionShape(0f, 0f);

character = new CharacterControl(capsule, 0.7f);

model = (Node) assetManager.loadModel(“Models/Ninja/Ninja.mesh.xml”);

float scale = 0.25f;

model.scale(0.05f, 0.05f, 0.05f);

model.addControl(character);

character.setPhysicsLocation(new Vector3f(60, 10, -60));

model.setShadowMode(ShadowMode.CastAndReceive);

character.setViewDirection(new Vector3f(1, 0, 0));

rootNode.attachChild(model);

getPhysicsSpace().add(character);[/java]



But when jumping the ninja sometimes makes the application crash. What happens with the jump method, how should I do about it?



[java]

public void simpleUpdate(float tpf) {

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 (!character.onGround()) {

airTime = airTime + tpf;

} else {

airTime = 0;

}

if (walkDirection.length() == 0) {

if (!“Idle1”.equals(animationChannel.getAnimationName())) {

animationChannel.setAnim(“Idle1”, 1f);

}

} else {

System.out.println("setting walk direction " + walkDirection);

character.setViewDirection(walkDirection.negate());



if (airTime > .3f) {

if (!“stand”.equals(animationChannel.getAnimationName())) {

animationChannel.setAnim(“stand”);

}

} else if (!“Walk”.equals(animationChannel.getAnimationName())) {

System.out.println("in walk Walk ");

animationChannel.setAnim(“Walk”, 0.7f);

}

}

character.setWalkDirection(walkDirection);

}

[/java]



The entire program is



[java]package adventure;



import java.applet.Applet;

import java.applet.AudioClip;

import java.awt.BorderLayout;

import java.awt.Dimension;

import java.awt.Image;

import java.awt.TextArea;

import java.io.File;

import java.net.MalformedURLException;

import java.net.URL;

import java.util.ArrayList;

import java.util.List;

import javax.swing.JFrame;

import javax.swing.JPanel;

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.TextureKey;

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.PhysicsCollisionEvent;

import com.jme3.bullet.collision.PhysicsCollisionListener;

import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

import com.jme3.bullet.collision.shapes.CollisionShape;

import com.jme3.bullet.collision.shapes.SphereCollisionShape;

import com.jme3.bullet.control.CharacterControl;

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.bullet.objects.PhysicsCharacter;

import com.jme3.bullet.util.CollisionShapeFactory;

import com.jme3.effect.ParticleEmitter;

import com.jme3.effect.ParticleMesh.Type;

import com.jme3.effect.shapes.EmitterSphereShape;

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.Material;

import com.jme3.material.MaterialList;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;

import com.jme3.post.FilterPostProcessor;

import com.jme3.post.filters.BloomFilter;

import com.jme3.renderer.Camera;

import com.jme3.renderer.queue.RenderQueue.ShadowMode;

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.scene.shape.Box;

import com.jme3.scene.shape.Sphere;

import com.jme3.scene.shape.Sphere.TextureMode;

import com.jme3.system.AppSettings;

import com.jme3.system.JmeCanvasContext;

import com.jme3.terrain.geomipmap.TerrainLodControl;

import com.jme3.terrain.geomipmap.TerrainQuad;

import com.jme3.terrain.heightmap.AbstractHeightMap;

import com.jme3.terrain.heightmap.ImageBasedHeightMap;

import com.jme3.texture.Texture;

import com.jme3.texture.Texture.WrapMode;

import com.jme3.util.SkyFactory;



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;

Node model;

// temp vectors

Vector3f walkDirection = new Vector3f();



RigidBodyControl terrainPhysicsNode;



// animation

AnimChannel animationChannel;

AnimChannel shootingChannel;

AnimControl animationControl;

float airTime = 0;

// camera

boolean left = false, right = false, up = false, down = 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_RETURN));

inputManager

.addMapping(“CharShoot”, new KeyTrigger(KeyInput.KEY_SPACE));

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, 0.05f);

character.setJumpSpeed(20f);

model = (Node) assetManager.loadModel(“Models/Ninja/Ninja.mesh.xml”);

float scale = 0.25f;

model.scale(0.05f, 0.05f, 0.05f);

model.addControl(character);

character.setPhysicsLocation(new Vector3f(60, 10, -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(160, 10, -60));

man.setShadowMode(ShadowMode.CastAndReceive);

rootNode.attachChild(man);



}



private void setupChaseCamera() {

flyCam.setEnabled(false);

chaseCam = new ChaseCamera(cam, model, inputManager);

}



private void setupAnimationController() {

animationControl = model.getControl(AnimControl.class);

animationControl.addListener(this);

animationChannel = animationControl.createChannel();

}



@Override

public void simpleUpdate(float tpf) {

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 (!character.onGround()) {

airTime = airTime + tpf;

} else {

airTime = 0;

}

if (walkDirection.length() == 0) {

if (!“Idle1”.equals(animationChannel.getAnimationName())) {

animationChannel.setAnim(“Idle1”, 1f);

}

} else {

System.out.println("setting walk direction " + walkDirection);

character.setViewDirection(walkDirection.negate());



if (airTime > .3f) {

if (!“stand”.equals(animationChannel.getAnimationName())) {

animationChannel.setAnim(“stand”);

}

} else if (!“Walk”.equals(animationChannel.getAnimationName())) {

System.out.println("in walk Walk ");

animationChannel.setAnim(“Walk”, 0.7f);

}

}

character.setWalkDirection(walkDirection);

}



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();

}

}



public void onAnimCycleDone(AnimControl control, AnimChannel channel,

String animName) {

if (channel == shootingChannel) {

channel.setAnim(“stand”);

}

}



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();

}

}[/java]



Can you help me make my character move properly e.g. jump and attack?

@niklasro said:
The difference might be that I took the code from WalkingChar that was in the example for Oto (the robot) in third person and now making it with the ninja I had to rotate the Ninja when he is created since it seems that Ninja and Oto have opposite directions in their models(?)


I think you're right, because I had same problems with my first blender models. I used to negate the viewDirection to turn the model just like you done, but flipping model's facing direction in Blender solved that issue.

@niklasro said:
But when jumping the ninja sometimes makes the application crash. What happens with the jump method, how should I do about it?


Maybe the error log could help ?
@kriss4realms said:
I think you're right, because I had same problems with my first blender models. I used to negate the viewDirection to turn the model just like you done, but flipping model's facing direction in Blender solved that issue.



Maybe the error log could help ?

Thanks for the answer, I just checked the error log and it was easy mistake to fix, I had called the old animation name that Oto had which is "stand" and for Ninja this name is "Idle1" so that was all I had to do to fix the crash when Ninja is jumping. Now I want to make the attack realistic to be able to attack and kick down other computer controlled characters e.g. zombies, goblins, trolls and skeletons but I

a) Can't seem to find any other models that are as well developed as Oto and Ninja. I looked everywhere online and none of the models available online are as detailed and animated and easy to work with as Oto and Ninja. Maybe this is because I'm a newbie with jme3 and this is my first project.

b) Don't know how to program a fight. There should be a fight between the ninja and e.g. zombies and skeleton warriors.

c) Don't know how to use blender. I'd really like to make a larger scene and a Dungeon but I'm no artist and I hardly ever used a 3D program. I'm strong at maths and programming so I suppose I could pick it up but since I'm left-handed I have difficulties using the computer as a drawing tool.

Can you elaborate further on my issues?

Thank you

a) For a game devolopper, the graphic part of the game is not this important. I mean does it really matters if your first in-game frags is BlueNinja vs RedNinja ? I guess not and btw the game logic/mechanic is still the same for ninja or dragons or anything else.



To get models, only one solution… make your own. With Blender, you’ll get nice results in a couple of months.

There is a lot of tutorials online, provided by Blender’s fans. Have a look and be patient man.



b) A “fight” is an interaction between two objects, changing each other states and var, responding and reacting to events user events. You will find everything in JME3 tutorials pages. (amazing stuff btw :slight_smile: )



c) Try to find a team or be aware that building a full game of your own will be a loooong road. Cheers !

1 Like
@kriss4realms said:
a) For a game devolopper, the graphic part of the game is not this important. I mean does it really matters if your first in-game frags is BlueNinja vs RedNinja ? I guess not and btw the game logic/mechanic is still the same for ninja or dragons or anything else.

To get models, only one solution... make your own. With Blender, you'll get nice results in a couple of months.
There is a lot of tutorials online, provided by Blender's fans. Have a look and be patient man.

b) A "fight" is an interaction between two objects, changing each other states and var, responding and reacting to events user events. You will find everything in JME3 tutorials pages. (amazing stuff btw :) )

c) Try to find a team or be aware that building a full game of your own will be a loooong road. Cheers !

Thank you for the advice, I will follow your recommendations. Most important might be to learn blender. We're a loose team of 2 programmers and 2 graphic artists so we should be able to come up with good stuff, it's just that I'm the most knowledgable in the team about 3D, and compared to the knowledge here I know only very little.

People tell me all the time to go back to the tutorials but I think the tutorials were good but can be more with more examples, even if they repeat the same techniques it's good with variation.

What I'm trying to do with the ninja just now is to make him act like the char in Diablo i.e. when the player clicks an area on the screen the ninja should go there and attack but I couldn't manage to code it:

[java] if (binding.equals("CharShoot") && value) {

Vector3f origin = cam.getWorldCoordinates(
inputManager.getCursorPosition(), 0.0f);
Vector3f direction = cam.getWorldCoordinates(
inputManager.getCursorPosition(), 0.3f);
character.setViewDirection(direction);
animationChannel.setAnim("Attack3");
animationChannel.setLoopMode(LoopMode.DontLoop);
}[/java]
The ninja performs an attack but in the wrong direction and always in the same direction. I suppose this means I must rehearse my 3D mathematics about vectors but this isn't matlab either, we could make a nice object-oriented API with ninja.moveTo(float x, float y, float z) and similar constructs to make the implementation more along the model view controller pattern.

Are there any other comments you think I could use? They told me not to start threads so I won't and "You should also see about insulating your movement direction from your camera" but what does that mean?

Thank you