Explanation Of & Issues with the new Input System (com.jme3.input.controls)

I've been trying to understand the new Input system for the last 3 or 4 hours and I think I've finally come to grips with it. For those of you wondering, here's the basic gist of it:


  • 1) Create a new Controls instance with the initialized inputs as parameters. (This needs to be done instead of, or after, initializing an InputManager instance.

  • 2) Add all of the bindings you want (now called mappings). Using the addMapping method, the first paramter is the name of the mapping, and the second is one of the following:

      [li]new KeyTrigger(KeyInput.keyCode)
    • new MouseAxisTrigger(mouseAxis, negative)  -- I'm not sure what negative means, but I think it's basically the direction.

    • new MouseButtonTrigger(mouseButton)


    [/li]
  • 3) Once you've added all of the bindings, make a String array called mappings with them all in, and call
           controls.addListener(this, mappings)

  • 4) For this to work, you need to make the class implement ActionListener, which requires you to add the method
           onAction(String name, boolean value, float tpf)
    which works similarly to the old onBinding method.

      [li]The name is now called 'name', instead of 'binding'.
    • The boolean 'value' is true every time except for the last time when the key is released.

    • The float 'tpf' is, I think, the time per frame (I'm just guessing from the name!)


That's pretty much all there is to it. I've modified my Application, SimpleApplication and FlyByCamera classes so they work with Controls rather than InputManager. I can post them if anyone is interest, but all of the sample and test applications will need to be modified anyway.

While I was trying to work all this out, I've noticed a couple of issues (some of which I'm sure I've forgotten since I started typing this up :P):

  • Using the float value passed to onAction to move the camera seems to result in much slower camera movement than before. I'm not sure if that is as planned or not.

  • Applications don't seem to be grabbing the focus of my mouse. I can still see a cursor (InputManager has a 'setCursorVisible' method, whereas Controls doesn't). The strange thing is some input from my mouse does seem to get through to the application. If I move my mouse wildly about, every so often, the camera will rotate.

  • [li]Also, as far as I can tell, SimpleApplication now needs to have an empty simpleAction method, which has the same parameters as onAction, that can be overridden by any class that extends it. This allows mappings that aren't relevant to SimpleApplication to be passed on.


If I've understood the new system properly, I don't mind going through all the test and sample applications to update them, however I don't want to do that if there's any easier way to do certain things.
InfernoZeus said:

I've been trying to understand the new Input system for the last 3 or 4 hours and I think I've finally come to grips with it. For those of you wondering, here's the basic gist of it:

  • 1) Create a new Controls instance with the initialized inputs as parameters. (This needs to be done instead of, or after, initializing an InputManager instance.

  • 2) Add all of the bindings you want (now called mappings). Using the addMapping method, the first paramter is the name of the mapping, and the second is one of the following:

      [li]new KeyTrigger(KeyInput.keyCode)
    • new MouseAxisTrigger(mouseAxis, negative)  -- I'm not sure what negative means, but I think it's basically the direction.

    • new MouseButtonTrigger(mouseButton)


    [/li]
  • 3) Once you've added all of the bindings, make a String array called mappings with them all in, and call
            controls.addListener(this, mappings)

  • 4) For this to work, you need to make the class implement ActionListener, which requires you to add the method
            onAction(String name, boolean value, float tpf)
    which works similarly to the old onBinding method.

      [li]The name is now called 'name', instead of 'binding'.
    • The boolean 'value' is true every time except for the last time when the key is released.

    • The float 'tpf' is, I think, the time per frame (I'm just guessing from the name!)


That's pretty much all there is to it. I've modified my Application, SimpleApplication and FlyByCamera classes so they work with Controls rather than InputManager. I can post them if anyone is interest, but all of the sample and test applications will need to be modified anyway.

While I was trying to work all this out, I've noticed a couple of issues (some of which I'm sure I've forgotten since I started typing this up :P):

  • Using the float value passed to onAction to move the camera seems to result in much slower camera movement than before. I'm not sure if that is as planned or not.

  • Applications don't seem to be grabbing the focus of my mouse. I can still see a cursor (InputManager has a 'setCursorVisible' method, whereas Controls doesn't). The strange thing is some input from my mouse does seem to get through to the application. If I move my mouse wildly about, every so often, the camera will rotate.

  • [li]Also, as far as I can tell, SimpleApplication now needs to have an empty simpleAction method, which has the same parameters as onAction, that can be overridden by any class that extends it. This allows mappings that aren't relevant to SimpleApplication to be passed on.


