Aiming with the mouse

I am currently working on a FPS and having trouble with aiming with the mouse. I want the way the flyby camera aims without having the flyby camera enabled. The camera is locked to my player via a camNode, therefore the flyby cam is made irrelevant. If someone could provide code in order to solve this problem I would greatly appreciate it. IHere’s the code if you can see a problem.
[java]
/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package mygame;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.cursors.plugins.JmeCursor;
import com.jme3.input.InputManager;
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.InputListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.renderer.Camera;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.system.AppSettings;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;

/**
*

  • @author Nick
    */
    public class GameplayControl extends AbstractAppState implements ScreenController,ActionListener,AnalogListener{
    private Screen screen;
    private Nifty nifty;
    private SimpleApplication app;
    private InputManager inputManager;
    private AssetManager assetManager;
    private Node rootNode;
    private BulletAppState bulletAppState;
    private Camera cam;
    private AppSettings settings;

    private Spatial terrain;
    private Spatial testGun;
    private Node gunPivot;
    private CameraNode camNode;
    private int walkSpeed=20;
    private CharacterControl player;
    float zeroX;
    float zeroY;

    public GameplayControl(){
    //contructor to accept parameters//

    }

    public void bind(Nifty nifty, Screen screen) {
    this.screen=screen;
    this.nifty=nifty;
    }

    public void onStartScreen() {}

    public void onEndScreen() {}

    @Override
    public void initialize(AppStateManager stateManager, Application app){
    super.initialize(stateManager,app);
    this.app=(SimpleApplication) app;
    this.inputManager=this.app.getInputManager();
    this.rootNode=this.app.getRootNode();
    this.assetManager=this.app.getAssetManager();
    this.cam=this.app.getCamera();
    this.settings=this.app.getContext().getSettings();
    zeroX=settings.getWidth()/2;
    zeroY=settings.getHeight()/2;
    inputManager.setCursorVisible(false);
    //createGui();

     bulletAppState=new BulletAppState();
     stateManager.attach(bulletAppState);
     createControls();
     setupScene();
     setUpLight();
    
    
    testGun = assetManager.loadModel("Models/P416.mesh.j3o");
    testGun.scale(.2f);
    testGun.rotate(0,FastMath.DEG_TO_RAD*-90f,0);
    
    
    
    camNode = new CameraNode("Camera Node", cam);
    camNode.setControlDir(ControlDirection.SpatialToCamera);
    camNode.rotate(0,FastMath.DEG_TO_RAD*-180f,0);
    camNode.setLocalTranslation(-.475f,.88f,2.1f);
    
    CapsuleCollisionShape capsuleShape= new CapsuleCollisionShape(1.5f,6f,1); 
    player=new CharacterControl(capsuleShape,0.05f);
    player.setPhysicsLocation(camNode.getLocalTranslation());
    player.setGravity(200);
    player.setFallSpeed(70);
    player.setJumpSpeed(50);
    bulletAppState.getPhysicsSpace().add(player);
    
    
    
    
    gunPivot =  new Node("pivot");
    gunPivot.attachChild(camNode);
    gunPivot.attachChild(testGun);
    rootNode.attachChild(gunPivot);
    

    }

    @Override
    public void update(float tpf){
    gunPivot.setLocalTranslation(player.getPhysicsLocation());
    System.out.println(inputManager.getCursorPosition());
    aim();
    }
    @Override
    public void setEnabled(boolean enabled){
    if(enabled){
    //do everything to be done while this state is RUNNING

     }else{
         //remover whatever is not needed while this state is PAUSED
         nifty.exit();
     }
     
     //Nifty Gui ScreenControl methods
    

    }
    public void createGui(){
    NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(
    app.getAssetManager(),app.getInputManager(),app.getAudioRenderer(),app.getGuiViewPort());
    //Create a new NiftyGui object
    Nifty nifty = niftyDisplay.getNifty();
    //Read your XML and initialize custom ScreenController
    nifty.fromXml(“Interface/HudScreen.xml”, “helmetView”, this);
    //atach the Nifty display to the gui view port as a processor
    app.getGuiViewPort().addProcessor(niftyDisplay);

         nifty.loadStyleFile("nifty-default-styles.xml");
         nifty.loadControlFile("nifty-default-controls.xml");
    

    }

    public void onAction(String name, boolean isPressed, float tpf) {

    }

    public void onAnalog(String name, float value, float tpf) {
    if(name.equals(“Left”)){player.setPhysicsLocation(new Vector3f(player.getPhysicsLocation().getX()-walkSpeedvalue,player.getPhysicsLocation().getY(),player.getPhysicsLocation().getZ()));}
    if(name.equals(“Right”)){player.setPhysicsLocation(new Vector3f(player.getPhysicsLocation().getX()+walkSpeed
    value,player.getPhysicsLocation().getY(),player.getPhysicsLocation().getZ()));}
    if(name.equals(“Forward”)){player.setPhysicsLocation(new Vector3f(player.getPhysicsLocation().getX(),player.getPhysicsLocation().getY(),player.getPhysicsLocation().getZ()-walkSpeedvalue));}
    if(name.equals(“Back”)){player.setPhysicsLocation(new Vector3f(player.getPhysicsLocation().getX(),player.getPhysicsLocation().getY(),player.getPhysicsLocation().getZ()+walkSpeed
    value));}
    if(name.equals(“Jump”)){player.jump();}

    }

    public void createControls(){

     inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A),
                                     new KeyTrigger(KeyInput.KEY_LEFT));
     inputManager.addMapping("Right",new KeyTrigger(KeyInput.KEY_D),
                                     new KeyTrigger(KeyInput.KEY_RIGHT));
     inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_W),
                                        new KeyTrigger(KeyInput.KEY_UP));
     inputManager.addMapping("Back", new KeyTrigger(KeyInput.KEY_S),
                                     new KeyTrigger(KeyInput.KEY_DOWN));
     inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
     inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
     inputManager.addListener(this, "Left");
     inputManager.addListener(this, "Right");
     inputManager.addListener(this, "Forward");
     inputManager.addListener(this, "Back");
     inputManager.addListener(this, "Jump");
     inputManager.addListener(this, "Shoot");
    

    }

    public void setupScene(){
    terrain = assetManager.loadModel(“Models/tempTerrain.j3o”);
    terrain.scale(100f);
    terrain.setLocalTranslation(new Vector3f(0,-10f,-200f));
    CollisionShape sceneShape =
    CollisionShapeFactory.createMeshShape(terrain);
    terrain.addControl(new RigidBodyControl(sceneShape, 0));
    bulletAppState.getPhysicsSpace().add(terrain);
    rootNode.attachChild(terrain);
    }

    private void setUpLight() {
    // We add light so we see the scene
    AmbientLight al = new AmbientLight();
    al.setColor(ColorRGBA.Black.mult(.8f));
    rootNode.addLight(al);

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

    private void aim(){

    }
    }

