StandardGame and input handling

I have some difficulties to understand how input should work when using StandardGame. If two GameStates bind the same key to an action, and both are active, the GameState which was attached first to the GameStateManager gets the key event, right? I don't have an idea what to do when I have some key events that should be catched by all active GameStates.

So:

  • an "active" GameState doesn't get an key event, if it's "stolen" by another one
  • the order of GameStateManager.getInstance().attachChild() is important
  • if you need another order of GameStates (e.g. because of some rendering tricks, let's say a skybox GameState), you have a problem



    Here is a little extension of TestStandardGame from the SimpleGame -> StandardGame tutorial, which illustrates that behavior (use some of the "debug" keys like B or L):



import com.jme.bounding.*;
import com.jme.math.*;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Cylinder;
import com.jmex.editors.swing.settings.*;
import com.jmex.game.*;
import com.jmex.game.state.*;

/**
 * TestStandardGame is meant to be an example replacement of
 * jmetest.base.TestSimpleGame using the StandardGame implementation
 * instead of SimpleGame.
 *
 * @author Matthew D. Hicks
 */
public class TestStandardGame {

First of all, this is not really specific to StandardGame but to the InputHandler as a whole.  I would say this should be considered a bug…the key event should propagate to all InputHandlers.



Anyone else know a reason why this should happen?

i had problems with keys not propagating trough multiple input handlers some time ago. the handlers just "consume" the input. i never understood why  :expressionless:



edit: "why" like in "why it should be that way"

Not to throw out unnecessary propaganda, but this should not be an issue for GameControl as it doesn't rely on InputHandlers, but the same code that InputHandler relies on. :wink:



You can create a GameControlManager for your game (for the in-game actions) and then have a GameControlManager for in-game menus as well and if you use the controllers I've added they'll work just fine as long as the GameState it's attached to is still enabled. :slight_smile:

i don't use game states. well… at least not those you mean :slight_smile:

Well, you don't have to use GameStates for GameControls either actually since the controllers I was speaking of just Controllers that can be added to any Node in jME.  :stuck_out_tongue:

just… shut up!  XD

The version in the repository HEAD does allow events to propagate to all subscribed handlers. I think you may be affected by a bug that was solved a week or two weeks ago. What version of jME are you using, Landei?



Also, I think there is no way to associate InputHandlers and GameStates. I've found that if I want to place input handling code in GameStates, it's easier to use a main input handler and route input events myself (which also gives me some more control on where I send input events).



Maybe all the InputHandlers and Listeners and GameControl stuff is great, but is too mixed and undocumented for me.



Example InputHandler using this aproach (it is dependent on my code, and I'm almost certain that I will try another solution for this, but maybe lets you make an idea):



/**
 * The MainInputHandler intercepts all the input events.
 */
public class MainInputHandler extends InputHandler {

   /**
    * Class logger
    */
   private static final Logger logger = Logger.getLogger(MainInputHandler.class.toString());
   
   /**
    * Joystick Input
    */
   JoystickInput joystickInput = null;
   
   /**
    * Key action
    */
   public class KeyInputAction extends InputAction {

      public void performAction(InputActionEvent evt) {

         //logger.info("Key pressed (index=" + evt.getTriggerIndex() + ",time=" + evt.getTime() + ",press=" + evt.getTriggerPressed() + ")");
         
         if ((evt.getTriggerIndex() == KeyInput.KEY_GRAVE) && (evt.getTriggerPressed() == true)) {
            
            // Console key pressed
            System.getMainState().getClientState().switchConsole();
            
         } else if (System.getMainState().getClientState().getConsoleState().isActive()) {
            
            // Key pressed while in console mode
            System.getConsole().processKey(evt.getTriggerCharacter());
            
         } else if (System.getMainState().getClientState().getSpectatorState().isActive()) {
            
            // Key pressed while in spectator mode
            System.getMainState().getClientState().getSpectatorState().processKeyInput(evt);
            
         } else if (System.getMainState().getClientState().getVehicleState().isActive()) {
            
            // Key pressed while in spectator mode
            System.getMainState().getClientState().getVehicleState().processKeyInput(evt);
            
         }
         
      }

   }

   /**
    * Mouse action
    */
   public class MouseInputAction extends InputAction {

      public void performAction(InputActionEvent evt) {

         //logger.info("Mouse event [axis=" + evt.getTriggerIndex() + ",delta=" + evt.getTriggerDelta() + "]");
         
         if (System.getMainState().getClientState().getSpectatorState().isActive()) {
            
            // Mouse event while in spectator mode
            System.getMainState().getClientState().getSpectatorState().processMouseInput(evt);
            
         }
         
         // Update camera input if a camera controller is being used
         if (System.getMainState().getClientState().getLevelRenderState().isActive()) {
            
            System.getMainState().getClientState().getLevelRenderState().getCameraController().processMouseInput(evt);
            
         }
      
         
      }

   }
   
   /**
    * Processes joystick input
    */
   public void processJoystick(float tpf) {
      
      if (System.getMainState().getClientState().getVehicleState().isActive()) {
         
         // Key pressed while in vehicle mode
         System.getMainState().getClientState().getVehicleState().processJoystickInput(joystickInput, tpf);
         
      }
      
   }
   
   /**
    * Constructor
    */
   public MainInputHandler() {

      addAction(new KeyInputAction(), InputHandler.DEVICE_KEYBOARD, InputHandler.BUTTON_ALL, InputHandler.AXIS_ALL, false);
      addAction(new MouseInputAction(), InputHandler.DEVICE_MOUSE, InputHandler.BUTTON_ALL, InputHandler.AXIS_ALL, false);
      joystickInput = JoystickInput.get();
      
   }

   /* (non-Javadoc)
    * @see com.jme.input.InputHandler#update(float)
    */
   @Override
   public void update(float time) {
      super.update(time);
      joystickInput.update();
      processJoystick (time);
   }
   
   

}



Regards,

J

Well, if you hadn't mentioned GameControl as part of that I would agree with you. Since we're posting code, I'll show you the code I just wrote this evening replacing a mess of input handling crap for a prototype game I'm working on:


      GameControlManager manager = getControlManager();
      
      // Throttle
      throttleController = new ThrottleController(ship, manager.getControl("Thrust Forward"), 100.0f, manager.getControl("Thrust Reverse"), -25.0f, 0.5f, Axis.Z);
      rootNode.addController(throttleController);
      loader.setProgress(0.825f);
      
      // Pitch Forward and Reverse
      pitchForwardReverseController = new RotationController(ship, manager.getControl("Pitch Forward"), manager.getControl("Pitch Reverse"), 1.0f, Axis.X);
      rootNode.addController(pitchForwardReverseController);
      loader.setProgress(0.85f);
      
      // Pitch Left and Right
      pitchLeftRightController = new RotationController(ship, manager.getControl("Pitch Left"), manager.getControl("Pitch Right"), 1.0f, Axis.Y);
      rootNode.addController(pitchLeftRightController);
      loader.setProgress(0.875f);
      
      // Rolling
      rollController = new RotationController(ship, manager.getControl("Roll Left"), manager.getControl("Roll Right"), 1.0f, Axis.Z);
      rootNode.addController(rollController);
      loader.setProgress(0.9f);
      
      // Strafing
      strafeController = new ThrottleController(ship, manager.getControl("Strafe Left"), 10.0f, manager.getControl("Strafe Right"), -10.0f, 1.0f, Axis.X);
      rootNode.addController(strafeController);
      loader.setProgress(0.925f);
      
      // Toggle Menu
      ActionRepeatController toggleMenuController = new ActionRepeatController(manager.getControl("Toggle Menu"), 5000, new MenuAction());
      rootNode.addController(toggleMenuController);
      loader.setProgress(0.95f);



Pretty simple and straight-forward in my opinion. ;) Also, it's got loader code in there as well, so forgive the added mess.  :P

My entire control system is nearly finished in that block of code.  This is just a prototype so much of that is currently hard-coded values, but you get the idea.

Landei, can you confirm your problem is solved in current CVS? All InputHandlers should trigger a subscribed action; no InputHandler should be able to 'steal' it.

Irrisor, I just downloaded the nightly build, with the same result. Could you please try it, too? I used the class I posted above for testing

Just checked the InputHandlers over here: I couldn't reproduce any 'stealing' of events.



So it seems it's a GameState issue, or the stealing requires a more complex setup.



I used this text class, pressing SPACE fires all three input handlers:

public class TestMultipleInputHandlers extends SimpleGame {
    private InputHandler input1;
    private InputHandler input2;
    private InputHandler input2_child;

    protected void simpleInitGame() {
        input1 = new InputHandler();
        input2 = new InputHandler();
        input2_child = new InputHandler();
        input2.addToAttachedHandlers( input2_child );

        input1.addAction( new InputAction() {
            public void performAction( InputActionEvent evt ) {
                System.out.println( "Input 1 got SPACE event" );
            }
        }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );

        input2.addAction( new InputAction() {
            public void performAction( InputActionEvent evt ) {
                System.out.println( "Input 2 got SPACE event" );
            }
        }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );

        input2_child.addAction( new InputAction() {
            public void performAction( InputActionEvent evt ) {
                System.out.println( "Input 2 child got SPACE event" );
            }
        }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );
    }

    @Override
    protected void simpleUpdate() {
        input1.update( tpf );
        input2.update( tpf );
    }

    public static void main( String[] args ) {
        new TestMultipleInputHandlers().start();
    }
}

What behaviour do you get with the test class from my first post? As I never had any problems with this in SimpleGame, I would also guess that it is a GameState thing.