If I've understood the new system properly, I don't mind going through all the test and sample applications to update them, however I don't want to do that if there's any easier way to do certain things.


Hi,
can you post your example here ?
xieu90 said:

Hi,
can you post your example here ?

I will do later (in like 5 hours time). I'm on my phone at the moment.

The reason you are still seeing the mouse is probably because the code that hides the mouse uses input manager and the code that hides the most may not be implemented in the new control system just yet however i am unsure.



I would figure you would use the inline methods to add a listener instead of implementing actionListener so instead of controls.addListener(this, mappings) you would have  controls.addListener(new ActionListener(){/*your code *///override w.e functions are here}, mappings) the same way its done for inputManager.



I think the only binding classes that are supported for the new input manager now is ActionListener

which gets events like "key is pressed" and doesn't allow for repeats and AnalogListener and the other for analog like mouse axis if you want repeatable, use AnalogListener.

Try changing your flycam to use AnalogListener instead which allows repeat the reason it worked with actionlistener before was because it wasn't repeatable



Im not sure about everything though as i haven't followed the progress of the new control manager. I am pretty sure once momoko gets on he will be able to give a better explanation with sound, lights and flashy effects … in the post =p  :mrgreen:



~Fear the Monkeys  :-o

Bonechilla said:

The reason you are still seeing the mouse is probably because the code that hides the mouse uses input manager and the code that hides the most may not be implemented in the new control system just yet however i am unsure.

I guessed as much, I'll take a look at the InputManager's setCursorVisible method, and see if the same can be done for Controls.

Bonechilla said:

I would figure you would use the inline methods to add a listener instead of implementing actionListener so instead of controls.addListener(this, mappings) you would have  controls.addListener(new ActionListener(){/*your code *///override w.e functions are here}, mappings) the same way its done for inputManager.

Either way works I think. Personally I like to have my method separate so it's easier to find, and I think it looks nicer.  :)

Bonechilla said:

I think the only binding classes that are supported for the new input manager now is ActionListener
which gets events like "key is pressed" and doesn't allow for repeats and AnalogListener and the other for analog like mouse axis if you want repeatable, use AnalogListener.
Try changing your flycam to use AnalogListener instead which allows repeat the reason it worked with actionlistener before was because it wasn't repeatable

Thanks for that, I've made flycam implement ActionListener and AnalogListener. The onAnalog is for the movement of the cursor, and the onAction is for the key presses. I tried putting it all in onAnalog but the key presses didn't seem to do anything. This works alright, but when I move the camera for the Strafe/Forward/Backward, I had to multiply the tpf by 10 to make the movement visible.


Another thing I've noticed is it doesn't seem capable of reacting to two buttons at once, such as walking forward and strafing to the side. Any idea how I can manage that?

I've attached my Application.java, SimpleApplication.java, FlyByCamera.java and HelloInput.java classes. HelloInput doesn't do much other than show a box, move around it and prints out a debug message when you press 'B'.

InfernoZeus said:

I've attached my Application.java, SimpleApplication.java, FlyByCamera.java and HelloInput.java classes. HelloInput doesn't do much other than show a box, move around it and prints out a debug message when you press 'B'.


i can't see your files right now but im guessing your using elseif blocks just try using if statements instead because else if states loop through list until one statment is true then break elseif so other else if statements wouldn't be called.

Thanks InfernoZeus, I followed your tips and got something somewhat working.



Here's my (preliminary) sample code:

http://www.jmonkeyengine.com/wiki/doku.php/jme3:beginner:hello_input_system#asidenew_input_system



My code only responds to a few key presses (no fly cam yet) and it only works when I comment out all lines in Controls.java that throw an UnsupportedOperationException. That's likely not the recommended way to go, but for my test case, it works…


I copied the code from zathras's tut and I always got this error (whenever I press any keys)

INFO Node 9:10:42 PM Child (Box) attached to this node (Root Node)

WARNING LwjglRenderer 9:10:43 PM Uniform m_VertexColor is not declared in shader.

SEVERE Application 9:10:43 PM Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]

java.lang.UnsupportedOperationException: KeyInput has raised an event at an illegal time.

at com.jme3.input.controls.Controls.onKeyEvent(Controls.java:227)

at com.jme3.input.lwjgl.LwjglKeyInput.update(LwjglKeyInput.java:45)

at com.jme3.app.Application.update(Application.java:383)

at com.jme3.app.SimpleApplication.update(SimpleApplication.java:149)

at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:112)

at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:162)

at java.lang.Thread.run(Thread.java:619)

AL lib: ALc.c:1352: exit(): closing 1 Device

AL lib: ALc.c:1329: alcCloseDevice(): destroying 1 Context

AL lib: alSource.c:2361: alcDestroyContext(): deleting 7 Source(s)




import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.math.ColorRGBA;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.Controls;
import com.jme3.input.controls.KeyTrigger;
 
public class inputSystem extends SimpleApplication implements ActionListener { //I just changed the name of class
 
    public static void main(String[] args){
        inputSystem app = new inputSystem();
        app.start();
    }
 
    protected Geometry player;
    Controls c;
    Boolean isRunning;
 
    @Override
    public void simpleInitApp() {
        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
        player = new Geometry("Box", b);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
        mat.setColor("m_Color", ColorRGBA.Blue);
        player.setMaterial(mat);
        rootNode.attachChild(player);
        isRunning=false;
        initKeys(); // load my custom keybinding
    }
 
    /** Custom Keybinding: Register named bindings for each input  */
    private void initKeys() {
      c = new Controls(keyInput, mouseInput, joyInput);
      c.addMapping("Pause", new KeyTrigger(KeyInput.KEY_P));
      c.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE));
      c.addListener(this, new String[]{"Pause","Rotate"} );
    }
 
  public void onAction(String name, boolean mouseDown, float tpf) {
    if( name.equals("Pause") && !mouseDown ) {
       if(isRunning)System.out.println ("OK, pausing the game...");
       else System.out.println("OK, unpausing the game...");
       isRunning=!isRunning;
    }
    if(isRunning){
        if( name.equals("Rotate") ) {
            player.rotate(0, 0.5f, 0);
        }
      } else
        System.out.println ("Press P to unpause.");
    }
}


