Example of an FPS camera

I wrote a small example for FPS-systems with the sensitivity parameter and the ability to invert the mouse. Also, the sensitivity can be adjusted separately for the axes, as well as the inversion.

Example of vertical inversion :

cameraNodeAxisP.rotate(deltaMouseXY.y * sensitivity, 0, 0);

package test;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.light.AmbientLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

public class Main extends SimpleApplication {

	private static Main app;

	public static void main(String[] args) {

		app = new Main();
		app.start();

	}

	private CameraNode cameraNodeAxisP;
	private float sensitivity;
	private boolean start;
	private Node playerNodeAxisH;
	protected Vector2f deltaMouseXY;

	@Override
	public void simpleInitApp() {

		viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));

		assetManager.registerLocator("town.zip", ZipLocator.class);

		Spatial sceneModel = assetManager.loadModel("main.scene");
		sceneModel.setLocalScale(2f);
		sceneModel.setLocalTranslation(new Vector3f(5, -20, 0));
		rootNode.attachChild(sceneModel);

		AmbientLight al = new AmbientLight();
		al.setColor(new ColorRGBA(2.0f, 2.0f, 2.0f, 1.0f));
		rootNode.addLight(al);

		flyCam.setEnabled(false);

		inputManager.addRawInputListener(inputListener);
		
		// Save cursor data
		deltaMouseXY = new Vector2f();

		// Control: rotation mouse H
		playerNodeAxisH = new Node("player");
		rootNode.attachChild(playerNodeAxisH);

		// Control: rotation mouse P
		cameraNodeAxisP = new CameraNode("camera", app.getCamera());
		playerNodeAxisH.attachChild(cameraNodeAxisP);

		// Mouse sensitivity
		sensitivity = 0.001f;

	}

	private RawInputListener inputListener = new RawInputListener() {

		public void onMouseMotionEvent(MouseMotionEvent evt) {
			deltaMouseXY.set(evt.getDX(), evt.getDY());
		}

		public void beginInput() {
			deltaMouseXY.set(0.0f, 0.0f);
		}

		public void endInput() {}
		public void onJoyAxisEvent(JoyAxisEvent evt) {}
		public void onJoyButtonEvent(JoyButtonEvent evt) {}
		public void onMouseButtonEvent(MouseButtonEvent evt) {}
		public void onKeyEvent(KeyInputEvent evt) {}
		public void onTouchEvent(TouchEvent evt) {}

	};

	@Override
	public void simpleUpdate(float tpf) {

		// Ugliness
		if (start == false) {
			inputManager.setCursorVisible(false);
			start = true;
		}

		// Rotating nodes
		if (deltaMouseXY.y != 0.0f) {
			cameraNodeAxisP.rotate(-deltaMouseXY.y * sensitivity, 0, 0);
		}

		// Rotating nodes
		if (deltaMouseXY.x != 0.0f) {
			playerNodeAxisH.rotate(0, -deltaMouseXY.x * sensitivity, 0);
		}

	}

}
2 Likes

Interesting.

a little advise, the display resolution can get from camera.

float widthDisplay = camera.getWidth();
float heightDisplay = camera.getHeight();
1 Like

I suppose when dividing the display, it will be better.

1 Like

Note: Lemur’s InputMapper (which can be used even if you don’t use the rest of Lemur) supports scaling the mappings which can be used to set the sensitivity of joystick axes or mouse axes (even separately).

Mapping mapping = inputMapper.map(myFunction, Joystick.JOYSTICK_Y);
mapping.setScale(0.5);
...

Since you can treat keys and input axes the same, it applies to keys as well.

It’s a bit different than what you are doing but would let the look code be sensitivity independent meaning you could have different sensitivities for keyboab-based axes versus joystick axes versus mouse axes.

It also seems strange to base the sensitivity on camera size since you will turn just as fast no matter what size the screen is… I guess for trying to pinpoint a specific target… but anyway, you could always update the Lemur InputMapper mappings based on screen size I guess.

Also, camera.getRotation().getZ() and camera.getRotation().getX() are not what you think they are. These are magic quaternion values and it makes no sense to treat them like angles. You are just lucky that they happen to be reasonably lined up in your example but it’s not right at all.

3 Likes

Now the question arose how to get Euler’s corners. Probably, I need to read the mathematical section…

1 Like

toAngles()… but this won’t work for all angles. You cannot reliably get predictable euler angles as you want them from a Quaternion.

(starts tape player) Quaternions are compact representation of orientation. Euler angles are a totally ambiguous, often redundant, representation of orientation. Thus if you put some set of euler angles into a quaternion then you will not necessarily get them back out again.

…if you want to base orientation on yaw and pitch as you are doing then you need to keep yaw and pitch, modify those, and create your rotation from them. You can’t go into a quaternion and out again.

Fortunately, I have an example of exactly this using Lemur’s InputMapper.

2 Likes

Looking at the camera in jME3, this is a kind of mythical object. Why does not it have spatial properties?
I can not attach it to the node, if there was such an opportunity, it made life easier.

1 Like

There is.
And this.

2 Likes

I transferred the FPS camera, but as it should, it was not without problems. If you disable vertical synchronization, this works poorly. I think this problem is connected: Engine having lagspikes on high-end gaming pc

After a simple experiment with removing from the code tpf, I found out that this would be a problem, I suppose it does not provide the correct data.

How else can I get another delta time?

1 Like

Use your own class for time control. do not use tpf as delta, intead of that use timeActionStart, timeActionEnd and actual time. you can calculate every position as f(t).

3 Likes

Thanks, I’ll try. I already tried with System.currentTimeMillis (), but it turned out be the same rake.

1 Like

This may seem strange but tpf does not do its job.

private long last_time;
last_time = System.nanoTime();
...

long time = System.nanoTime();
		
float delta_time = (float) ((time - last_time)/10000000.0f);
		
last_time = time;

This solves the problem!

1 Like

I continue to stumble … I now do not have enough to reset the cursor in the center of the screen.

For example:
SetCursorPosition (x, y)

1 Like

Why are you trying to do what JME is already doing?

Anyway, you seem to want to do everything the hardest way possible… so I guess you will have a lot of issues.

1 Like

Thus, the mouse path counter is like a speedometer of a car. This can create problems with a long game.

1 Like

I have trouble making sense of this statement.

1 Like

inputManager.getCursorPosition()

This will grow with time: (-69352.0, -234.0)

1 Like

I’m pretty sure you are mistaken. When the cursor is disabled, InputManager is constantly resetting the cursor position as I recall.

1 Like

These are real numbers, I moved the mouse for 1 minute.

1 Like

Yeah, I’m looking at the code and you’re right. Part of a larger problem I guess.

Still, I guess it comes up rarely that a user would turn one direction significantly more often than another unless the game is clamping input. JME should probably provide a way to reset the internal counters in that case.

1 Like