[/java]

Perhaps you need flyCam.setEnabled(false); in your initialize(); method.

i have that in the Main code. I’m looking for code that mimics the movement of the flyby camera rotation with the mouse

Something like this:
[java]
CameraControl cameraControl = new CameraControl(this.getCamera(), ControlDirection.SpatialToCamera);
cameraNode = new CameraNode(“Camera”, cameraControl);
cameraNode.setLocalTranslation(new Vector3f(0, 6f, 0f));

player.getCharacterControl().warp(cameraNode.getLocalTranslation());
player.getNode().attachChild(cameraNode);
[/java]

and for your inputlistener:

[java]
private Vector3f viewDirection = new Vector3f(0, 0, 1);
private Quaternion viewAngle = new Quaternion();

@Override
public void onAnalog(String binding, float value, float tpf)
{
    switch (binding)
    {
        case "Mouse_Move_Left":
        {
            Quaternion rotateL = new Quaternion().fromAngleAxis(-FastMath.PI * value, Vector3f.UNIT_Y);
            rotateL.multLocal(viewDirection);
            break;
        }
        case "Mouse_Move_Right":
        {
            Quaternion rotateR = new Quaternion().fromAngleAxis(FastMath.PI * value, Vector3f.UNIT_Y);
            rotateR.multLocal(viewDirection);
            break;
        }
        case "Mouse_Move_Up":
        {
            viewAngle = new Quaternion().fromAngleAxis(value, Vector3f.UNIT_X);
            break;
        }
        case "Mouse_Move_Down":
        {
            viewAngle = new Quaternion().fromAngleAxis(-value, Vector3f.UNIT_X);
            break;
        }
       
    }
}

[/java]

Then in the update loop:
[java]
player.getCharacterControl().setViewDirection(viewDirection);
gameManager.getCameraNode().rotate(viewAngle);

viewAngle.set(0, 0, 0, 1);

[/java]

This basically rotates your player when you move your mouse left and right, and rotates the camera when you look up or down.

Do i have to create those listeners for the mouse or are they pre-made?

See here: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:beginner:hello_input_system

Yeah I’m stupid haha

I also want to set a limit on how high/low the camera can look. I know absolutely nothing about Quaternions but I know you have to apply them to do this check in the update loop.

You may find this useful: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

You may also be able to use Camera.getDirection() to determine the elevation angle of the camera.

