In this recipe, we will make an AppState
object, which will handle the player input for a character. It's a great way to add functionality to the application in a modular way. The AppState
object we create here could easily be added during the game and removed or disabled during cut scenes or while in the game menu.
We won't require any special assets for this recipe, but it will be beneficial to have a basic understanding of how AppState works and its purpose in jMonkeyEngine. This particular implementation of the recipe will use the character-control class created in the previous example. It can still be used to manipulate a spatial
object directly without the GameCharacterControl
class. This recipe will provide pointers on where to do this.
To attach an input AppState
object, perform the following steps:
InputAppState
, extending AbstractAppState
, and implementing ActionListener
and AnalogListener
.InputAppState
class needs a couple of fields to be functional. First of all, we're going to keep a reference to the application's InputManager
in a field called inputManager
. We're also adding a GameCharacterControl
field called character
. This can be replaced by any spatial
. Lastly, we're going to have a value that controls the sensitivity of the analog controls. We do this with a float called sensitivity. Add getters and setters for character and sensitivity.enum
and supply the name of the value as the mapping. We use it to create some basic FPS controls as follows:public enum InputMapping{ RotateLeft, RotateRight, LookUp, LookDown, StrafeLeft, StrafeRight, MoveForward, MoveBackward; }
addInputMappings
to add these to inputManager
and make sure it listens to them. To do this, we supply the name of the enum
value as the mapping and bind it to a certain input as follows:private void addInputMappings(){ inputManager.addMapping(InputMapping.RotateLeft.name(), new MouseAxisTrigger(MouseInput.AXIS_X, true)); inputManager.addMapping(InputMapping.RotateRight.name(), new MouseAxisTrigger(MouseInput.AXIS_X, false)); inputManager.addMapping(InputMapping.LookUp.name(), new MouseAxisTrigger(MouseInput.AXIS_Y, false)); inputManager.addMapping(InputMapping.LookDown.name(), new MouseAxisTrigger(MouseInput.AXIS_Y, true)); inputManager.addMapping(InputMapping.StrafeLeft.name(), new KeyTrigger(KeyInput.KEY_A), new KeyTrigger(KeyInput.KEY_LEFT)); inputManager.addMapping(InputMapping.StrafeRight.name(), new KeyTrigger(KeyInput.KEY_D), new KeyTrigger(KeyInput.KEY_RIGHT)); inputManager.addMapping(InputMapping.MoveForward.name(), new KeyTrigger(KeyInput.KEY_W), new KeyTrigger(KeyInput.KEY_UP)); inputManager.addMapping(InputMapping.MoveBackward.name(), new KeyTrigger(KeyInput.KEY_S), new KeyTrigger(KeyInput.KEY_DOWN)); }
InputManager
to listen to the commands, or it won't actually fire on any of the inputs:for (InputMapping i : InputMapping.values()) { inputManager.addListener(this, i.name()); }
AppState
is attached, it runs the initialize
method (in a thread-safe way). Here, we get the reference to the application's InputManager
object and run the addMappings
method we just created, as follows:public void initialize(AppStateManager stateManager, Application app) { super.initialize(stateManager, app); this.inputManager = app.getInputManager(); addInputMappings(); }
InputManager
detects any of the actions and sends them our way, we will just forward them to the GameCharacterControl
object by applying the sensitivity value to the analog input as follows: public void onAnalog(String name, float value, float tpf) { if(character != null){ character.onAnalog(name, value * sensitivity, tpf); } } public void onAction(String name, boolean isPressed, float tpf) { if(character != null){ character.onAction(name, isPressed, tpf); } }
AppState
is not to be used anymore. We do this by overriding the cleanup method. Here, we remove all the mappings and remove this instance from listeners of inputManager
as follows:public void cleanup() { super.cleanup(); for (InputMapping i : InputMapping.values()) { if (inputManager.hasMapping(i.name())) { inputManager.deleteMapping(i.name()); } } inputManager.removeListener(this); }
The AppState
object works with the application in a way that is similar to how Control
works with spatial
. They give extended functionalities in a modular way. Once it has been attached to stateManager
, its update
method will be called every cycle. This gives us access to the application's thread as well. It also has the stateAttached
and stateDetached
methods, which can be used to turn functionality on and off easily.
18.227.72.212