Adding input commands when engine is running and addig keyboard combinations

Hello! Today I have two questions.


  1. Is there a nice way to allow the user to define keyboard mappings as the engine is running? This could be used for something as simple as re-binding the keys. As far as I understand input now, it consists of some magical numbers in KeyInput which is fine when programming, but less good when the user should supply the input. What I would preffer is simply that the user is allowed to enter text (such as “p” or “Enter”) or possibly that the user presses a key.


  2. Given that I have what is in 1, is there a way to allow the users to use combinations? For instance “Alt + Space” or the like? That would be really useful in my game.



    //Unarmed

Yes. Look at the TestComboMoves.java test case.

Thanks! Love quick replies!



I see, that seems to answer 2 but not 1. I do realize that I could in my application bind each and every button to an action and then test for that. However, it feels to me as if JME would then parse a key to return a command that in turn gets parsed back to a key to (maybe) execute another command. But maybe thats the way to go. Any input on this? (no pun intended)

Hmm. Let me see if I got it! Are you wanting to change the input maps in runtime? Yes, you can. Each input map has a key, then you can change it when you like. Change it in simpleUpdate() method as well. For example:





public void simpleInitApp(){

//here you define the default exit mapping

inputManager.addMapping(“exit”, new KeyTrigger(KeyInput.KEY_ESCAPE));

}





public void simpleUpdate() {

//here you change that

inputManager.addMapping("exit ", new KeyTrigger(KeyInput.KEY_Q));

}





:roll:?

Getting warmer :slight_smile:



In your code, I still need to know that the user wants to map Q to “exit”. In order to do that I must also know KeyInput.KEY_Q. What I would like in the simpleUpdate is something like:



inputManager.addMapping(“exit”, new KeyTrigger(“q”));



or even better:



inputManager.addMapping(“exit”, new KeyTrigger(“Alt + q”));



If I could have done that, then I could also read a string from input and let the user supply the key/combination instead! That would be much neater!

Hmm, now I got it :). Ok, then first you have to listen to any key the user has pressed. Then you could register a RawInputListener in inputManager like this:



[java]

inputManager.addRawInputListener(myListener);

[/java]



and your myListener:



[java]

public class myListener implements RawInputListener {





//Here you check the pressed key by the user

public void onKeyEvent(KeyInputEvent evt) {

if(evt.isPressed())

keyPressed = evt.getKeyChar();

}





}

[/java]



Now you have the keyPressed, then register it :



[java]

inputManager.addMapping(“exit”, new KeyTrigger(keyPressed));

[/java]



Hope this helps.

Yeah, I thought that might be the only way. I’ll see if I can throw a simple factory together for creating KeyTriggers or combinations, otherwise I’ll fallback to this. Thanks for your help!

The raw input listener is the best way, I think. Otherwise you end up making the user type in the key combos? Like “alt q”, “alternate Q”, “ctrl s”, “ctl S”, “control S”… no thanks. :slight_smile:



Most games have you click an action and then you press the keys you want for that action, I guess… that’s what raw input listener gets you.



…still too bad there’s no standard way because that GUI is non-trivial.

I agree with some of that. As for the case sensitive stuff a simple .toLowerCase() will solve that. As for differnent names for the same key, I would simply go with what is written on them. There is an “Alt” key on my keyboard but nothing labeled “Alternative”, This leaves just the whitespace keys and and perhaps something like distinguishing between left and right shift etc. But thats a whole lot less.



And as you say, for most games there is an action and you press what you want for that action. But this time I need something differnent, so factories will come in handy.

Since I didn’t find anything that seemed to do what I wanted I decided to code something myself. It took a couple of runs before I actually got my head around what I wanted and I thought I’d post the results here in case someone googles and finds this. This code allows creation of key combinations and sequences from strings. A key combination is delimited using “-” or “+” such as:

“LCONTROL + A”

“lcontrol + lshift-+ delete”



where the names are the same as those found in KeyInput.java minus the “KEY_”-part. I know that’s not what I said a few posts up, I’m workin on that though. :slight_smile: Key names are case insensitive. Key sequences can also be created using “->” such as:

“A → S ->D”



Of course these two can also be combined to create expressions such as:

“LCONTROL + A → D”

to create a sequence where the user has to press first “LCONTROL + a”, then release both those keys and then press “d”. If any other key is pressed before “d” then no event is generated.



Simple one key combinations are ofcourse also OK.



IMPORTANT: Events will only be sent when an entire sequence is completed. No release events are sent. The “tfp” variable will also always be 0 since this makes less sense in this setting.



This code is not by any means perfect, so use with care. Hopefully it will be of some use to someone. :slight_smile:



Intended use:

[java]MultipleKeyTrigger trig = new MultipleKeyTrigger(inputManager);

trig.addKeySequence(“LCONTROL”);

trig.addKeySequence(“LCONTROL + A”);

trig.addKeySequence(“A”);

