Make Player)

Hi guys,

After half a year of Java in school our task is to build a little game. My friend and I decided to make turtle race game and because we have to use java to use the jmonkey engine.

After finishing the main model:

I started tyring to get used to the jmonkey engine, and trying to get the motion of the turtle when pressing WASD done. After a few hours, checking a few tutorials (a lot of the code is almost exactly copied :wink: ) this is what I have managed to do.

[java]package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
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.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

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

private BulletAppState bulletAppState;
private Spatial gameLevel;
private BetterCharacterControl playerControl;
private ChaseCamera chaseCam;

private boolean left = false, right = false, up = false, down = false;

Node playerNode;



@Override
public void simpleInitApp() {

bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);

mouseInput.setCursorVisible(false);
flyCam.setEnabled(false);

initAmbient();
initScene();
initPlayer();
initPlayerPhysics();
initChaseCam();
initControls();

}

    private Vector3f walkDirection = new Vector3f(0,0,0); // stop
    private float airTime = 0;

@Override
public void simpleUpdate(float tpf) {
 Vector3f camDir = cam.getDirection().clone(); // The use of multLocal here is to control the rate of movement multiplier for character walk speed. 

Vector3f camLeft = cam.getLeft().clone();
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 (!playerControl.isOnGround()) {
airTime += tpf;
} else {
airTime = 0;
}

playerControl.setWalkDirection(walkDirection.multLocal(5f));
}

@Override
public void simpleRender(RenderManager rm) {
    
}

private void initAmbient(){ 
AmbientLight ambient = new AmbientLight();
ambient.setColor(ColorRGBA.White.mult(2));
rootNode.addLight(ambient); 
}

private void initScene(){
    gameLevel = assetManager.loadModel("Scenes/Scene.j3o");        
    gameLevel.setLocalTranslation(0, -1, 0);
    gameLevel.addControl(new RigidBodyControl(0));
    // 0 stands for the weight of the object (how fast it falls to the ground)
    rootNode.attachChild(gameLevel);
    bulletAppState.getPhysicsSpace().addAll(gameLevel);
}

private void initPlayer(){
//create player
Box playerBox = new Box(1, 1, 1);
Geometry player = new Geometry("Player", playerBox);
Material matPlayer = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matPlayer.setColor("Color", ColorRGBA.Orange);
player.setMaterial(matPlayer);
playerNode = new Node();
playerNode.attachChild(player); 
}

private void initPlayerPhysics(){
  //Create Controls
playerControl = new BetterCharacterControl(1f, 1f, 1f); //contruct character collsion shape
playerNode.addControl(playerControl); //attach Control to playerNode crated above
    //physical properties
        playerControl.setJumpForce(new Vector3f(0, 5f, 0));
        playerControl.setGravity(new Vector3f(0, 10f, 0));
playerControl.warp(new Vector3f(0, 10, 10));
    //add to physics state
        bulletAppState.getPhysicsSpace().add(playerControl);
        bulletAppState.getPhysicsSpace().addAll(playerNode);
 rootNode.attachChild(playerNode);
}

private void initChaseCam(){
chaseCam = new ChaseCamera(cam, playerNode, inputManager);
chaseCam.setSmoothMotion(true);
chaseCam.setTrailingEnabled(true);
chaseCam.setMinDistance(10f);
chaseCam.setRotationSpeed(1f);
}

private void initControls(){
 inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
 inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
 inputManager.addMapping("CharForward", new KeyTrigger(KeyInput.KEY_W));
 inputManager.addMapping("CharBackward", new KeyTrigger(KeyInput.KEY_S));
 //inputManager.addMapping("CharJump", new KeyTrigger(KeyInput.KEY_SPACE));
 inputManager.addListener(analogListener, "CharLeft", "CharRight");
 inputManager.addListener(analogListener, "CharForward", "CharBackward");
 
}

private ActionListener analogListener = new ActionListener(){    
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("CharForward")) {
        if (value) up = true;
        else up = false;
    } else if (binding.equals("CharBackward")) {
        if (value) down = true;
        else down = false;
    }
}
};

}[/java]

(I used a box to represent the turtle mesh)