Bonechilla said:

i can't see your files right now but im guessing your using elseif blocks just try using if statements instead because else if states loop through list until one statment is true then break elseif so other else if statements wouldn't be called.

Lol, so obvious  :// How did I not realise that?! Thanks though  :wink:

zathras said:

Thanks InfernoZeus, I followed your tips and got something somewhat working.

Here's my (preliminary) sample code:
http://www.jmonkeyengine.com/wiki/doku.php/jme3:beginner:hello_input_system#asidenew_input_system

My code only responds to a few key presses (no fly cam yet) and it only works when I comment out all lines in Controls.java that throw an UnsupportedOperationException. That's likely not the recommended way to go, but for my test case, it works...

No worries, I'll have a look at your sample code if you want. Can you tell me which lines you had to comment out, and I'll see what solution I found.

xieu90 said:

I copied the code from zathras's tut and I always got this error (whenever I press any keys)
INFO Node 9:10:42 PM Child (Box) attached to this node (Root Node)
WARNING LwjglRenderer 9:10:43 PM Uniform m_VertexColor is not declared in shader.
SEVERE Application 9:10:43 PM Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.UnsupportedOperationException: KeyInput has raised an event at an illegal time.
at com.jme3.input.controls.Controls.onKeyEvent(Controls.java:227)
at com.jme3.input.lwjgl.LwjglKeyInput.update(LwjglKeyInput.java:45)
at com.jme3.app.Application.update(Application.java:383)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:149)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:112)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:162)
at java.lang.Thread.run(Thread.java:619)
AL lib: ALc.c:1352: exit(): closing 1 Device
AL lib: ALc.c:1329: alcCloseDevice(): destroying 1 Context
AL lib: alSource.c:2361: alcDestroyContext(): deleting 7 Source(s)

Is that using my Application/SimpleApplication/FlyByCamera? You need to modify all three if you are extending SimpleApplication.