In my own game, I construct the camera direction from its altitude and azimuth angles like so:

            Quaternion elevate = new Quaternion();
            elevate.fromAngleNormalAxis(elevationAngle, Vector3f.UNIT_Z);
            Vector3f elevation = elevate.mult(Vector3f.UNIT_X);
            Vector3f direction = yRotate(elevation, azimuthAngle);

/**
 * Rotate a vector CLOCKWISE about the +Y axis. Note: Used for applying
 * azimuths, which is why its rotation angle convention is non-standard.
 *
 * @param vector input (not null, not altered)
 * @param radians clockwise (LH) angle of rotation in radians
 * @return a new vector
 */

Vector3f yRotate(Vector3f vector, float radians) {
float cosine = FastMath.cos(radians);
float sine = FastMath.sin(radians);
float x = cosine * vector.x - sine * vector.z;
float y = vector.y;
float z = cosine * vector.z + sine * vector.x;
Vector3f result = new Vector3f(x, y, z);
return result;
}

1 Like

@jayfella the camera aims up and down but the left and right does not rotate. Here’s the code if you can find an error
[java]public void onAnalog(String name, float value, float tpf) {
if(name.equals(“Left”)){player.setPhysicsLocation(player.getPhysicsLocation().add(-walkSpeedvalue, 0, 0));}
if(name.equals(“Right”)){player.setPhysicsLocation(player.getPhysicsLocation().add(walkSpeed
value, 0, 0));}
if(name.equals(“Forward”)){player.setPhysicsLocation(player.getPhysicsLocation().add(0, 0, -walkSpeedvalue));}
if(name.equals(“Back”)){player.setPhysicsLocation(player.getPhysicsLocation().add(0, 0, walkSpeed
value));}

    if(name.equals("Jump")){player.jump();}
    
    if(name.equals("Mouse_Move_Left")){Quaternion rotateL = new Quaternion().fromAngleAxis(-FastMath.PI * value, Vector3f.UNIT_Y);
                                       rotateL.multLocal(viewDirection);}
    if(name.equals("Mouse_Move_Right")){Quaternion rotateR = new Quaternion().fromAngleAxis(FastMath.PI * value, Vector3f.UNIT_Y);
                                        rotateR.multLocal(viewDirection);}
    if(name.equals("Mouse_Move_Up")){viewAngle = new Quaternion().fromAngleAxis(-value, Vector3f.UNIT_X);}
    if(name.equals("Mouse_Move_Down")){viewAngle = new Quaternion().fromAngleAxis(value, Vector3f.UNIT_X);}

}[/java] 

and here’s the update code
[java]public void update(float tpf){
gunPivot.setLocalTranslation(player.getPhysicsLocation());
System.out.println(inputManager.getCursorPosition());

player.setViewDirection(viewDirection);

gunPivot.rotate(viewAngle);

viewAngle.set(0, 0, 0, 1);
}[/java]

The code looks fine, but I notice you didnt create a node and attach the playercontrol and cameranode to it - hence the reason you have to move the camera on every update loop. Other than that, I can’t see why it shouldnt work. I looked at my code again, and other than what I just said, it’s pretty much verbatim.

Left/Right should be on Y, not X. That is unless you use some weird modeling and/or axis.

[java]
public void onAnalog(String name, float value, float tpf) {
switch (name) {
case “PitchDown”: //NOI18N
spatial.rotate(shipClass.getPitch() * tpf, 0, 0);
stopAutopilot();
break;
case “PitchUp”: //NOI18N
spatial.rotate(-shipClass.getPitch() * tpf, 0, 0);
stopAutopilot();
break;
case “MoveLeft”: //NOI18N
spatial.rotate(0, shipClass.getYaw() * tpf, 0);
stopAutopilot();
break;
case “MoveRight”: //NOI18N
spatial.rotate(0, -shipClass.getYaw() * tpf, 0);
stopAutopilot();
break;
case “RollRight”: //NOI18N
spatial.rotate(0, 0, shipClass.getRoll() * tpf);
stopAutopilot();
break;
case “RollLeft”: //NOI18N
spatial.rotate(0, 0, -shipClass.getRoll() * tpf);
stopAutopilot();
break;
}
}
[/java]

Oh, I forgot:

[java]
characterControl.setApplyPhysicsLocal(true);
[/java]

