- jMonkeyEngine 3.0 Beginner’s Guide
- Ruth Kusterer
- 894字
- 2025-04-04 22:38:53
Time for action – pick a brick (pointer with ray casting)
Now your player can click items, but how do you find out what he clicked? In principle, we can use the same ray casting algorithm as for the previous example with the crosshairs. Only instead of casting the ray from the camera forward, we now cast it forward from the 3D coordinates of the click location.
- Implement the
AnalogListener
object on theAnalog()
method to test for our left-click action,MAPPING_ROTATE
.private AnalogListener analogListener = new AnalogListener() { public void onAnalog(String name, float intensity, float tpf) { if (name.equals(MAPPING_ROTATE)) { // implement action here } } };
- Replace the implement action here comment by the following code. To identify what was clicked, we use methods from the
com.jme3.collision
package. As before, we create an empty results list.CollisionResults results = new CollisionResults();
- The next few steps are new. Since we are working with the mouse pointer, we get the 2D coordinates of the cursor position from the
inputManager
object:Vector2f click2d = inputManager.getCursorPosition();
- We use the
cam.getWorldCoordinates()
method to convert these (x,y) coordinates into (x,y,z) coordinates with a depth (z) of zero.Vector3f click3d = cam.getWorldCoordinates( new Vector2f(click2d.getX(), click2d.getY()), 0f)
- We want to cast the ray forward from this point. The following code calculates the direction vector of a temporary point that is 1 WU deep into the screen from the clicked location:
Vector3f dir = cam.getWorldCoordinates( new Vector2f(click2d.getX(), click2d.getY()), 1f). subtractLocal(click3d);
- Instead of aiming the ray forward from the camera location in the camera direction, we now aim the ray starting from the click location into the calculated forward direction.
Ray ray = new Ray(click3d, dir);
- Now that we have the ray, the rest of the code is the same as for the crosshairs example. We calculate intersections between this line-of-sight ray and all geometries attached to the
rootNode
object, and collect them in the results list.rootNode.collideWith(ray, results);
- If the user has clicked anything, the results list is not empty. In this case we identify the selected geometry—the closest item must be the target that the player picked! If the results list is empty, we just print some feedback to the console.
if (results.size() > 0) { Geometry target = results.getClosestCollision().getGeometry(); if (target.getName().equals("Red Cube")) { target.rotate(0, -intensity, 0); // rotate left } else if (target.getName().equals("Blue Cube")) { target.rotate(0, intensity, 0); // rotate right } } else { System.out.println("Selection: Nothing" ); }
Build and run the sample. When you point the mouse pointer at either the red or blue cube and click, the cube rotates as in the previous example. Congrats! Not even fancy-free mouse clicks can escape your notice now!
What just happened?
Previously, when aiming with crosshairs, we assumed that the crosshairs were located in the center of the screen, where the camera is located—we simply aimed the ray forward from the (known) 3D camera location, in the (known) 3D camera direction. A mouse click with a pointer, however, can only identify (x,y) coordinates on a 2D screen.
Vector2f click2d = inputManager.getCursorPosition();
Similarly, we can no longer simply use the known camera direction as the direction of the ray. But to be able to cast a ray, we need a 3D start location and a 3D direction. How did we obtain them?

The cam.getWorldCoordinates()
method can convert a 2D screen coordinate, plus a depth z from the camera forward, into 3D world coordinates. In our case, the depth z is zero—we assume that a click is right on the lens of the camera.
To specify the forward direction of the ray, we first calculate the coordinates of a temporary point tmp
. This point has the same (x,y) coordinates as the mouse click, but is 1 WU deeper into the scene (1 WU is an arbitrarily chosen depth value). We use the cam.getWorldCoordinates()
method once more with the click's 2D screen coordinate, but now we specify a depth of 1 WU:
Vector3f tmp = cam.getWorldCoordinates( new Vector2f(click2d.getX(), click2d.getY()), 1f);
We need to identify the direction from the 3D click location towards the temporary point 1 WU in front of it. Mathematically, you get this direction vector by subtracting the click's vector from tmp
point's vector as in the following code:
Vector3f dir = tmp.subtractLocal(click3d);
Subtracting one vector from another is a common operation if you need to identify the direction from one point to another. You can do it in one step:
Vector3f dir = cam.getWorldCoordinates( new Vector2f(click2d.getX(), click2d.getY()), 1f). subtractLocal(click3d);
Tip
In these small examples, we collide the ray with the whole scene attached to the rootNode
object. This ensures that all nodes are tested. On the other hand, too many tests may slow performance in large scenes. If you know that only mutually exclusive sets of nodes are candidates for a certain action, then restrict the collision test to this subset of nodes. For example, attach all spatials that respond to an open/close action to an Openable
node. Attach all spatials that respond to a take/drop action to a Takeable
node, and so on.
Experiment with either picking method, crosshairs or mouse pointer, and see which one suits your gameplay better.
Pop quiz – input handling
Q1. In which order do you define and register the pairings for your input handler?
- trigger-mapping, mapping-listener, listener-action
- mapping-listener, listener-action, action-trigger
- action-trigger, trigger-mapping, mapping-listener
- listener-action, action-trigger, trigger-mapping