@xieu90


xieu90 said:

SEVERE Application 9:10:43 PM Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.UnsupportedOperationException: KeyInput has raised an event at an illegal time.


Yes. As I wrote above: My sample only works if you uncomment the lines that throw UnsupportedOperationExceptions from the source code in com/jme3/input/controls/Controls.java. ( if (!eventsPermitted) throw new UnsupportedOperationException() ) Did you do that?

Kirill has just written this new class and is still working on it, it's just not completed yet, I guess we have to wait.

@InfernoZeus: I don't see either how we could respond to a modifier plus key... Do I have to flip my own "modifier on/off" variable?

I extended SimpleApplication

I downloaded all of your file, but I dont think I used them. they are still in download folder, should I move them to source code in nightly build to make it work? or how should I use them?

xieu90 said:

I extended SimpleApplication
I downloaded all of your file, but I dont think I used them. they are still in download folder, should I move them to source code in nightly build to make it work? or how should I use them?

Yeh, I think that's the easiest way. I've got my Eclipse setup connected to the svn, and I regularly update the source. Then I can just edit the source files, and when it updates next, I choose to ignore all the changes I made. Obviously, if you're not using Eclipse, or you're downloading the nightly builds, things will be slightly different.

@Zathras

sorry I didnt see it, so I didnt do it



now I did it but it still give me error

SEVERE Application 9:49:54 PM Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]

java.lang.UnsupportedOperationException: MouseInput has raised an event at an illegal time.

at com.jme3.input.controls.Controls.onMouseMotionEvent(Controls.java:200)



