- jMonkeyEngine 3.0 Beginner’s Guide
- Ruth Kusterer
- 1055字
- 2025-04-04 22:38:53
Time for action – get these cubes under control
One type of dedicated jMonkeyEngine class that encapsulates a spatial's behavior is the Control
class from the com.jme3.scene.control
package. You can create a Control
object based on the code mentioned earlier, and add it to an individual cube. This prompts the cube to automatically test its own distance to the camera, and move away when the player looks at it! Seeing is believing:
- Create a
CubeChaserControl
class. Make it extend theAbstractControl
class from thecom.jme3.scene.control
package. - Implement abstract methods of the
CubeChaserControl
class with the following template:@Override protected void controlUpdate(float tpf) { } protected void controlRender(RenderManager rm, ViewPort vp) { } public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException( "Not supported yet."); }
- Move the
ray
field fromCubeChaser
class to theCubeChaserControl
class. Create additional class fields, acom.jme3.renderer.Camera
field calledcam
, and acom.jme3.scene.Node
field calledrootNode
.private Ray ray = new Ray(); private final Camera cam; private final Node rootNode;
- Add a custom constructor that initializes
cam
androotNode
.public CubeChaserControl(Camera cam, Node rootNode) { this.cam = cam; this.rootNode = rootNode; }
- Move the ray casting block from the
simpleUpdate()
method of theCubeChaser
class to thecontrolUpdate()
method of theCubeChaserControl
class.protected void controlUpdate(float tpf) { CollisionResults results = new CollisionResults(); ray.setOrigin(cam.getLocation()); ray.setDirection(cam.getDirection()); rootNode.collideWith(ray, results); if (results.size() > 0) { Geometry target = results.getClosestCollision().getGeometry(); // interact with target } }
- Test whether I (the controlled spatial, represented by the predefined
spatial
variable) am a target (one of the cubes that the player is looking at). If yes, test whether the player (represented by the camera) is close enough to chase me. Replace the interact with target comment with the following conditional statement:if (target.equals(spatial)) { if (cam.getLocation().distance(spatial.getLocalTranslation()) < 10) { spatial.move(cam.getDirection()); } }
- Back in the
CubeChaser
class, you want to add this control's behavior to some of the spatials. We create lots of spatials in themakeCubes()
method; let's amend it so that every fourth cube receives this control while the rest remain unfazed. You can create instances of the control with the main camera androotNode
object as arguments. Replace therootNode.attachChild()
code line in themakeCubes()
method with the following code:Geometry geom = myBox("Cube" + i, loc, ColorRGBA.randomColor()); if (FastMath.nextRandomInt(1, 4) == 4) { geom.addControl(new CubeChaserControl(cam, rootNode)); } rootNode.attachChild(geom);
Before you run this sample, make sure that you have indeed removed all code from the simpleUpdate()
method of the CubeChaser
class; it is no longer needed. By using controls, you can now chase specific group of cubes—defined by the subset that carries the CubeChaserControl
class. Other cubes are unaffected and ignore you.
Tip
There are two equivalent ways to implement controls. In most cases, you will just extend the AbstractControl
class from the com.jme3.scene.control
package. Alternatively, you could also implement the control interface directly; you only implement the interface yourself in cases where your control class already extends another class, and therefore cannot extend AbstractControl
.
If you get tired of finding out which cubes are chasable, go back to the CubeChaserControl
class and add spatial.rotate(tpf, tpf, tpf);
as the last line of the controlUpdate()
method. Now the affected cubes spin and reveal themselves!
Tip
Note that various built-in methods provide you with a float argument, tpf. This float is always set to the current time per frame (tpf), equal to the number of seconds it took to update and render the last video frame. You can use this value in the method body to time actions, such as this rotation, depending on the speed of the user's hardware—the tpf is high on slow computers, and low on fast computers. This means, the cube rotates in few, wide steps on slow computers, and in many, tiny steps on fast computers.
What just happened?
Every control has a controlUpdate()
method that hooks code into the game's main loop, just as if it were in the simpleUpdate()
method. You always add a control instance to a spatial; the control doesn't do anything by itself. Inside the control instance, you reach up to that spatial by referring to the predefined spatial
variable. For example, if you add control instance c17
to geometry instance cube17
, then the spatial
variable inside c17
equals cube17
. Every transformation that c17
applies to spatial
, is applied directly to cube17
.
One control class (such as the CubeChaserControl
class) can control the behavior of several spatials that have something in common—such as being chasable in this example. However, each spatial needs its own control instance.
One spatial can be affected by zero or more controls at the same time. You want to teach an old spatial new tricks? If you ever decide to change a game mechanic, you only have to modify one control; rebuild the application, and all controlled spatials immediately adhere to it. With only a few lines of code, you can add behavior to, or remove behavior from, an arbitrary number of spatials. Encapsulating behavior into controls is very powerful!
If you need various groups of nodes that share behavior, don't create subclasses of the Spatial
class with added custom methods! You will get into hot water soon, because using inheritance, a spatial would be locked into the one role it inherited—for example, a shopkeeper NPC could never fight back to defend its shop, because its parent is not a fighter class. Controls on the other hand are modular and can add or remove roll-based behavior easily—every NPC with an ArcheryControl
method can use crossbows and bows; every NPC with the MerchantControl
method can buy and sell items, and so on.
The spatial
object has a few interesting accessors, including a getControl()
method that gives you access to other controls added to the same spatial. There is no need to pass objects as references if they are already accessible via a custom control's accessors. When you look at the custom constructor of the CubeChaserControl
class, you see that we actually pass the camera and the rootNode
object into it. It's untypical for a control to need that much information about the scene graph or the camera. The ideal control class is self-contained and gets all the information it needs directly from its spatial, using custom accessors that you define. Let's make it better!