trig.addActionListener( new ActionListener() {

public void onAction(String string, boolean bln, float f) {

System.out.println(string + " was pressed");

});[/java]



Helper for getting key names.



[java]



import com.jme3.input.KeyInput;

import com.jme3.input.dummy.DummyKeyInput;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;



/**

  • @author Eng

    */

    public class KeyNameFactory {



    private static Map<String, Integer> mappings = new HashMap<String, Integer>();



    private static void populateMap() {

    DummyKeyInput k = new DummyKeyInput();

    Field[] fields = KeyInput.class.getFields();

    for(Field f : fields) {

    try {

    mappings.put(f.getName().substring(4).toLowerCase(), f.getInt(k));

    } catch (Exception e) {

    // Ignore…

    }

    }

    }



    public static int getKeyValue(String str) {

    if(mappings.isEmpty()) {

    populateMap();

    }

    return mappings.get(str.toLowerCase());

    }

    }

    [/java]



    And finally the class that does the work:

    [java]



    import com.jme3.input.InputManager;

    import com.jme3.input.RawInputListener;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.event.JoyAxisEvent;

    import com.jme3.input.event.JoyButtonEvent;

    import com.jme3.input.event.KeyInputEvent;

    import com.jme3.input.event.MouseButtonEvent;

    import com.jme3.input.event.MouseMotionEvent;

    import com.jme3.input.event.TouchEvent;

    import java.util.ArrayList;

    import java.util.HashSet;

    import java.util.LinkedList;

    import java.util.Set;



    /**

    *
  • @author Eng

    */

    public class MultipleKeyTrigger implements RawInputListener{



    private LinkedList<ActionListener> listeners = new LinkedList<ActionListener>();



    private Set<Integer> pressed = new HashSet<Integer>();

    private Set<KeySequence> sequences = new HashSet<KeySequence>();



    public MultipleKeyTrigger(InputManager im) {

    im.addRawInputListener(this);

    }



    public void addKeySequence(String s) {

    s = s.replaceAll("\s", “”).toLowerCase();

    ArrayList<Set<Integer>> sequence = new ArrayList<Set<Integer>>();

    for(String str : s.split("->")) {

    Set<Integer> set = new HashSet<Integer>();

    for(String subStr : str.split("[-+]")) {

    set.add(KeyNameFactory.getKeyValue(subStr));

    }

    sequence.add(set);

    }

    sequences.add(new KeySequence(s, sequence));

    }





    public void addActionListener(ActionListener al) {

    listeners.add(al);

    }



    public void removeActionListener(ActionListener al) {

    listeners.remove(al);

    }



    private void notifyListeners(String name) {

    for( ActionListener al : listeners) {

    al.onAction(name, true, 0.0f);

    }

    }



    public void beginInput() {}

    public void endInput() {}

    public void onJoyAxisEvent(JoyAxisEvent jae) {}

    public void onJoyButtonEvent(JoyButtonEvent jbe) {}

    public void onKeyEvent(KeyInputEvent kie) {

    if(kie.isPressed()) {

    if(!pressed.contains(kie.getKeyCode())) {

    pressed.add(kie.getKeyCode());

    checkInput();

    }

    } else {

    pressed.remove(kie.getKeyCode());

    checkInput();

    }

    }



    private void checkInput() {

    for(KeySequence ks : sequences) {

    if(ks.readInput()) {

    notifyListeners(ks.getName());

    }

    }

    }



    public void onMouseButtonEvent(MouseButtonEvent mbe) {}

    public void onMouseMotionEvent(MouseMotionEvent mme) {}

    public void onTouchEvent(TouchEvent te) {}



    private class KeySequence {

    private ArrayList<Set<Integer>> keyList;

    private int counter = 0;

    private boolean waitingForBlank = false;



    private String name;



    public KeySequence(String name, ArrayList<Set<Integer>> keyList) {

    this.name = name;

    this.keyList = keyList;

    }



    public String getName() {

    return name;

    }



    public boolean readInput() {

    if(waitingForBlank) {

    if (MultipleKeyTrigger.this.pressed.isEmpty()) {

    waitingForBlank = false;

    }

    return false;

    }



    for(Integer i : MultipleKeyTrigger.this.pressed) {

    //Is there something pressed that I don’t like?

    if(!keyList.get(counter).contains(i)) {

    counter = 0;

    waitingForBlank = false;

    return false;

    }

    }



    for(Integer i : keyList.get(counter)) {

    //Am I still waiting?

    if(!MultipleKeyTrigger.this.pressed.contains(i)) {

    return false;

    }

    }



    counter++;

    // Got input. Am I done?

    if(counter == keyList.size()) {

    counter = 0;

    waitingForBlank = true;

    return true;

    }



    //Nope. Let’s wait for blank

    waitingForBlank = true;

    return false;

    }

    }

    }[/java]
2 Likes

@unarmed, thx on the code!
I am currently using the normal input and when shift or ctrl or alt are pressed, I set booleans (false on release), and code based on that.
But gotta try your code!

@teique said: @unarmed, thx on the code! I am currently using the normal input and when shift or ctrl or alt are pressed, I set booleans (false on release), and code based on that. But gotta try your code!

Note: in the 3 years since this thread was last active there have been other developments also. The Lemur GUI library has an InputMapper class that wraps InputManager and provides support for combinations of keys. For example, you can bind different functions to W and Shitf-W. It also has a few other nice things. The Lemur library is relatively small and you can use the InputMapper without having to use any of the other stuff.

http://hub.jmonkeyengine.org/forum/board/projects/lemur/