My Problem is that the box always moves in the direction the camera looks. Let’s say the box was already the turle: If the camera looked in the same direction as the turtle (forward) and the user presses W the turtle moves forward. But if I moved the camera 90° to the left or and the press W the turtle would slide sideways. I would like to change it so that it can only move in two directions: Only on it’s local z axis (forwards and backwards). If i move the camera around i want the box(or turtle) to rotate on its y axis together with the camera. So that if i press W the turtle always moves forward.

I accidentaly pressed enter therefore the title is wrong :confused: I wanted to write MAKE PLAYER ROTATE WITH CAMERA AND/OR WITH A & D. (If a mod or s.o. could change that i’d be grateful because I can’t figure out how to)

Furthermore I didn’t finish writing yet:

Apart from the movement I’d like to be able to rotate the box if I press A and D. But I don’t know where to integrate this, because the code from the tutorial is build up so the the movement is dependant on the camera, but the rotation would be independant from the camera. Any help is greatly appreaciated. :slight_smile:

In your simpleUpdate you compute direction based on the camera

[java]
public void simpleUpdate(float tpf) {
Vector3f camDir = cam.getDirection().clone(); // The use of multLocal here is to control the rate of movement multiplier for character walk speed.
Vector3f camLeft = cam.getLeft().clone();
[/java]

Try to compute the direction based on current orientation(not tested) :

[java]
Vector3f front = player.getWorldRotation().mult(Vector3f.UNIT_X);
Vector3f right = player.getWorldRotation().mult(Vector3f.UNIT_Z);
[/java]

PS: I never used the BetterCharacterControl

I need a little bit more help :confused: I don’t quite understand what this code segment precisely does:

[java]if (left) walkDirection.addLocal(camLeft);
if (right) walkDirection.addLocal(camLeft.negate());
if (up) walkDirection.addLocal(camDir);
if (down) walkDirection.addLocal(camDir.negate());[/java]

Is it if the left is true, so a is pressed we add a value to the walkdirection. But what value? to which axis? Could somebody thoroughly explain how this walkDirection(); works?
[java]@Override
public void simpleUpdate(float tpf) {
Vector3f camDir = cam.getDirection().clone(); // The use of multLocal here is to control the rate of movement multiplier for character walk speed.
Vector3f camLeft = cam.getLeft().clone();
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 (!playerControl.isOnGround()) {
airTime += tpf;
} else {
airTime = 0;
}

playerControl.setWalkDirection(walkDirection.multLocal(5f));
}[/java]

Only very few people seem the use the "Better"CharacterControl. How can I use the old one, because mine i crossed out. How can i reactivate it?

I feel like this might be helpful here:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

I suppose walkDirection define the direction + distance (length).

so you can use something like :
[java]
public void simpleUpdate(float tpf) {
walkDirection.set(0,0,0);

// define walkDirection in Local (x = forward, y = up, z = right)
if (left) walkDirection.addLocal(0f, 0f, -1f);
if (right) walkDirection.addLocal(0f, 0f, 1f);
if (up) walkDirection.addLocal(1f, 0f, 0f);
if (down) walkDirection.addLocal(-1f, 0f, 0f);
walkDirection.normalize(); // in case of 2 directions

// change to World (only apply rotation)
player.getWorldRotation().multLocal(walkDirection);

if (!playerControl.isOnGround()) {
airTime += tpf;
} else {
airTime = 0;
}

playerControl.setWalkDirection(walkDirection.multLocal(5f));
[/java]

@pspeed said: I feel like this might be helpful here: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

Vectors are generally not my problem, but applying it in the jmonkey engine isn’t all to simble, because i just don’t know the commands yet. So from slide 33 it gets very useful thanks :slight_smile:

What I have manager to create:

[java]public void simpleUpdate(float tpf) {

// define walkDirection in Local (x = forward, y = up, z = right)

if (left && up) {
player.rotate(0f, .001f, 0f);
playerNode.rotate(0f, 0.001f, 0f);
} if (right && up) {
player.rotate(0f, -.001f, 0f);
playerNode.rotate(0f, -.001f, 0f);
}

float x = 0;
if (x<0.0008 && up) x=x+0.0001f;
if (up) walkDirection.addLocal(x, 0f, 0f);
if (down) walkDirection.addLocal(-x, 0f, 0f);
walkDirection.normalize(); // in case of 2 directions

//change to World (only apply rotation)
playerNode.getWorldRotation().multLocal(walkDirection);

if (!playerControl.isOnGround()) {
airTime += tpf;
} else {
airTime = 0;
}

playerControl.setWalkDirection(walkDirection);[/java]

But I still have a few problems: As you can see in the Code if I press A or D I rotate palyer and playerNode. If I only rotate the palyerNode the model doesn’t doesn’t move. I tried rotating playerControl from Better Character control but that just gives me an error. So ay adive on how to fix the rotation would be appreciated.

The 2nd problem is that the seems to accelerate all the time. I tried to fix this with the if clause but this doesn’t quite work. I assume it has to do with the friction of the player, because he slows down very slowly. I would like to change that and get rid of that strange acceleration thing. playerControl.setFriction(1f); doesn’t seem to work for the character control. So advice here would be appreciated as well.

few comments :

  • your walkDirection vector is never modified by ‘up’ or ‘down’ because x == 0 (always)
  • normalize() will set the length to 1 (in your expected case (-1,0,0) or (1,0,0), but can throw an error if vector is (0,0,0) and I made a mistake it should be normalizeLocal() to modified the caller (walkDirection). My suggestion remove the call to normalizeLocal() if you no longeur mult or combine direction. (I just browse the source of BetterCharacterControl and walDirection is normalized before being applied)
  • BetterCharacterControl is a “physic control” so should not directly modified orientation, position of playerNode but used playerControl.setViewDirection(…)

take a look at the javadoc http://hub.jmonkeyengine.org/javadoc/com/jme3/bullet/control/BetterCharacterControl.html

What shall I put in the brackets of SetViewDirection.

new Vector3f(…) gives no result
creating a new vector outside and calling it gives no result

if i put in walkDirection the player turns, but I don’t know where to control the “Speed” of the turn and afterwards he can’t move forward anymore.

Update: I cleaned up the mess in the Code walking forwards and backwards works perfectly right now.

[java]package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
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.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

public class Main extends SimpleApplication implements ActionListener {

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

private BulletAppState bulletAppState;
private Spatial gameLevel;
private Spatial player;
private BetterCharacterControl playerControl;
private ChaseCamera chaseCam;    
private boolean left,right, up, down;

private Vector3f walkDirection = new Vector3f(0,0,0);
//private float airTime = 0;

Node playerNode;




@Override
public void simpleInitApp() {

bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);

flyCam.setEnabled(false);
mouseInput.setCursorVisible(false);

    
initSunLight();
initAmbientLight();
initScene();
initPlayer();
initPlayerPhysics();
initChaseCam();
initControls();

}

    
    
@Override
public void simpleUpdate(float tpf) {

walkDirection.set(0,0,0);
if (up){
walkDirection.addLocal(1f, 0f, 0f);
}
if (down){ walkDirection.addLocal(-0.5f, 0f, 0f);
}
if (left){
playerControl.setViewDirection(new Vector3f(0f, 1f, 0f));
}
if (right){
playerControl.setViewDirection(new Vector3f(0f, -1f, 0f));
}

/* if (!playerControl.isOnGround()) {
airTime += tpf;
} else {
airTime = 0;
}*/

playerControl.setWalkDirection(walkDirection.multLocal(5f));

}

@Override
public void simpleRender(RenderManager rm) {
    
}

private void initSunLight(){ 

DirectionalLight sun = new DirectionalLight();
sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
sun.setColor(ColorRGBA.White);
rootNode.addLight(sun); 
}

private void initAmbientLight(){
     /** A white ambient light source. */ 
AmbientLight ambient = new AmbientLight();
ambient.setColor(ColorRGBA.White.mult(1.3f));
rootNode.addLight(ambient);    
}

private void initScene(){
    gameLevel = assetManager.loadModel("Scenes/Scene.j3o");        
    gameLevel.setLocalTranslation(0, -1, 0);
    gameLevel.addControl(new RigidBodyControl(0f));
    // 0 stands for the weight of the object (how fast it falls to the ground)
    rootNode.attachChild(gameLevel);
    bulletAppState.getPhysicsSpace().addAll(gameLevel);
}

private void initPlayer(){
//create player
player = assetManager.loadModel("Models/turtlefinished_80_textured/turtlefinished_80_textured.j3o");
player.scale(1f);
player.rotate(0, FastMath.PI / 2 , 0);

playerNode = new Node();
playerNode.attachChild(player); 

}

private void initPlayerPhysics(){
  //Create Controls
playerControl = new BetterCharacterControl(0.26f, 0.5f, 20f); //contruct character collsion shape and weight
playerNode.addControl(playerControl); //attach Control to playerNode crated above
        playerControl.setJumpForce(new Vector3f(0, 5f, 0));
        playerControl.setGravity(new Vector3f(0, 10f, 0));
playerControl.warp(new Vector3f(0, 10, 10));
        bulletAppState.getPhysicsSpace().add(playerControl);
        bulletAppState.getPhysicsSpace().addAll(playerNode);
 rootNode.attachChild(playerNode);
}

private void initChaseCam(){
chaseCam = new ChaseCamera(cam, playerNode, inputManager);
chaseCam.setSmoothMotion(true);
chaseCam.setTrailingEnabled(true);
chaseCam.setMinDistance(10f);    
}

private void initControls(){
 inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
 inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
 inputManager.addMapping("CharForward", new KeyTrigger(KeyInput.KEY_W));
 inputManager.addMapping("CharBackward", new KeyTrigger(KeyInput.KEY_S));
 inputManager.addListener(this, "CharLeft", "CharRight");
 inputManager.addListener(this, "CharForward", "CharBackward");
 
}


public void onAction(String name, boolean isPressed, float tpf) {
   if (name.equals("CharLeft")) {
        left = isPressed;
        
    } else if (name.equals("CharRight")) {
        right = isPressed;
        
    } else if (name.equals("CharForward")) {
        up = isPressed;
        
    } else if (name.equals("CharBackward")) {
        down = isPressed;
        
    } 
}

}
[/java]

However, the rotation is still not working and I can’t exactly see why. What do I have to change so the turle rotates to the left around the y axis when i press A and right around the y axis when I press D?

[java]
@Override
public void simpleUpdate(float tpf) {
walkDirection.set(0,0,0);
float speed = 5.0f;
if (up) walkDirection.addLocal(speed, 0f, 0f);
if (down) walkDirection.addLocal(- speed, 0f, 0f);
// change to World (only apply rotation)
player.getWorldRotation().multLocal(walkDirection);
playerControl.setWalkDirection(walkDirection);

	if (left || right) {
		viewDirection.set(playerControl.getViewDirection());
		player.getWorldRotation().inverse().multLocal(viewDirection);
		if (left) viewDirection.addLocal(0.1f, 0f, 0f);
		if (right) viewDirection.addLocal(-0.1f, 0f, 0f);
		viewDirection.normalizeLocal();
		player.getWorldRotation().multLocal(viewDirection);
		playerControl.setViewDirection(viewDirection);
	}
}

[/java]

I prefer to use RigidBody + velocity (linear / angular).

You prefer to use RigidBody control for the main Character? Does this have any benefits, or does it not matter for my turtle game?
And I don’t quite unterstand what your Code segmet does. is view Direction a Vector? If I just implement your Code like that, when I press W or S the palyer moves sideways, if I press A or D nothing happens.

The last sample I send was “tested” with your code (only add viewDirection as local field like walkDirection) and replace Model by box.

I prefer RigidBodyControl because :

  • I didn’t know about BetterCharacterControl before I replied your question
  • I control force, velocity,… x linear, angular

But I imagine BetterC… will hide you some computation.

FYI, my basic control :
[java]
@Override
protected void controlUpdate(float tpf) {
RigidBodyControl phy0 = spatial.getControl(RigidBodyControl.class);
dir.set(1.0f, 0.0f, 0.0f);
//gp.geom.getWorldRotation().multLocal(dir);
phy0.getPhysicsRotation().multLocal(dir);
dir.y = 0f;
dir.normalizeLocal();
forward.set(dir).multLocal(forwardLg);
turn.set(0.0f, turnLg, 0.0f);
phy0.applyCentralForce(forward);
float dist = 0.0f - phy0.getPhysicsLocation().y;
if (dist > 1.0) {
dist = 1.0f;
}
if (dist < -1.0) {
dist = -1.0f;
}
gravity.set(0.0f, /9.0f * phy0.getMass()/ 9.0f * dist, 0.0f);
phy0.setGravity(gravity);
phy0.setLinearDamping(linearDamping);
phy0.setAngularVelocity(turn);
}
[/java]

Disclaimer: I’m far to be a physic expert.