Attaching an input AppState object

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.

Getting ready

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.

How to do it...

To attach an input AppState object, perform the following steps:

  1. Start off by creating a class called InputAppState, extending AbstractAppState, and implementing ActionListener and AnalogListener.
  2. The 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.
  3. Next, we'll set up the kinds of input we're going to handle. Strings are used by jMonkeyEngine for the mappings, but enums can be easier to manage across classes. Here, we'll use an 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;
    }
  4. We create a method called 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));
    
    }

    Note

    It's okay to assign several keys to the same mapping. For example, this recipe assigns both the arrow keys and the classical WASD pattern to the movement keys.

  5. Finally, in the same method, we tell 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());
    }
  6. Now, once 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();
    }
  7. Once 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);
      }
    }
  8. We're actually almost done with this recipe. We just need to make sure that we reset everything when 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);
    }

How it works...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.12.186