althought I commented it (line 200,218,227,191,178) save the file (with admin in linux) and refereshed the jm3 library in eclipse


  public void onMouseMotionEvent(MouseMotionEvent evt) {
      //  if (!eventsPermitted)
     //       throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");



@InfernoZeus
I also copied your three files simple,app and fly to app folder in source code folder and refreshed eclipse but it is still the same.
zathras said:

@xieu90

xieu90 said:

SEVERE Application 9:10:43 PM Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.UnsupportedOperationException: KeyInput has raised an event at an illegal time.


Yes. As I wrote above: My sample only works if you uncomment the lines that throw UnsupportedOperationExceptions from the source code in com/jme3/input/controls/Controls.java. ( if (!eventsPermitted) throw new UnsupportedOperationException() ) Did you do that?



A better solution for that is to modify your Application.java class, in the update method, change

        if (inputEnabled){
            if (mouseInput != null)
                mouseInput.update();

            if (keyInput != null)
                keyInput.update();

            if (joyInput != null)
                joyInput.update();

            inputManager.update(timer.getTimePerFrame());
        }



to

if (inputEnabled){

           controls.update(timer.getTimePerFrame());

        }



zathras said:

@InfernoZeus: I don't see either how we could respond to a modifier plus key... Do I have to flip my own "modifier on/off" variable?

I'm sorry, I don't really understand what you mean? Is this in reference to the Pause button exercise?
xieu90 said:

@Zathras
sorry I didnt see it, so I didnt do it

now I did it but it still give me error
SEVERE Application 9:49:54 PM Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.UnsupportedOperationException: MouseInput has raised an event at an illegal time.
at com.jme3.input.controls.Controls.onMouseMotionEvent(Controls.java:200)

althought I commented it (line 200,218,227,191,178) save the file (with admin in linux) and refereshed the jm3 library in eclipse

  public void onMouseMotionEvent(MouseMotionEvent evt) {
      //  if (!eventsPermitted)
     //       throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");



Have a look at my post above, where I propose, what I think is, a better solution. :)

xieu90 said:

@InfernoZeus
I also copied your three files simple,app and fly to app folder in source code folder and refreshed eclipse but it is still the same.

I'm not sure exactly why it's not working. Alternatively, open the files I uploaded, copy the code, and then paste it into the appropriate class files in Eclipse.

I opened the application file and it is already like you told me (application file was overwritten by your file before ^^)

I just dont know where the flycam file is. but I dont think it is because of that file.



I am not sure, but I feel like nightly build doesnt depend on source code and java doc, so if I modifiy the source code it wont affect the jme lib in eclipse (just a feeling)

xieu90 said:

I opened the application file and it is already like you told me (application file was overwritten by your file before ^^)
I just dont know where the flycam file is. but I dont think it is because of that file.

FlyByCamera is in com.jme3.input

xieu90 said:

I am not sure, but I feel like nightly build doesnt depend on source code and java doc, so if I modifiy the source code it wont effect the jme lib in eclipse (just a feeling)

I'm not sure what you mean by that? Do the nightly builds include the source files, or is it just a jar? What have you added to the projects build path? Obviously if you've got a jar in the build path, but you're editing the source files, nothing is going to change.

The "Input has raised an event at an illegal time" occurs when the Input subclasses' update methods are called from somewhere other than Controls.update(). The only place that happens as far as I can recall is in the Application, which I removed, and no longer throws an error for me.

@xieu90: If you are using my code, then please note that it is not ONE line to comment out, but several similar ones! And yes, the nightly build is independent of your own sources, you got that right.

I have the suspicion that you are mixing InfernoZeus' files with mine. There are two related conversations in one thread, sorry. :slight_smile:


InfernoZeus said:

A better solution for that is to modify your Application.java class, in the update method, change

if (inputEnabled){
           controls.update(timer.getTimePerFrame());
        }




Aaaah, yes, thanks, better solution.

InfernoZeus said:

zathras said:

@InfernoZeus: I don't see either how we could respond to a modifier plus key... Do I have to flip my own "modifier on/off" variable?
I'm sorry, I don't really understand what you mean? Is this in reference to the Pause button exercise?


No. Was it not you who said: "Another thing I've noticed is it doesn't seem capable of reacting to two buttons at once, such as walking forward and strafing to the side. Any idea how I can manage that?"? My reply to that: I'd try to flip a boolean variables for the modifier. Don't know whether it really works in an efficient way, but, I mean like this:

if( name.equals("Fast") && pressed ) { fast=true;  }
        if( name.equals("Fast") && !pressed ) { fast=false; }
        if( name.equals("Forward") ) {
          Vector3f v = player.getLocalTranslation();
           if(fast) player.setLocalTranslation(v.x+1f, v.y, v.z);
           else    player.setLocalTranslation(v.x+0.5f, v.y, v.z);
        }



PS: Hm no wait. Actually you didn't ask for shift+arrow key combinations -- you asked for all kinds of combinations of directional keys... Hm... my method does not solve that.
zathras said:

No. Was it not you who said: "Another thing I've noticed is it doesn't seem capable of reacting to two buttons at once, such as walking forward and strafing to the side. Any idea how I can manage that?"? My reply to that: I'd try to flip a boolean variables for the modifier. Don't know whether it really works in an efficient way, but, I mean like this:

*snip*

PS: Hm no wait. Actually you didn't ask for shift+arrow key combinations -- you asked for all kinds of combinations of directional keys... Hm... my method does not solve that.


Ah yes, I did ask that :P A solution was proposed by Bonechilla; I simply had to use multiple if statements, instead of if/elseif/elseif/... like this:


if (name.equals("FLYCAM_Left")){
            rotateCamera(value, initialUpVec);
        }
if (name.equals("FLYCAM_Right")){
            rotateCamera(-value, initialUpVec);
        }
if (name.equals("FLYCAM_Up")){
            rotateCamera(value, cam.getLeft());
        }
if (name.equals("FLYCAM_Down")){
            rotateCamera(-value, cam.getLeft());
        }
if (name.equals("FLYCAM_ZoomIn")){
            zoomCamera(value);
        }
if (name.equals("FLYCAM_ZoomOut")){
            zoomCamera(-value);
        }



instead of

if (name.equals("FLYCAM_Left")){
            rotateCamera(value, initialUpVec);
        }else if (name.equals("FLYCAM_Right")){
            rotateCamera(-value, initialUpVec);
        }else if (name.equals("FLYCAM_Up")){
            rotateCamera(value, cam.getLeft());
        }else if (name.equals("FLYCAM_Down")){
            rotateCamera(-value, cam.getLeft());
        }else if (name.equals("FLYCAM_ZoomIn")){
            zoomCamera(value);
        }else if (name.equals("FLYCAM_ZoomOut")){
            zoomCamera(-value);
        }