[java]CameraControl camControl = new CameraControl(cam,ControlDirection.SpatialToCamera);
camNode = new CameraNode(“Camera”, camControl);
camNode.rotate(0,FastMath.DEG_TO_RAD*-180f,0);
camNode.setLocalTranslation(-.475f,.88f,2.1f);

   CapsuleCollisionShape capsuleShape= new CapsuleCollisionShape(1.5f,6f,1); 
   player=new CharacterControl(capsuleShape,0.05f);
   player.setPhysicsLocation(camNode.getLocalTranslation());
   player.setGravity(200);
   player.setFallSpeed(70);
   player.setJumpSpeed(50);
   bulletAppState.getPhysicsSpace().add(player);
   player.setApplyPhysicsLocal(true);
   player.warp(camNode.getLocalTranslation());
   
   gunPivot =  new Node("pivot");
   gunPivot.attachChild(camNode);
   gunPivot.attachChild(testGun);
   rootNode.attachChild(gunPivot);
   
   
}



@Override
public void update(float tpf){
    gunPivot.setLocalTranslation(player.getPhysicsLocation());
    System.out.println(inputManager.getCursorPosition());
    
player.setViewDirection(viewDirection);

gunPivot.rotate(viewAngle);

viewAngle.set(0, 0, 0, 1);
}
@Override
public void setEnabled(boolean enabled){
    if(enabled){
        //do everything to be done while this state is RUNNING
        
        
    }else{
        //remover whatever is not needed while this state is PAUSED
        nifty.exit();
    }
    
    //Nifty Gui ScreenControl methods
    
}
public void createGui(){
    NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(
                app.getAssetManager(),app.getInputManager(),app.getAudioRenderer(),app.getGuiViewPort());
        //Create a new NiftyGui object
        Nifty nifty = niftyDisplay.getNifty();
        //Read your XML and initialize custom ScreenController
        nifty.fromXml("Interface/HudScreen.xml", "helmetView", this);
        //atach the Nifty display to the gui view port as a processor
        app.getGuiViewPort().addProcessor(niftyDisplay);
        
        nifty.loadStyleFile("nifty-default-styles.xml");
        nifty.loadControlFile("nifty-default-controls.xml");
}

public void onAction(String name, boolean isPressed, float tpf) {
     
}

public void onAnalog(String name, float value, float tpf) {
    if(name.equals("Left")){player.setPhysicsLocation(player.getPhysicsLocation().add(-walkSpeed*value, 0, 0));}
    if(name.equals("Right")){player.setPhysicsLocation(player.getPhysicsLocation().add(walkSpeed*value, 0, 0));}
    if(name.equals("Forward")){player.setPhysicsLocation(player.getPhysicsLocation().add(0, 0, -walkSpeed*value));}
    if(name.equals("Back")){player.setPhysicsLocation(player.getPhysicsLocation().add(0, 0, walkSpeed*value));}

    if(name.equals("Jump")){player.jump();}
    
    if(name.equals("Mouse_Move_Left")){gunPivot.rotate(0, -value, 0);}
    if(name.equals("Mouse_Move_Right")){gunPivot.rotate(0, value, 0);}
    if(name.equals("Mouse_Move_Up")){viewAngle = new Quaternion().fromAngleAxis(-value, Vector3f.UNIT_X);}
    if(name.equals("Mouse_Move_Down")){viewAngle = new Quaternion().fromAngleAxis(value, Vector3f.UNIT_X);}

}[/java] 

I wasn’t getting rotation left to right so I changed the action to the above. I’m also working on a limit to how high and low the player can look. The movement I have is also flawed because it only moves the player along the axis. How would I change the code so that the player moves on it’s own orientation, not the worlds?

Regarding moving the character, see here, specifically the update loop of the first code block.
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:beginner:hello_collision

Regarding limiting the vertical limitations, just check if the value of the quaternion on the axis exceeds FastMath.HALF_PI.

The only problem with the hello collision movement is that when the camera is pointed downward towards the ground, if the player backs up, it lifts up off the ground.

The aiming works great except for one flaw. When moving around for a while or rapidly moving the mouse in circles, the player and gunNode rotate such that the world around them eventually turns upside-down. Essentially the player could end up walking around on his head. Any solutions?

@Tsar-Belail There are two things you must do.

Firstly, restrict the camera movement on the Y axis so they cannot exceed 0 degrees (looking up) and 180 degrees (looking down). That will stop them being able to flip the camera when continually pulling the mouse back or forward.

The second thing is that you should rotate the camera on the Y axis, and the spatial on the X axis. If you don’t, the camera rotation will start to act strangely. Imagine rotating a ball with a dot on it to represent an eye. If you look up a little, then rotate to the right, you are kind of rotating on an angle. Kind of obvious once you realize what’s happening. I made the same mistake myself.

When tracking directions and rotations in 3d space its always easier to track a direction and up vector and performing operations on these instead of trying to track and accumulate rotations which will inevitably cause issues like gimbal lock and similar things.

@jayfella How would I go about restricting the camera. I feel stupid asking this but I know next to nothing about quaternions