user2220771
user2220771

Reputation: 459

KeyListener - Ignore Key Released When Holding Down (Ubuntu, ...)

I have a game where you can attack. When the SPACE-key is pressed, the weapon moves down, and when it's released it should move up.

The problem is that on (certain) UNIX systems, the KeyReleased event is also triggered while holding the key down. I was working with a BitSet to keep track of which keys are pressed, but because the KeyReleased keeps firing, the weapon just moves up and down really fast on my Ubuntu-machine.

I can move with my two characters at the same time, but I cannot keep the weapon down while the spacebar is pressed.

(What really happens when holding the key for a few seconds is: keyPressed - keyReleased - keyPressed - keyReleased - keyPressed - keyReleased - ....)

Here is a System.out from what happens, with the System.currentTimeMillis() before each action:

1368696607559 - Pressed
1368696608052 - Released
1368696608053 - Pressed
1368696608082 - Released
1368696608083 - Pressed
1368696608112 - Released
1368696608113 - Pressed
1368696608143 - Released
1368696608144 - Pressed
1368696608173 - Released

The code I'm using is:

//Inner Class
private class KeyDispatcher extends KeyAdapter implements ActionListener{

    private BitSet keyBits = new BitSet(256);
    private Timer timer = new Timer(20, this);
    boolean player1Attack = false, player2Attack = false;

    public KeyDispatcher(){
        timer.start();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        keyBits.set(keyCode);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int keyCode = e.getKeyCode();
        keyBits.clear(keyCode);
        if(player1Attack && !isKeyPressed(KeyEvent.VK_SPACE)){
            controller.stopAttack(0);
            player1Attack = false;
        }
        //player 2
        ...
    }

    public boolean isKeyPressed(final int keyCode) {
        return keyBits.get(keyCode);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if(isGameOver()){
            timer.stop();
        }else{
            //player1
            if(!player1Attack && isKeyPressed(KeyEvent.VK_RIGHT)){
                controller.moveRight(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_UP)){
                controller.moveUp(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_DOWN)){
                controller.moveDown(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_LEFT)){
                controller.moveLeft(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_NUMPAD0)){
                controller.attack(0); player1Attack = true; update();
            }
            //player2
            ...
        }
    }

}

I also tried replacing it with KeyBinding, as these seem more recommended, but here the same 'problem' remains (the released action keeps firing while it's pressed)...

    Action attack = new AbstractAction(){

        @Override
        public void actionPerformed(ActionEvent e) {
            controller.attack(0);
        }

    };
    Action stopAttack = new AbstractAction(){

        @Override
        public void actionPerformed(ActionEvent e) {
            controller.stopAttack(0);
        }

    };
    background.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "attack");
    background.getActionMap().put("attack", attack);

    background.getInputMap().put(KeyStroke.getKeyStroke("released SPACE"), "stopAttack");
    background.getActionMap().put("stopAttack", stopAttack);

I have read multiple topics on this situation, but haven't been able to get it working.

Thanks in advance

Upvotes: 2

Views: 2404

Answers (3)

tstew
tstew

Reputation: 131

You should try using Key Bindings.

More information from docs here

Upvotes: 0

user2220771
user2220771

Reputation: 459

I got it kind of working with keeping track of the times like Rockster suggested. Because the keyPressed is fired almost immediately after the keyReleased, you can check if: keyPressedTime

When the application is busy (adding enemies to the field in my case), this sometimes failes because the keyPressed isn't fired immediately. But for this application this is not a big deal.

The code for those interested:

//Inner Class
private class KeyDispatcher extends KeyAdapter implements ActionListener{

    private long p1AttackLastKeyRelease = 0, p1AttackLastKeyPressed = 0;
    private BitSet keyBits = new BitSet(256);
    private Timer timer = new Timer(20, this);
    boolean player1Attack = false;

    public KeyDispatcher(){
        timer.start();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if(keyCode == KeyEvent.VK_NUMPAD0){
            p1AttackLastKeyPressed = System.currentTimeMillis();
        }
        keyBits.set(keyCode);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if(keyCode == KeyEvent.VK_NUMPAD0){
            p1AttackLastKeyRelease = System.currentTimeMillis();
        }
        keyBits.clear(keyCode);
    }

    public boolean isKeyPressed(final int keyCode) {
        return keyBits.get(keyCode);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if(isGameOver()){
            timer.stop();
        }else{
            //release the key
            if(p1AttackLastKeyPressed != 0 && p1AttackLastKeyPressed<p1AttackLastKeyRelease && player1Attack && !isKeyPressed(KeyEvent.VK_NUMPAD0)){
                p1AttackLastKeyPressed = 0;
                controller.stopAttack(0);
                player1Attack = false;
            }
            //do the actions of the keys that are down
            if(!player1Attack && isKeyPressed(KeyEvent.VK_RIGHT)){
                controller.moveRight(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_UP)){
                controller.moveUp(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_DOWN)){
                controller.moveDown(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_LEFT)){
                controller.moveLeft(0);
            }
            if(!player1Attack && isKeyPressed(KeyEvent.VK_NUMPAD0) && !isKeyPressed(KeyEvent.VK_UP) && !isKeyPressed(KeyEvent.VK_DOWN) && !isKeyPressed(KeyEvent.VK_LEFT) && !isKeyPressed(KeyEvent.VK_RIGHT)){
                controller.attack(0); player1Attack = true; update();
            }
        }
    }

}

This still isn't fully working like I said before, but good enough for this application (the players cannot get advantage out of it).

Upvotes: 0

Oscar Ortiz
Oscar Ortiz

Reputation: 813

So let's put an example, in one of those UBUNTU machines that you have a problem; Let's say they press and hold a keyboard button for 1000 milliseconds.

According to your comments, this is what would happen on the time line:

0 ms         -    10ms         -   1000ms
keyPressed      keyReleased        keyReleased

So in these problematic computers, you have 1x keyPressed event, and 2x keyReleased events. The second keyReleased event, being the bad guy.

I would do the following to "Patch" the problem (Until you properly identify the difference in those UBUNTU computers):

(Note I'm using System time, and not a fixed MS per second, since the KeyEvents happen on the UI thread and not a game or OpenGL thread)

//This isn't the 10ms of the example, but if this doesn't work, try increasing it a bit further or 
//try meassuring the exact time of the "faulty/not wanted" first onKeyReleased event  
final private static long KEY_RELEASE_TIME_LOCK_IN_MS = 100L;
private static long lastKeyRelease_TimeMillis;

@Override
public void keyPressed(KeyEvent e) {

    int keyCode = e.getKeyCode();
    keyBits.set(keyCode);


}



@Override
public void keyReleased(KeyEvent e) {
    final long currentTimeMillis = System.currentTimeMillis();

    if( (currentTimeMillis - lastKeyRelease_TimeMillis) <= KEY_RELEASE_TIME_LOCK_IN_MS){
        //We just ignored the keyRelease!, we are not allowing keyReleases that fast!
        return;
    }


    int keyCode = e.getKeyCode();
    keyBits.clear(keyCode);
    if(player1Attack && !isKeyPressed(KeyEvent.VK_SPACE)){
        controller.stopAttack(0);
        player1Attack = false;
    }

    lastKeyRelease_TimeMillis = System.currentTimeMillis();

    //player 2
    ...
}


//Rockster., let me know if it worked out

Upvotes: 3

Related Questions